Copy components with shadcn
Lily publishes its React headless components as a shadcn registry, so npx shadcn add @lily/button copies a component, and any
Lily component it imports, into your project as your own code.
When to copy, and when to install
Copying gives you the source to edit. Installing @lilydesignsystem/react-headless gives you updates. Pick one
per component: a copy is a fork. Lily's own RFC 8 asks whether copying should be the primary path at all, because a copy
does not receive fixes.
Step 1 — Add the Lily registry to components.json
The shadcn CLI (version 3 or later) reads namespaced registries from your
project's components.json. Add Lily under the name @lily; the items refer to each other by that name.
{
"$schema": "https://ui.shadcn.com/schema.json",
"rsc": true,
"tsx": true,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
},
"registries": {
"@lily": "https://lilydesignsystem.com/r/react/{name}.json"
}
} A project that already ran npx shadcn init only needs the registries entry. Lily's components use no Tailwind and no
shadcn utilities; the CLI needs components.json only to know
where your components directory is.
Step 2 — Add components
npx shadcn@latest add @lily/button
npx shadcn@latest add @lily/dialog @lily/scroller Each item is the component's slug, as in the component list. Files land in one directory, components/lily/ (under src/ if your project
has one), so the components' relative imports keep working: @lily/scroller also copies ScrollerBase.tsx,
which it imports, and a component that uses a shared helper module (such
as form-reset.ts) brings that module too. No npm package is
installed; Lily's React components need only React.
import Button from "@/components/lily/Button";
export function SaveBar() {
return <Button onClick={save}>Save</Button>;
} Components that use React hooks start with "use client", so
they work in the Next.js App Router; the rest stay server components.
Each file keeps its header comment (props, keyboard, ARIA), moved below
the directive or the imports, because the shadcn CLI drops comments that
lead a file. Style the components with your own CSS against their class
hooks, or with one of Lily's themes.
The registry
https://lilydesignsystem.com/r/react/registry.jsonlists every item (607 components and the shared helper modules) in shadcn's registry.json schema.https://lilydesignsystem.com/r/react/{slug}.jsonis one registry item, with the file's content inlined. You can also add one by URL, with nocomponents.jsonentry:npx shadcn add https://lilydesignsystem.com/r/react/button.json(an item with a dependency still needs the@lilyentry).https://lilydesignsystem.com/r/{slug}.json, withoutreact/, is a different file: Lily's own per-component metadata (ARIA, keyboard, props, related components). It is not a shadcn item.
The registry is generated from the React headless source by bin/generate-registries, and CI runs npx shadcn add against it into a fresh Next.js app on every
change.
What copying costs
A copied component is your code from the moment it lands. Lily cannot reach it again:
- No updates. An accessibility fix to Lily's Dialog next
year never reaches the Dialog you copied this year. Running
npx shadcn add @lily/dialogagain offers to overwrite your file with the new version, which discards your edits; there is no merge. - You own the tests. Lily's tests and stories stay in Lily. A copy is checked only by the tests you write for it.
- Track what you copied. Watch Lily's CHANGELOG for the components you hold, and diff your copy against the current item when one changes.
If you do not need to edit a component, install the package instead and receive fixes with every upgrade. See the React tutorial.
Svelte and Vue
Only React is published as a shadcn registry so far. shadcn-svelte and shadcn-vue each define their own registry schema, and Lily has not yet tested its items with their CLIs. For Svelte and Vue, install the headless package or copy the component file from the repository.