CLI tool
akii09/pdfx avatar
akii09/pdfx

pdfx: copy-in React PDF components instead of a dependency

A React PDF component library with pre-built, themeable components and a CLI for seamless PDF document creation.

1,181 stars46 forksTypeScriptMIT

At a glance

What is it?
PDFx packages pre-built, themeable PDF components for React as source you copy into your own repository, delivered through a CLI and a shadcn-style registry. The trade-off is that there is no runtime dependency on PDFx and no upgrade path, only a copy.
Who is it for?
Adopt PDFx if your team already writes React and wants styled PDF output without hand-building every page, and if owning the copied source is acceptable to you. Do not adopt it if you need upstream fixes to flow in automatically or a single package to bump.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 31 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The dependency you delete after the first commit

Most component libraries arrive as an npm package and stay that way forever, so every upstream release reaches your bundle and every bug fix is a version bump. PDFx inverts that. Its README states the position plainly: pre-built PDF components for React that you copy into your project and own completely, built on @react-pdf/renderer, with no runtime dependency on PDFx. That sentence is the whole design, and it explains most of what follows.

The project is a TypeScript component library for producing PDF documents in React applications, and it is aimed at developers who already ship React and need a second rendering target. A dashboard that has to emit an invoice, a report, or a contract is the typical case. The components are described as themeable, and the visual language is meant to stay consistent across the document rather than being re-specified per screen.

The cost of the copy-in model is equally concrete. There is no package to upgrade, so upstream changes reach you only when you choose to pull new source, and the diff lands as ordinary application code in your repository. Teams that measure dependency hygiene will read that as good. Teams that expect a vendor to own upstream defects will read it as a liability. The library is MIT licensed, and the README shows the notice as MIT © Akii, so the copied files carry the permissive terms with them, subject to keeping the notice.

Two ways in: the CLI and the shadcn registry

Installation comes in two flavours, and the choice depends on whether your project already uses shadcn/ui.

The direct route is the CLI, which is a separate published package named pdfx-cli:

bash
npx pdfx-cli init
npx pdfx-cli add heading text badge

The first command sets the project up, the second copies named components into your source tree. No configuration file, no token, and no build step appears in the README, which suggests the CLI works on an existing project directory and writes files into it.

If your project is already a shadcn/ui project, the same components are delivered through the shadcn registry instead, namespaced so that they never collide with the heading or table components that shadcn/ui already installs. That path needs an existing components.json, and the README tells you to create one with npx shadcn@latest init before continuing. The registry add command takes a template URL, and the add command takes namespaced component names:

bash
npx shadcn@latest registry add @pdfx=https://getpdfx.dev/r/shadcn/{name}.json
npx shadcn@latest add @pdfx/heading @pdfx/text @pdfx/badge

The {name} placeholder is meant to stay as written in the URL template, with the component name substituted per component. The practical difference between the two paths is who resolves the dependency: with the CLI, PDFx writes into your tree; with the registry, shadcn's own tooling does, and PDFx only serves the JSON.

Using the components in a document

Once the source is in your project, the components are ordinary React code, and the usage example in the README is short enough to read in one pass. It imports the document and page primitives from @react-pdf/renderer and the visual pieces from the local components directory:

tsx
import { Document, Page } from '@react-pdf/renderer';
import { Heading, Text, Badge } from './components/pdfx';

export default () => (
  <Document>
    <Page>
      <Heading level={1}>Invoice #1042</Heading>
      <Badge label="Paid" variant="success" />
      <Text>Thank you for your business.</Text>
    </Page>
  </Document>
);

Three details are worth noting. The import path is relative,./components/pdfx, which confirms the copy-in model rather than a package import. The primitives come from @react-pdf/renderer, so that library is a real dependency of your app even though PDFx itself is not. And the components take ordinary props: a level on the heading, a label and a variant on the badge, matching what you would expect from a component library.

The site at getpdfx.dev hosts the full documentation, component previews, and block templates, and it is where you would go to see what a component looks like before copying it. The README also points at a user-facing release notes page there, which is worth bookmarking because, with a copy-in library, release notes are how you find out that new source exists.

A monorepo with a registry build and a turbo pipeline

The repository layout explains how the two delivery mechanisms fit together. The root package.json is a private workspace named pdfx, with a preinstall hook that forces pnpm via npx only-allow pnpm, and it pins [email protected] with Node 20 or newer and pnpm 10 or newer as its engine requirements. Turbo drives the tasks, and the root scripts are thin wrappers around it: turbo dev, turbo build, turbo test, turbo lint, and turbo run typecheck. There is a combined check script that runs lint, typecheck, build, and test in sequence, which is the sort of gate you want before a release in a registry that pushes source to other people's repositories.

The workspace contains apps/, packages/, and configs/. The apps/www entry matters most because the .env.example names apps/www/public/r as the default registry output directory, so the www application is what serves the registry JSON that the shadcn registry add command points at. packages/ holds the CLI, since the README links the changelog at packages/cli/CHANGELOG.md. Building the registry is a separate task, build:registry, meaning the published registry payloads are generated artifacts rather than hand-written files.

