A native Reflex component wrapping PDFSlick (PDF.js + Zustand) to view and interact with PDF documents in pure Python.
Project description
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/ implemented, pre-release. 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. The remaining step is the PyPI/GitHub release. See Roadmap for details.
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 standard Reflex custom
component: the package
lives in custom_components/reflex_pdfslick/ (shipping the PdfSlickWrapper.tsx
wrapper and a generated pdfslick.pyi stub), the demo app in pdfslick_demo/,
and pyproject.toml carries the reflex-custom-components keyword used by the
Reflex component gallery to discover it on PyPI.
# Build the wheel + sdist into dist/ (also regenerates the .pyi type stubs).
# PYTHONPATH=. lets the stub generator import custom_components/.
PYTHONPATH=. uv run reflex component build
# Inspect, then publish to PyPI (bump the version in pyproject.toml first):
uv run twine upload dist/* # or: uv publish
# Share on the Reflex community gallery:
uv run reflex component share
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 / Next.js (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: 35 unit tests, GitHub Actions (pytest 3.10–3.13, TSX transpile, demo build smoke). Demo compiles to a Reflex production build.
- Release — PyPI publish + tagged GitHub release (awaiting maintainer credentials).
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reflex_pdfslick-0.0.1.tar.gz.
File metadata
- Download URL: reflex_pdfslick-0.0.1.tar.gz
- Upload date:
- Size: 21.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db8871191da59f323b178da4475d51286b5b80f60c82f6b1baf17c34565c9a9d
|
|
| MD5 |
468b72307a75530f785f99ccc7660a85
|
|
| BLAKE2b-256 |
b03796acd18667a5b42d5c869db7613e8a06a8771bf74d4c1692a11ef406cd61
|
File details
Details for the file reflex_pdfslick-0.0.1-py3-none-any.whl.
File metadata
- Download URL: reflex_pdfslick-0.0.1-py3-none-any.whl
- Upload date:
- Size: 16.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c9a77899e9980d3b17e3fc01122d04df156319372b2dad05adc530c0bf8fc4f3
|
|
| MD5 |
3a5587a8b72174725aa932c9bd4a599d
|
|
| BLAKE2b-256 |
74e3ed20bf5d2b6ad1ca8ca9dd4e72f84e1a5ab9218937d9fb6f7d933f7b9131
|