Skip to main content

Examples

Five example apps live in examples/ in the repo, each with its own README, and each runnable live in the Playground.

They also split the styling spectrum between them: no two take the same approach to the package's CSS, so between them both CSS exports get exercised plus the mode that imports neither.

ExamplePackage CSSStyling strategy
report-buildertheme.cssthe theme as it ships, no customization
composed-layouttheme.cssretheme by --jpd-* token only, not one rule rewritten
no-previewtheme.cssdark mode via [data-jpd-theme], with a toggle
headless-designerreset.css onlyappearance written by hand, ~5 rules
custom-uinone192 .jpd-* selectors from scratch

Theming goes through that table with the measured numbers. It is guarded by a test that reads the examples' real import lines, so it cannot drift quietly.

report-builder​

Open in the playground →

The full designer — JSON data sources, a field explorer, 6 ready-made templates, page tabs (Template.pages — each tab its own design, one combined PDF with continuous numbering) — using the package's ready-made UI components (Button/Card/Input/PdfPreviewModal). The fastest path: use the components the library already ships.

It was migrated to the composable parts in 3.0.0 and its layout is identical to the preset's — the point being what the migration bought: a <SelectedFieldBar> in the app's own shell, reading the editor's selection through a hook. That was impossible in 2.x, where <Designer> owned selection and no prop could drive it from outside.

Styling: json-pdf-designer/theme.css, untouched — no token override, no .jpd-* rule. It is the "one import and you're done" end of the spectrum. Its own shell is Tailwind (@tailwindcss/vite), which is the one example where a Tailwind pipeline still exists — on the app side, never the package's.

composed-layout​

Open in the playground →

The editor assembled part by part, without the <Designer> component: toolbar across the top, field list on the left, canvas in the middle, and a right-hand column with five stacked panels that inside <Designer> would be five tabs. It mounts <DesignerProvider> by hand and places the parts in a layout the preset cannot produce.

This is the example that proves <Designer> isn't indivisible — and specifically that the tab gate is opt-in. No part gets whenTab, so all five right-hand panels render at once. Measured: eight parts in the DOM simultaneously, zero .jpd-tabs. Were the gate the default, this layout would show one panel and blank the other four.

Styling: it imports json-pdf-designer/theme.css whole and rethemes it by token alone — its index.css sets --jpd-accent* and --jpd-radius-* values on :root and rewrites not a single .jpd-* rule. Measured after: .jpd-btn[data-variant="primary"] computes violet oklch(0.541 0.245 292.717) and border-radius: 4px, where the default theme gives sky-600 and 8px. It is the cheapest way to look like your own brand — see Theming. Its shell is plain CSS, no Tailwind plugin anywhere. Dev server on port 5177. See Composing the Designer.

custom-ui​

Open in the playground →

The exact same features as report-builder, but the entire shell (header, sidebar, cards, field tree, modals) is hand-written plain CSS — zero Button/Card/Input/icon imported from the package. Proves <Designer> itself works with any design system, or none at all.

It deliberately stays on <Designer> rather than the parts: it's the regression test for the one-line path.

Styling: the far end of the spectrum — it imports no CSS from the package at all, neither theme.css nor reset.css. What is left in the DOM without them is the .jpd-* classes and the data-* state attributes, and its own index.css dresses all of it: 192 distinct .jpd-* selectors, written by hand. That is the price of the whole editor with no package appearance, and it is what makes "import neither" a supported mode rather than a claim.

headless-designer​

Open in the playground →

No <Designer> component at all. A hand-built canvas — its own drag/ resize logic (plain mousedown/mousemove/mouseup, no react-rnd) over generatePdf + types from json-pdf-designer/server, plus PdfPreview from json-pdf-designer/preview. Proves the data model and PDF engine work standalone, independent of the drag/drop editor — the same idea a Node backend relies on (see Server-only usage and Backend integration), just running in the browser here for the demo.

Styling: the only example on json-pdf-designer/reset.css alone — the appearance-free export, which nothing else in the repo exercises. It can afford that because <Designer> never appears here: the only package component it renders is <PdfPreview>, whose styling surface is four names (.jpd-error, .jpd-error--md, .jpd-preview__count, and the token --jpd-shadow-page-preview), so writing its appearance by hand is about five rules. Measured: 54 rules arrive from the package and --jpd-accent computes empty. That last token is the trap — PdfPreview reads it inline, so the reset-only consumer has to declare it or the page shadow vanishes silently. See Theming.

no-preview​

Open in the playground →

<Designer> + generatePdf + downloadPdf, straight to download with no preview screen — and no pdfjs-dist installed. It's the app that proves the main entry never needs the optional peer: the dependency is deliberately absent from its package.json, and its build greps its own bundle for pdf.js symbols (the file:../.. symlink would otherwise let a leaked import resolve pdf.js from the parent repo). No feature is missing here: pdfjs-dist only buys you the on-screen preview.

Styling: it imports theme.css and turns it dark. This is the only app in the repo that exercises dark mode, and it defaults to it: a data-jpd-theme attribute on <html>, toggled from the header and persisted in localStorage. The instructive part is that one attribute drives the editor and the app shell, because the example declares its own --app-* variables under the same key — measured on the toggle, 6 of 6 sampled properties flip together, three on the shell (body background, .app-sidebar, .panel-title) and two inside the editor (.jpd-sidebar, .jpd-ruler__tick). See Theming.

It is also the best smoke test for the no-Tailwind mode: zero Tailwind pipeline at either end, so the package's own reset has to carry its weight unaided. report-builder has a pipeline of its own, so it emits Preflight on its own account and would not reproduce a missing reset — testing there first gives you a false pass. (For the mode with no package stylesheet at all, the app is custom-ui.)

Its README spells out why the dependency is deliberately absent and why omitting it isn't enough on its own; Architecture covers all three checks that guard the boundary.

Running locally​

None of the five are npm workspaces — each points at the package via "json-pdf-designer": "file:../.." in its own package.json. From the repo root:

npm run build # build the root package once, so dist/ exists
cd examples/report-builder # or composed-layout, custom-ui, headless-designer, no-preview
npm install
npm run dev

The dev servers pin one port each, so you can run several at once: custom-ui 5174, headless-designer 5175, no-preview 5176, composed-layout 5177 — report-builder pins none and takes Vite's default, 5173.