@luminpdf/ds
v0.2.0
Published
Lumin design system registry — shadcn/ui + Tailwind. Use with: npx shadcn add @lumin/<component>
Readme
@luminpdf/ds
Lumin design system, built on shadcn/ui + Tailwind v4.
Distributed two ways:
- As a regular npm package —
import { Button } from "@luminpdf/ds/button". - As a shadcn/ui registry —
npx shadcn add @lumin/buttoncopies the component source into your own codebase.
Most teams use (2) because it matches the shadcn workflow: you own the source, you can tweak it, and upgrades are explicit.
Maintaining or contributing to the registry itself? See
MAINTAINERS.md.
Table of Contents
- 1. Prerequisites
- 2. Install Peer Dependencies
- 3. Wire Up Tailwind + Theme + Tokens
- 4. Use the Shadcn Registry (recommended)
- 5. Use as a Regular npm Package
- 6. Versioning & Upgrades
- 7. Troubleshooting
1. Prerequisites
| Requirement | Version |
|------------|---------|
| Node.js | >=22.15.0 |
| React | >=18 (19 recommended) |
| Tailwind CSS | ^4.0.0 |
| shadcn CLI | >=2.4.0 (for namespace support) |
2. Install Peer Dependencies
@luminpdf/ds declares these as peer dependencies — install them once in your host app:
pnpm add \
@luminpdf/icons \
class-variance-authority \
clsx \
tailwind-merge \
tailwindcss \
tw-animate-css \
shadcn
tw-animate-cssis optional — only needed if you use components that rely onanimate-in/animate-oututilities (dialog, dropdown, popover, etc.).
shadcnis required because@luminpdf/ds/app/theme.css@importsshadcn/tailwind.css— the upstream file that ships the shadcn@custom-variant data-*rules (data-open, data-closed, data-active, data-selected, …), accordion keyframes, and theno-scrollbarutility. We intentionally depend on upstream rather than hand-rolling these variants, so they stay in sync with shadcn releases.
2.1 Framework-specific Tailwind v4 plugin
Tailwind v4 needs a bundler plugin to work. Install the one that matches your host stack:
| Stack | Plugin | Install |
|-------|--------|---------|
| Next.js (App Router, Pages Router) | @tailwindcss/postcss | pnpm add -D @tailwindcss/postcss |
| Vite (any framework) | @tailwindcss/vite | pnpm add -D @tailwindcss/vite |
| Webpack | @tailwindcss/postcss | pnpm add -D @tailwindcss/postcss |
| Rspack / Rsbuild | @tailwindcss/postcss | pnpm add -D @tailwindcss/postcss |
| Create React App (CRA) | @tailwindcss/postcss + CRACO | See shadcn/ui CRA guide |
create-next-app --tailwind and most create-vite templates preinstall the correct plugin already — if you scaffolded with a starter that includes Tailwind v4, skip this step.
Next.js (PostCSS)
postcss.config.mjs:
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};Vite
vite.config.ts:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
});No postcss.config is needed for Vite.
2.2 Install @luminpdf/ds itself
pnpm add @luminpdf/ds3. Wire Up Tailwind + Theme + Tokens
The DS ships its Tailwind theme, design tokens, and @source scan paths as a single CSS file: @luminpdf/ds/app/theme.css.
3.1 Import order (critical)
Your host app's global stylesheet must look like this:
/* app/globals.css (or equivalent) */
/* 1. Your Tailwind instance. Only ONE @import "tailwindcss" in the whole app. */
@import "tailwindcss";
/* 2. Optional animate-in/out utilities used by DS overlays. */
@import "tw-animate-css";
/* 3. Lumin DS theme + tokens + shadcn data-state variants. */
@import "@luminpdf/ds/app/theme.css";
/* 4. Your own app styles… */Do NOT import
@luminpdf/ds/app/globals.css. That file is for the DS's own dev site only — it re-importstailwindcssand will break your cascade.
3.2 How it works
theme.css@importstokens.css(Figma-generated CSS variables) andshadcn/tailwind.css(upstream custom variants + accordion keyframes +no-scrollbarutility). Your bundler resolvesshadcn/tailwind.cssvia theshadcnnpm package'sexports["./tailwind.css"]map — make sureshadcnis installed (see §2).- It declares
@theme inline { ... }so Tailwind v4 exposes the tokens as utilities (bg-primary,text-muted-foreground,rounded-lg, …). - It
@sources the DS's owncomponents/,hooks/,lib/folders so Tailwind generates the utilities used inside the shipped components.
3.3 Dark mode
theme.css registers @custom-variant dark (&:is(.dark *)). Toggle dark mode by adding the dark class to any ancestor (typically <html> or <body>). Works with next-themes out of the box.
4. Use the Shadcn Registry (recommended)
This is the workflow if you want to own the source of each component.
4.1 Register the @lumin namespace
Edit your own components.json (in your host app):
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tailwind": {
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@luminpdf/ds/components",
"utils": "@luminpdf/ds/lib/utils",
"ui": "@luminpdf/ds/components/ui",
"lib": "@luminpdf/ds/lib",
"hooks": "@luminpdf/ds/hooks"
},
"iconLibrary": "phosphor",
"registries": {
"@lumin": "https://unpkg.com/@luminpdf/ds@latest/public/r/{name}.json"
}
}
"rsc": trueis required. Whenrscisfalse(or omitted — the default for some starters), the shadcn CLI strips the"use client"directive from generated files, which breaksdialog,dropdown-menu,select,popover, and every other component that uses React context. See shadcn-ui/ui#8173. This applies to all React frameworks, not just Next.js App Router — the flag is a CLI setting, not a runtime one.
Caching note:
@lateston unpkg can cache for up to 10 minutes. For reproducible CI builds, pin a specific version:"@lumin": "https://unpkg.com/@luminpdf/[email protected]/public/r/{name}.json"Alternative CDN:
https://cdn.jsdelivr.net/npm/@luminpdf/[email protected]/public/r/{name}.json.
4.2 Install components
npx shadcn@latest add @lumin/button
npx shadcn@latest add @lumin/dialog
npx shadcn@latest add @lumin/file-pickerThe shadcn CLI will:
- Download the registry item JSON.
- Copy
components/ui/button.tsx(and friends) into your project, rewriting@luminpdf/ds/...imports to match your aliases. npm installany npm dependencies declared on the item (e.g.radix-ui,class-variance-authority).- Recursively add any
registryDependencies(e.g. addingbuttonalso pullsutilsandspinner).
4.3 Available items
Core UI components
| Item | shadcn add command | Notes |
|------|---------------------|-------|
| utils | shadcn add @lumin/utils | cn() helper — required by every component |
| accordion | shadcn add @lumin/accordion | |
| alert | shadcn add @lumin/alert | |
| alert-dialog | shadcn add @lumin/alert-dialog | |
| aspect-ratio | shadcn add @lumin/aspect-ratio | Radix UI aspect ratio primitive |
| avatar | shadcn add @lumin/avatar | |
| badge | shadcn add @lumin/badge | |
| breadcrumb | shadcn add @lumin/breadcrumb | |
| button | shadcn add @lumin/button | 6 variants × 8 sizes, loading state |
| button-group | shadcn add @lumin/button-group | |
| calendar | shadcn add @lumin/calendar | Requires date-fns, react-day-picker |
| card | shadcn add @lumin/card | |
| carousel | shadcn add @lumin/carousel | Requires embla-carousel-react |
| chart | shadcn add @lumin/chart | Recharts-based with theme support |
| checkbox | shadcn add @lumin/checkbox | |
| collapsible | shadcn add @lumin/collapsible | |
| combobox | shadcn add @lumin/combobox | Requires @base-ui/react |
| command | shadcn add @lumin/command | Requires cmdk |
| context-menu | shadcn add @lumin/context-menu | |
| date-picker | shadcn add @lumin/date-picker | |
| dialog | shadcn add @lumin/dialog | |
| direction | shadcn add @lumin/direction | RTL/LTR text direction provider |
| drawer | shadcn add @lumin/drawer | Vaul-based drawer/sheet |
| dropdown-menu | shadcn add @lumin/dropdown-menu | |
| empty | shadcn add @lumin/empty | Empty state with header, title, description, media |
| field | shadcn add @lumin/field | Form field wrapper with label, description, error |
| file-picker | shadcn add @lumin/file-picker | Composite — pulls many deps |
| form | shadcn add @lumin/form | React Hook Form integration |
| hover-card | shadcn add @lumin/hover-card | |
| input | shadcn add @lumin/input | |
| input-group | shadcn add @lumin/input-group | |
| input-otp | shadcn add @lumin/input-otp | |
| item | shadcn add @lumin/item | Generic item with media, content, actions |
| kbd | shadcn add @lumin/kbd | Keyboard key display |
| label | shadcn add @lumin/label | |
| menubar | shadcn add @lumin/menubar | |
| native-select | shadcn add @lumin/native-select | Native HTML select wrapper |
| navigation-menu | shadcn add @lumin/navigation-menu | |
| pagination | shadcn add @lumin/pagination | |
| popover | shadcn add @lumin/popover | |
| progress | shadcn add @lumin/progress | |
| radio-group | shadcn add @lumin/radio-group | |
| resizable | shadcn add @lumin/resizable | |
| scroll-area | shadcn add @lumin/scroll-area | |
| select | shadcn add @lumin/select | |
| separator | shadcn add @lumin/separator | |
| sheet | shadcn add @lumin/sheet | Dialog-based side sheet |
| sidebar | shadcn add @lumin/sidebar | Collapsible app sidebar with mobile support |
| skeleton | shadcn add @lumin/skeleton | Loading placeholder with animated pulse |
| slider | shadcn add @lumin/slider | |
| sonner | shadcn add @lumin/sonner | Toast notifications |
| spinner | shadcn add @lumin/spinner | |
| switch | shadcn add @lumin/switch | |
| table | shadcn add @lumin/table | |
| tabs | shadcn add @lumin/tabs | |
| textarea | shadcn add @lumin/textarea | |
| toggle | shadcn add @lumin/toggle | |
| toggle-group | shadcn add @lumin/toggle-group | |
| tooltip | shadcn add @lumin/tooltip | |
AI Elements
Components for building conversational AI interfaces, built on Vercel AI SDK types.
| Item | shadcn add command | Notes |
|------|---------------------|-------|
| attachments | shadcn add @lumin/attachments | Grid / inline / list variants with media preview |
| chain-of-thought | shadcn add @lumin/chain-of-thought | Multi-step thought process display |
| confirmation | shadcn add @lumin/confirmation | Tool approval flow (request / accepted / rejected) |
| conversation | shadcn add @lumin/conversation | Scrollable message container with auto-scroll |
| inline-citation | shadcn add @lumin/inline-citation | Citation badges with hover card carousel |
| message | shadcn add @lumin/message | User/assistant message with Streamdown markdown |
| prompt-input | shadcn add @lumin/prompt-input | Full-featured prompt form with file attachments |
| reasoning | shadcn add @lumin/reasoning | Collapsible thinking block with duration tracking |
| shimmer | shadcn add @lumin/shimmer | Animated text shimmer effect |
| sources | shadcn add @lumin/sources | Collapsible source list |
| suggestion | shadcn add @lumin/suggestion | Horizontally scrollable suggestion chips |
agent, artifact, audio-player, canvas, checkpoint, code-block, commit, connection, context, controls, edge, environment-variables, file-tree, image, jsx-preview, mic-selector, model-selector, node, open-in-chat, package-info, panel, persona, plan, queue, sandbox, schema-display, snippet, speech-input, stack-trace, task, terminal, test-results, tool, toolbar, transcription, voice-selector, web-preview
Install any of them with npx shadcn@latest add @lumin/<name>.
Run npx shadcn@latest view @lumin/<name> to preview an item before adding it.
4.4 Using a component
// app/page.tsx
import { Button } from "@luminpdf/ds/components/ui/button";
import { FolderPlus } from "@luminpdf/icons/csr";
export default function Page() {
return (
<Button>
<FolderPlus />
New project
</Button>
);
}5. Use as a Regular npm Package
If you'd rather import components directly instead of copying their source, every component is also exported from @luminpdf/ds:
import { Button } from "@luminpdf/ds/button";
import { Dialog, DialogContent, DialogTitle } from "@luminpdf/ds/dialog";
import { cn } from "@luminpdf/ds/lib/utils";The full list of entry points lives in the "exports" field of this package's package.json.
Trade-offs vs the registry workflow:
| | Registry (shadcn add) | Direct import (@luminpdf/ds/...) |
|--|------------------------|-----------------------------------|
| Own the source | ✅ | ❌ |
| Customize a single component | ✅ trivial | ❌ have to wrap or fork |
| Upgrade story | Re-run shadcn add per component | Bump @luminpdf/ds version once |
| Tree-shaking | N/A (source in your repo) | ✅ (per-subpath exports) |
6. Versioning & Upgrades
@luminpdf/dsfollows semver.- Breaking visual/API changes bump major; additive changes bump minor; token-only / non-visible tweaks bump patch.
- When upgrading via the registry, re-run
npx shadcn@latest add @lumin/<name>for each affected component. The CLI will show a diff before overwriting. - For reproducible builds, pin the
@luminregistry URL to a specific version incomponents.json(see §4.1).
7. Troubleshooting
"Cannot find module @luminpdf/ds/lib/utils" after shadcn add
The shadcn CLI rewrites imports based on the aliases.utils field in your components.json. If you don't have @luminpdf/ds/lib/utils, change the alias or move the generated lib/utils.ts to match.
Styles look wrong / Tailwind utilities missing
Most commonly caused by:
- Missing the
@import "@luminpdf/ds/app/theme.css"line in your global CSS. - Importing
@luminpdf/ds/app/globals.css(wrong file — DS-dev only). - Having two
@import "tailwindcss"statements (second one corrupts the cascade).
@lumin/<name> not found
You haven't registered the namespace in components.json. See §4.1. Verify with:
npx shadcn@latest view @lumin/buttonCDN caching makes CI flaky
Pin to a specific version instead of @latest (see §4.1), or switch to cdn.jsdelivr.net.
Peer-dependency install warnings
These are informational — your package manager is telling you which versions @luminpdf/ds expects. Make sure your installed versions of react, tailwindcss, etc. satisfy the ranges in package.json's peerDependencies.
Can't resolve 'shadcn/tailwind.css'
Build error pointing at node_modules/@luminpdf/ds/app/theme.css:
Error: Can't resolve 'shadcn/tailwind.css' in '…/@luminpdf/ds/app'shadcn/tailwind.css is a real file shipped by the shadcn npm package (exposed via exports["./tailwind.css"]), but it's declared as a peer dependency so it isn't installed automatically. Install it in your host app:
pnpm add shadcnIf you already have shadcn installed and still see the error, your bundler may not honor the style export condition — ensure you're on Tailwind v4 with its framework plugin (@tailwindcss/postcss or @tailwindcss/vite, see §2.1). Tailwind v4 processes CSS @imports before passing to PostCSS, which resolves subpath exports correctly.
@tailwindcss/postcss missing / Tailwind utilities not generated
Tailwind v4 is a peer dep, but you also need its bundler plugin. See §2.1 for the matrix of which plugin to install per framework (Next → @tailwindcss/postcss, Vite → @tailwindcss/vite, etc.).
TypeError: (0 , j.createContext) is not a function (or similar) from a DS component
Symptom: a server-rendered page that uses Dialog, DropdownMenu, Select, Popover, or another Radix-based component blows up at module init, usually during next build page data collection.
Cause: your shadcn-installed file is missing its "use client" directive at line 1. The DS source and registry JSON both ship the directive, but shadcn CLI strips it when rsc: false (the default when the key is missing from components.json).
Fix: add "rsc": true to your components.json (see §4.1), delete the broken files in components/ui/, and re-run npx shadcn@latest add @lumin/<name> for each one. Verify the first line of each generated .tsx is "use client" before rebuilding.
Direct @luminpdf/ds/* import fails to compile in Next.js
Symptom: import { Card } from "@luminpdf/ds/card" (the "regular npm package" workflow from §5) fails with Unknown module type or similar from Turbopack / Webpack.
Cause: Next.js does not transpile .tsx files inside node_modules by default.
Fix: add @luminpdf/ds to transpilePackages in next.config.ts:
const nextConfig: NextConfig = {
transpilePackages: ["@luminpdf/ds"],
};Components installed via shadcn add live in your app tree and are compiled normally — this setting is only needed for the direct-import workflow.