Tooling choices are modern and opinionated. Biome handles formatting and linting, the format:check script runs biome check, and Changesets drives versioning with changeset version and changeset publish, alongside a release script that builds before publishing.

The sync script and its two environment variables

The local development path has a piece that will not be obvious from the README alone. The root scripts include sync:project, which runs node scripts/sync-to-project.mjs, and the committed .env.example documents two variables for it. One is PDFX_TARGET_PROJECT, the absolute or relative path to the project you want the built registry components synced into, with a CLI argument taking priority over the value in the file. The other is PDFX_REGISTRY_DIR, the path to the built registry directory, defaulting to apps/www/public/r and only needing to change if you have moved the registry output location.

bash
PDFX_TARGET_PROJECT=../react-test
PDFX_REGISTRY_DIR=apps/www/public/r

The file also states the local convention: copy .env.example to .env and fill in your own paths, with .env git-ignored and .env.example committed as the canonical template. The example values, a relative path and a default registry path, show that the intended workflow is to develop components in the monorepo, build the registry, and push the result into a throwaway project so you can see the real installed output rather than the source in isolation.

That workflow is the closest thing in the repository to a test harness for a copy-in library, and it is aimed at contributors rather than users. If you are only consuming the components, none of this reaches your application.

Versioning, release cadence, and what a bump means to you

The published surface of the project is the CLI. The recent release list shows pdfx-cli at 0.8.0, with 0.7.2 and 0.7.1 published the same day, 2026-09-22, and the last push to the repository carries that same date. Version numbers below 1.0 mean the command line interface is still expected to change shape, so scripts that wrap npx pdfx-cli should pin the version rather than track the latest tag.

The three release channels are worth separating. User-facing release notes live at getpdfx.dev/releases, the CLI changelog is packages/cli/CHANGELOG.md, and GitHub Releases are on the repository page. Because the components themselves are copied rather than installed, the release notes are the mechanism that tells a consumer that new component source exists. Nothing in the repository describes a way to diff a component against a newer published version and reapply it, so updating means comparing the registry payload for that component against your copy.

The project is not archived, and the CLI ships releases rather than living only in the repository. The README also marks itself beta, so treat the component set as a moving target. The contribution path is documented in CONTRIBUTING.md, and the repository ships standard governance files including SECURITY.md, CODE_OF_CONDUCT.md, and AGENTS.md, plus a LICENSE file holding the MIT terms.

Where the copy-in model works, and where it hurts

The honest limitation is the one the README advertises. If PDFx fixes a rendering bug in the badge component, nothing happens to your project until a human copies the new source. There is no version constraint to update and no npm resolution to rely on, so the cost of staying current is a recurring manual diff. For a stable component set, that is cheap. For a set you depend on heavily across many documents, it is an operational tax that grows with the number of components you take.

There is a second limitation that follows from the same design. Because the components are styled for a document rather than for a browser, and because the primitives come from @react-pdf/renderer rather than from React DOM, the components cannot be dropped into an existing web UI. The Heading and Text you copy are PDF nodes. The README does not document a theming API beyond the variant and prop style shown in the example, so customising a palette means editing the copied files.

The alternative approach, and the one most teams reach for first, is to write the same document primitives directly against @react-pdf/renderer. The difference is where the effort goes. Hand-writing means a small amount of code per document and full control, but every invoice, report, and letter repeats the same layout decisions and the same styling mistakes. PDFx moves that work into a maintained set of components, at the price of owning the copy. The other alternative, importing a PDF component package as a dependency, keeps upstream fixes but reintroduces the version coupling PDFx exists to avoid, and the README positions the library against exactly that model.

Editorial conclusion

Adopt PDFx if your team already writes React and wants styled PDF output without hand-building every page, and if owning the copied source is acceptable to you. Do not adopt it if you need upstream fixes to flow in automatically or a single package to bump. Before starting, run npx pdfx-cli init in a branch and read the copied files, since the copy is the product.

Frequently asked questions

How do I install PDFx components into a React project?

Run npx pdfx-cli init to set the project up, then copy components with npx pdfx-cli add followed by the component names, for example npx pdfx-cli add heading text badge. The components are written into your own source tree.

Does PDFx work with shadcn/ui projects?

Yes, through the shadcn registry rather than the CLI. The README says the same components are namespaced so they never collide with shadcn/ui components, and it requires an existing components.json, created with npx shadcn@latest init, before running the registry add and add commands.

Is PDFx a runtime dependency of my application?

No. The README states there is no runtime dependency on PDFx, because the components are copied into your project and owned by you. Your application still depends on @react-pdf/renderer for the Document and Page primitives.

What license is PDFx released under?

The repository LICENSE file and the README both indicate MIT, with the notice shown as MIT © Akii. Because the components are copied into your project, the terms travel with the copied source.

How do I pick up new PDFx component updates?

There is no package to bump, since the components are copied into your own repository. New versions are announced through the release notes at getpdfx.dev/releases and the CLI changelog at packages/cli/CHANGELOG.md, and applying them means comparing the new registry source against your copy.

Official sources

  1. akii09/pdfx on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/akii09-pdfx.svg)](https://hysenlabs.com/projects/akii09-pdfx)

Was this useful?