Try it:
cd /tmp && rm -rf pmndrs-foo && \
npx -y create-next-app@latest pmndrs-foo --ts --tailwind --app --eslint --src-dir --import-alias "@/*" --no-turbopack --use-npm --yes && \
cd pmndrs-foo && \
npx -y shadcn@latest init --preset b1VlIttI --yes && \
npx -y shadcn@latest add pmndrs/docs/keypoints#v4.22.0 --yes && \
printf '%s' 'import { Keypoints, KeypointsItem } from "@/components/keypoints"
export default function Home() {
return (
<main className="bg-background text-foreground min-h-screen p-10">
<h1 className="text-2xl font-bold">pmndrs design system</h1>
<Keypoints title="What this proves">
<KeypointsItem>One add pulled the block and the colour layer with it</KeypointsItem>
<KeypointsItem>The panel sits on bg-surface-dim, an MD3 role shadcn has none for</KeypointsItem>
<KeypointsItem>Nothing mounted: the palette is baked into the CSS</KeypointsItem>
</Keypoints>
</main>
)
}' > src/app/page.tsx && \
npx next devAdd dark to <html> for the dark scheme.
shadcn's are the base; MD3's --md-*
roles are additive
Every item, this repo's and the other pmndrs repos', linked to its source: the registry catalog.
The docs site also serves this repo's items as static JSON, rebuilt from registry.json
on every deploy of main (npm run hosted-registry writes the same files into public/r/).
Declare the namespace in components.json:
{
"registries": {
"@pmndrs": "https://pmndrs.github.io/design-system/r/{name}.json"
}
}then npx shadcn@latest add @pmndrs/theme. It serves the latest state of main; the git
address at a tag (pmndrs/design-system/theme#v0.9.0) stays the pinned one. A branch's
preview deployment serves its own r/, but preset's dependency on theme is that tagged
git address, so it resolves to the released tag, not to the branch. The
shadcn MCP server (npx shadcn@latest mcp init --client claude)
reads the same namespace to list and install the items.
To start a fresh project from it, init with preset: the poimandres preset and the theme in
one item, no preset code, and the namespace declared on the way.
npx shadcn@latest init https://pmndrs.github.io/design-system/r/preset.jsonTo prototype in v0 with the pmndrs colours, fonts and radius already applied:
Open in v0.
Or have v0 build the brand guidelines from it, as slides:
brand guidelines in v0.
Both open the v0 item: a starter page, the logo, guidelines/Guidelines.md and a
globals.css with the colours resolved, under the utility names a pmndrs project has
(bg-primary-container, bg-lime-container), so what v0 writes runs unchanged in one.
For a palette other than the pmndrs one: install md3-base — same plumbing, no
baked palette — and follow its docs.
npx shadcn@latest add pmndrs/design-system/md3-base#v0.9.0Nothing renders until something emits --md-sys-color-*: regenerate the values
from your seed with Mtb.
Either side works.
// RSC — `builder` is the root export and carries no 'use client'
const { source, ...rest } = pmndrsMtb;
<style dangerouslySetInnerHTML={{ __html: builder(source, rest).toCss() }} />;
// client — same output, from `material-theme-builder/react`
<Mtb {...pmndrsMtb}>{children}</Mtb>;Prefer the server one where there is a server: it keeps the palette code off the
client. <Mtb> is for where there is no build to hook — a Storybook preview,
say.
Always pin a ref — pmndrs/design-system/theme#v0.9.0. Refs are not
inherited: every entry in registryDependencies carries its own.
- a shadcn primitive →
registryDependencies: ["button"] - shared across pmndrs blocks → its own item, by full pinned address
(
pmndrs/docs/mdx-prose#v1.0.0) — a bare name never means a same-repo item - meaningful only here → another entry in the same item's
files - trivial and app-specific → inline it
A block never hardcodes a font family (the font-sans / font-mono utilities):
it inherits the consumer's. Icons come from
components.json's iconLibrary; lucide is the pmndrs baseline.
The design system is published to Claude as the
Poimandres Design System artifact,
so that the designs Claude generates stay on brand: its tokens, its brand book
(README.md), its fonts, and a card per pmndrs block of the registry catalog.
The artifact draws the foundations, colours to shadows, from the tokens itself.
Every file of it comes from this repo. npm run artifact writes them into
out/artifact/project/ (gitignored), laid out as the artifact's own
project/:
tokens.json, from the theme palette, the shadcn remap and the docs tables, with the usage lines and provenance ofscripts/artifact.notes.json;README.md,assets/Logos/README.mdandcomponents/<Block>/*, copied fromartifact/with their{{placeholders}}filled: every version and sha in them comes from git,package.json,node_modulesorregistry/external.json.README.mdis the brand book, whichnpm run buildalso turns into the docs site's Guidelines page and theguidelinesitem'sGuidelines.md: passages that only make sense in the artifact sit between<!-- artifact-only -->and<!-- /artifact-only -->, and stay out of those two;fonts/*, the latin subsets of Inter and Inconsolata.
components/ mirrors the catalog's blocks (registry:block items of
registry.json and registry/external.json): each has a hand-written
preview.html, a static rendition styled with the artifact's token variables,
and a README.md. A block listed without its card fails the run, and so does
a card whose block left the catalog.
The artifact generates the rest itself (tokens.css, manifest.json, api/),
and its index, design-system.json, which names the logo uploads, is edited
in place.
A maintainer publishes after each release, from Claude Code, signed in to claude.ai, which is why CI cannot do it:
- Check out
mainat the release and runnpm run artifact. - Diff
out/artifact/project/against the artifact's files, and publish only the files that changed to https://claude.ai/artifact/FF46zANDT1mt2kdF9D9QAf with Claude Code's Artifact tool. - Last, update
design-system.json'slastChange(by,at,vianaming the commit,note).
Nothing of it is committed but its sources: scripts/artifact.mjs,
scripts/artifact.notes.json and artifact/.
npm install
npm run build # regenerate registry.json, figma/*.tokens.json, the docs catalog
npm run artifact # generate the Claude Design artifact files into out/artifact/
npm run lgtm # outputs are current and valid, preset code round-trips
npm run refresh-catalog # re-fetch the other repos' items listed in the docs catalogDocs live in docs/ (pmndrs/docs), deployed to
GitHub Pages on push to main, and to Vercel as well (a preview per pull request,
which the sidebar's version switcher links to). Preview them on http://localhost:3000:
curl -sL https://raw.githubusercontent.com/pmndrs/docs/refs/heads/main/preview.sh | MDX=docs NEXT_PUBLIC_LIBNAME=design-system sh