Introduction

The Poimandres design-system — the shared pmndrs colour layer (Material Design 3) and the poimandres shadcn preset.

figmalight figmadark

Summary

A distributed design-system: nothing to install from npm. It ships as a shadcn preset and a shadcn registry, so every pmndrs app — and yours — gets the same colours, fonts and radii by copying code in, not by depending on a package.

A classroom whiteboard with the word "bleu" written in red and "jaune" in green

What you get

  • shadcn's tokens as the base, Material Design 3 colour roles on top
  • The pmndrs palette baked into the CSS: no provider, no client JavaScript
  • Light and dark schemes, also exported as Figma tokens
  • A git tag as the install address, so every app pins the version it was built against

Quick start

Any app shadcn can init works. With a fresh Next.js one:

Create the app

npx create-next-app@latest my-app --ts --tailwind --app --src-dir --yes
cd my-app

Init shadcn with the poimandres preset

pnpm dlx shadcn@latest init --preset b1VlIttI

Add a pmndrs block

Blocks depend on the colour layer, so adding one pulls it in with it:

pnpm dlx shadcn@latest add pmndrs/docs/keypoints#v4.22.0

Use it

src/app/page.tsx
import { Keypoints, KeypointsItem } from "@/components/keypoints"

export default function Home() {
  return (
    <main className="bg-background text-foreground min-h-screen p-10">
      <Keypoints title="Hello">
        <KeypointsItem>The panel sits on bg-surface-dim, an MD3 role shadcn has none for</KeypointsItem>
      </Keypoints>
    </main>
  )
}

Add the dark class to <html> for the dark scheme.

Tip

No block in mind? Add the theme on its own — the palette and the mono font:

pnpm dlx shadcn@latest add pmndrs/design-system/theme#v0.9.0

Tokens

Four layers set the tokens, each one deciding what the layer below leaves open:

shadcn's tokens are the base — bg-background, text-primary, border-border… keep working as usual.

Material Design 3's colour roles are additive: every --md-sys-color-* role becomes a Tailwind colour, for what shadcn has no name for (bg-surface-dim, text-on-surface-variant, bg-tertiary-container…).

Registry items

The design-system is distributed: every pmndrs/* repo can contribute blocks, each one installable with a single shadcn add. Any public repo becomes a shadcn registry with a registry.json at its root, and the CLI installs straight from GitHub — no registry server, no package to publish, just files. A block ships from the repo that already owns it, pmndrs/docs' Keypoints for one, so there is nothing to move. This repo holds the theme only: the shared defaults — colours, radius, every shadcn token — that blocks build on, and that your app can still override.

Not only UI, either: an item is a set of files, their dependencies and a target path, and the CLI never looks inside. A shader, a drei helper or a SKILL.md ships the same way.

An item's address is the registry, the item and a git ref:

pnpm dlx shadcn@latest add pmndrs/design-system/theme#v0.9.0

The CLI reads registry.json and the item's files at that ref — a tag, a branch or a commit SHA. Pin a tag: an address without one follows the repo's default branch, and changes under you. npx shadcn@latest view <address> prints an item, files and all, before you add it.

Namespace

This site also serves every item of this repo as static JSON, under https://pmndrs.github.io/design-system/r/. Declare it once as a namespace in components.json:

components.json
{
  "registries": {
    "@pmndrs": "https://pmndrs.github.io/design-system/r/{name}.json"
  }
}

Then an item is a name, no repo and no ref:

pnpm dlx shadcn@latest add @pmndrs/theme
pnpm dlx shadcn@latest search @pmndrs

The namespace serves the latest state of main, rebuilt from registry.json on every deploy of it. Pin the git address above, at a tag, where an app must not move.

A branch's preview deployment serves its own r/ too, but not all the way down: preset depends on pmndrs/design-system/theme#v0.9.0, so the theme it installs is the released tag's, not the branch's.

The shadcn MCP server reads the same namespace, so Claude, Cursor or VS Code can list, read and install the items from the conversation:

pnpm dlx shadcn@latest mcp init --client claude

A fresh project can start from it too: preset is the poimandres preset and the theme in one item, so init with its URL does what the Quick start does, without the preset code, and declares the namespace in components.json on the way:

pnpm dlx shadcn@latest init https://pmndrs.github.io/design-system/r/preset.json

To prototype in v0 with the pmndrs colours, fonts and radius already applied, open the theme in v0. Or have v0 build the brand guidelines from it, as slides: open the brand guidelines in v0. v0 reads no namespace, so both open the v0 item: a v0 project with a starter page, the logo in public/pmndrs/, the brand book as guidelines/Guidelines.md, and a globals.css with the colours resolved, light and dark. Its utilities are the ones a pmndrs project has, shadcn's and the Material Design 3 roles' (bg-primary-container, bg-surface-container-high, the brand lime as bg-lime-container), so what v0 writes runs unchanged in one.

Publish a block

A block has one source of truth, and it moves. It starts in the repo it was born in, used there first — the proof it works, not a component cut off from a real app. It is promoted to pmndrs/design-system only once a second repo reuses it, and promoting it is just moving its files. Any earlier is premature optimisation.

So publish it from your own repo: a registry.json at its root, one entry per item in the registry-item.json shape.

registry.json
{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "pmndrs-<repo>",
  "homepage": "https://github.com/pmndrs/<repo>",
  "items": [
    {
      "name": "<block>",
      "type": "registry:block",
      "registryDependencies": [
        "sidebar",
        "collapsible",
        "pmndrs/design-system/theme#v0.9.0"
      ],
      "files": [{ "path": "registry/<block>/<block>.tsx", "type": "registry:block" }]
    }
  ]
}

sidebar and collapsible are stock shadcn components. pmndrs/design-system/theme is the pmndrs theme, for a block that paints with an MD3 role; a ref is not inherited by dependencies, so it pins its own. npx shadcn@latest registry validate checks the file, and npx shadcn@latest add pmndrs/<repo>/<block>#<ref> installs the block anywhere. To list it below, add the repo to registry/external.json and run npm run refresh-catalog.

Catalog

Here they all are, across repos, each linked to its source, then the same list from shadcn search:

The CLI lists the same items, read live from each repo at the ref to pin:

pnpm dlx shadcn@latest search pmndrs/design-system#v0.9.0 pmndrs/docs#v4.22.0