reflex-pdfslick
- Source: https://github.com/ecrespo/reflex-pdfslick
- Issues: https://github.com/ecrespo/reflex-pdfslick/issues
- PDFSlick (upstream): https://github.com/pdfslick/pdfslick — https://pdfslick.dev
A native Reflex component that wraps
PDFSlick so you can view and interact with PDF
documents in pure Python — no HTML, no iframe, no hand-written
JavaScript.
PDFSlick is built on top of Mozilla's PDF.js
and uses Zustand to expose a reactive store
for the loaded document. reflex-pdfslick bridges that store and PDFSlick's
imperative API into Reflex's declarative, state-driven model: document state
(page number, page count, zoom, rotation, metadata, thumbnails) flows back to
Python as events, and Python triggers actions (go to page, zoom, rotate, print,
download) on the viewer.
Project status —
ALPHA, published on PyPI. The component runtime, imperative controls, and the full 7-example demo gallery are built and covered by tests; the demo compiles to a Reflex production build in CI. See Roadmap for details.
Requirements
| Dependency | Supported version |
|---|---|
| Python | 3.11 – 3.13 |
| Reflex | >= 0.10.0 (tested with 0.10.0) |
| Node.js | 20+ (used by Reflex to build the frontend; CI runs on Node 24) |
reflex-pdfslick0.1.0 requires Reflex 0.10 and drops Python 3.10, matching Reflex's ownrequires-python >= 3.11. Stay onreflex-pdfslick==0.0.1if you are pinned to Reflex 0.9.x.
import reflex as rx
from reflex_pdfslick import pdf_slick
class State(rx.State):
page_number: int = 1
num_pages: int = 0
@rx.event
def on_page_change(self, page: int):
self.page_number = page
@rx.event
def on_document_load(self, num_pages: int):
self.num_pages = num_pages
def index() -> rx.Component:
return pdf_slick(
url="/sample.pdf",
scale_value="page-fit",
single_page_viewer=True,
on_page_change=State.on_page_change,
on_document_load=State.on_document_load,
height="700px",
)
app = rx.App()
app.add_page(index)
Why a native component instead of an iframe?
Embedding a PDF viewer through an iframe or a raw <embed> works, but it is
isolated from your app: you cannot react to page changes, drive the zoom from a
Python toolbar, or read the document's metadata into your state. Wrapping
PDFSlick as a first-class Reflex component means:
- Reactive state —
page_number,num_pages,scale,rotation,is_document_loadedand document metadata are pushed into Reflex state via typed events. - Imperative control from Python — go to page, zoom in/out, fit width/page, rotate, switch scroll/spread modes, print and download are exposed as component-level controls.
- Reusable & versioned — installable with
pip, with the npm dependency (@pdfslick/react) pinned for reproducible builds. - SSR-safe — PDF.js is browser-only, so the component is wrapped as a
Reflex
NoSSRComponentand only ever renders on the client.
Installation
pip install reflex-pdfslick
# or, from source
git clone https://github.com/ecrespo/reflex-pdfslick.git
cd reflex-pdfslick
pip install -e .
Reflex installs the underlying npm packages automatically on first run. The
component pins @pdfslick/react and pdfjs-dist (which pull in
@pdfslick/core and zustand) and imports the required stylesheet
@pdfslick/react/dist/pdf_viewer.css for you.
Custom-component packaging (build & publish)
This repository is a reusable Reflex
component package: the code
lives in custom_components/reflex_pdfslick/ (shipping the PdfSlickWrapper.tsx
wrapper and a committed pdfslick.pyi stub), the demo app in pdfslick_demo/,
and pyproject.toml carries the reflex-custom-components / reflex-component
keywords used to discover it on PyPI.
Reflex 0.10 note: the
reflex component init/build/shareCLI was removed in Reflex 0.10. Packages are now built with standard Python tooling (uv build) and stubs are generated with Reflex's own stub generator.
# Regenerate the .pyi type stub after changing props/events.
PYTHONPATH=custom_components uv run python -m reflex_base.utils.pyi_generator \
custom_components/reflex_pdfslick
# Build the wheel + sdist into dist/ and validate the metadata.
uv build
uvx twine check --strict dist/*
# Manual publish (bump the version in pyproject.toml first):
uv publish # or: uv run twine upload dist/*
Automated release (GitHub Actions)
Releases are published by .github/workflows/publish.yml when a vX.Y.Z tag
that points to a commit on main is pushed:
# 1. Bump `version` in pyproject.toml and `__version__` in __init__.py,
# update CHANGELOG.md, merge the PR into main.
git checkout main && git pull
git tag -a v0.1.0 -m "reflex-pdfslick 0.1.0"
git push origin v0.1.0
The workflow checks that the tag matches the package version, runs the tests,
builds the sdist + wheel and uploads them to PyPI (Trusted Publishing / OIDC,
or a PYPI_API_TOKEN repository secret), then creates the GitHub Release.
The built wheel includes the React wrapper (reflex_pdfslick/PdfSlickWrapper.tsx)
and the type stub, so rx.asset(...) resolves the wrapper when the package is
installed from PyPI — not only in editable mode.
Planned API (summary)
The full contract lives in specs/api/component-api.md.
| Prop | Type | Description |
|---|---|---|
url |
str |
Path/URL of the PDF to load. |
scale_value |
str |
"auto", "page-fit", "page-width", "page-actual" or a numeric string. |
single_page_viewer |
bool |
Render a single page at a time. |
thumbnail_width |
int |
Width (px) of generated thumbnails. |
remove_page_borders |
bool |
Hide page borders. |
| Event | Payload | Fired when |
|---|---|---|
on_document_load |
int (num pages) |
The document finishes loading. |
on_page_change |
int (page) |
The current page changes. |
on_scale_change |
float (scale) |
The zoom level changes. |
on_error |
dict |
Loading/parsing fails. |
Imperative controls (next/previous page, zoom, rotate, print, download) are driven from Python state — see the API spec for the dispatch mechanism.
Examples (demo gallery)
The pdfslick_demo/ Reflex app mirrors PDFSlick's own example set. Each example
is documented in the PRD and
task breakdown:
- Simple Viewer — basic viewer with prev/next navigation and zoom.
- Full Viewer App — toolbar, zoom selector, rotation, scroll/spread modes, document info, search and thumbnails sidebar.
- Thumbnails Layout — a grid of page thumbnails as the primary surface.
- Horizontal Thumbnails — a horizontal thumbnail strip.
- Multiple Documents — several independent viewers on one page.
- Load from ArrayBuffer — open a PDF from in-memory bytes.
- Error Handling — graceful handling of load/parse errors.
Run the demo:
cd pdfslick_demo
pip install -r requirements.txt
reflex run
Architecture (at a glance)
Python (Reflex app)
pdf_slick(url=..., scale_value=..., on_page_change=...)
│ compiles to
▼
React Router + Vite (generated by Reflex)
<PdfSlickWrapper> ← local .tsx, NoSSR, calls usePDFSlick(url, options)
│ renders <PDFSlickViewer/> + (optional) thumbnails
│ subscribes to usePDFSlickStore selectors → emits Reflex events
▼
@pdfslick/react → @pdfslick/core → PDF.js (pdfjs-dist) + Zustand store
Because PDFSlick's public surface is the hook usePDFSlick(...) (not a
drop-in component) and its thumbnails component uses a render-prop child, the
wrapper is a small local .tsx component that calls the hook internally and
exposes a clean prop/event API to Python. Full rationale and trade-offs are in
specs/technical/architecture.md.
Spec-Driven Design documentation
This project is documented before it is built. The specs/ directory is the
source of truth:
| Document | Purpose |
|---|---|
specs/prd/pdfslick-component.md |
Product requirements, goals, scope, examples. |
specs/technical/architecture.md |
Technical design, SSR strategy, wrapper pattern. |
specs/api/component-api.md |
Public Python API: props, events, controls. |
specs/data-model/store-schema.md |
PDFSlick store schema and Python ↔ JS mapping. |
specs/plans/implementation-plan.md |
Phases, milestones, sequencing. |
specs/tasks/task-breakdown.md |
Granular, trackable task list per phase. |
specs/research/pdfslick-and-reflex-research.md |
Primary-source research notes that ground the design. |
Roadmap
- Phase 0 — Foundation & SDD: repository, packaging, SDD artifacts, README, demo skeleton.
- Phase 1 — Core wrapper:
PdfSlickWrapper.tsx,pdf_slickcomponent, props, CSS import, NoSSR. - Phase 2 — Events & state bridge: page/scale/load/error/metadata events.
- Phase 3 — Imperative controls: navigation, zoom, rotation, modes,
print, download (declarative
commandprop +commandshelpers). - Phase 4 — Thumbnails & multi-document: vertical/horizontal thumbnails, independent multi-instance viewers.
- Phase 5 — Demo gallery (all 7 examples) & docs polish.
- Phase 6 — Tests & CI: 36 unit tests, GitHub Actions (pytest 3.11–3.13, TSX transpile, demo build smoke on Node 24). Demo compiles to a Reflex production build.
- Release —
0.0.1(Reflex 0.9) and0.1.0(Reflex 0.10) on PyPI; tag-driven publish workflow.
Credits
- PDFSlick by Vancho Stojkov — the library being wrapped (MIT).
- PDF.js by Mozilla.
- Reflex — the Python web framework.
This wrapper is an independent project and is not affiliated with or endorsed by the PDFSlick authors.
License
MIT © Ernesto Crespo
Metadata
Release files for reflex-pdfslick 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| reflex_pdfslick-0.1.0.tar.gz | 22.6 kB | With provenance | Details for reflex_pdfslick-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| reflex_pdfslick-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for reflex_pdfslick-0.1.0-py3-none-any.whl |
Total release size: 40.1 kB
Release files / reflex_pdfslick-0.1.0.tar.gz
| Download URL | reflex_pdfslick-0.1.0.tar.gz |
|---|---|
| Size | 22.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
87c2d9d625e80955d66845f8aba355e80fd9408df9a3137bf0336b119e53d1cd
|
|
BLAKE2b-256 checksum How to use checksums |
3504132b16a7e88674f90c0d866b70607e8250293216cb852eeb4a48f33e1167
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logRelease files / reflex_pdfslick-0.1.0-py3-none-any.whl
| Download URL | reflex_pdfslick-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
da5d35ad4b467ef60a4edaa0dda87eef5b7cb2e4f3fb70ccb1334df06f5e1e3c
|
|
BLAKE2b-256 checksum How to use checksums |
0aab02b06d9fe44b6a064c660ce27e82ee3b6a86d4340fde7cabba97057573fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logSecurity for reflex-pdfslick 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for reflex_pdfslick-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.