Skip to main content

A native Reflex component wrapping PDFSlick (PDF.js + Zustand) to view and interact with PDF documents in pure Python.

Project description

reflex-pdfslick

PyPI version Python versions License: MIT GitHub

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 statepage_number, num_pages, scale, rotation, is_document_loaded and 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 NoSSRComponent and 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:

  1. Simple Viewer — basic viewer with prev/next navigation and zoom.
  2. Full Viewer App — toolbar, zoom selector, rotation, scroll/spread modes, document info, search and thumbnails sidebar.
  3. Thumbnails Layout — a grid of page thumbnails as the primary surface.
  4. Horizontal Thumbnails — a horizontal thumbnail strip.
  5. Multiple Documents — several independent viewers on one page.
  6. Load from ArrayBuffer — open a PDF from in-memory bytes.
  7. 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_slick component, 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 command prop + commands helpers).
  • 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

This wrapper is an independent project and is not affiliated with or endorsed by the PDFSlick authors.

License

MIT © Ernesto Crespo

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reflex_pdfslick-0.0.1.tar.gz (21.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reflex_pdfslick-0.0.1-py3-none-any.whl (16.9 kB view details)

Uploaded Python 3

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

Hashes for reflex_pdfslick-0.0.1.tar.gz
Algorithm Hash digest
SHA256 db8871191da59f323b178da4475d51286b5b80f60c82f6b1baf17c34565c9a9d
MD5 468b72307a75530f785f99ccc7660a85
BLAKE2b-256 b03796acd18667a5b42d5c869db7613e8a06a8771bf74d4c1692a11ef406cd61

See more details on using hashes here.

File details

Details for the file reflex_pdfslick-0.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for reflex_pdfslick-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c9a77899e9980d3b17e3fc01122d04df156319372b2dad05adc530c0bf8fc4f3
MD5 3a5587a8b72174725aa932c9bd4a599d
BLAKE2b-256 74e3ed20bf5d2b6ad1ca8ca9dd4e72f84e1a5ab9218937d9fb6f7d933f7b9131

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page