Nx-based monorepo for reusable design system libraries and project scaffolding.
@elirobinson/tokens— CSS and JSON design tokens.@elirobinson/react— Accessible React components bound to Miltinson tokens.@elirobinson/ai-patterns— AI UX patterns, prompt contracts, guardrails, and thedsdiscovery CLI.@elirobinson/eslint-config— the statically checkable contracts as a flat ESLint config.create-elirobinson-design-system—npxstarter generator.
pnpm install
pnpm buildnpx nx run storybook:storybookImport components and styles in your app shell:
import '@elirobinson/tokens/tokens.css';
import '@elirobinson/react/styles.css';
import { Button } from '@elirobinson/react/components/atoms/Button';
import { Card, CardHeader, CardTitle } from '@elirobinson/react/components/molecules/Card';There is no inventory table here on purpose. This README used to carry one; by the time
@elirobinson/react reached 1.1.0 it listed 25 of 45 components under a heading claiming
0.2.0 had 17. Every such list rots on the next release, and a consumer who copied it has no
way to know.
Ask the installed package instead — @elirobinson/ai-patterns ships the elirobinson-ds
bin for exactly this:
pnpm add -D @elirobinson/ai-patterns@latest # then: "scripts": { "ds": "elirobinson-ds" }
pnpm ds # components with exports and variants, hooks, typography, token groups
pnpm ds props Card # props, variant unions, and the exact import line to copy
pnpm ds tokens accent # tokens and their values
pnpm ds contracts # the rules your UI must satisfy, and what verifies each
pnpm ds patterns # working principles and the definition of done for UI work
pnpm ds init --agents # install the Claude Code / Cursor / Copilot / AGENTS.md instructionsIt reads node_modules at run time and walks the package tree rather than assuming a
layout, so it stays correct across releases — including the 0.x flat layout and the 1.x
atoms/molecules/organisms one. Machine-readable equivalent:
@elirobinson/react/manifest.
Layout patterns (Header, Footer, Hero, Sidebar, TopBar) are documented in Storybook under Patterns/Marketing and prototyped in design-system-docs/ui_kits/ — compose them from primitives rather than importing fixed layout components.
Starting a new project? npx github:EliRobinson/design-system/packages/create-elirobinson-design-system my-app scaffolds most of this for you — skip to step 7.
Adding the system to an existing app takes about ten minutes. Nothing below has to be revisited when this repo changes: bump a version and you are current.
Packages live in GitHub Packages, so the app needs a registry line and a token with read:packages — see Install packages in a consumer app for the auth details, including the pnpm 10 gotcha.
pnpm add @elirobinson/react@latest @elirobinson/tokens@latest
pnpm add -D @elirobinson/ai-patterns@latest @elirobinson/eslint-config@latestreact and tokens ship code the app renders; the other two are tooling.
This is the single most useful line in the list. pnpm ds reads node_modules at run time, so it always describes the versions actually installed — which is why no doc here lists components.
Three ways to reach it, in order of how often you'll want them:
pnpm ds # with the script above
pnpm exec elirobinson-ds # installed, no script
pnpm --package=@elirobinson/ai-patterns dlx elirobinson-ds # not installed at allAll three describe the project you run them in — even the dlx form, which reads the local @elirobinson/react and @elirobinson/tokens and falls back to its own copy for contracts, patterns and prompts. Handy for inspecting a repo you haven't set up yet.
// app/layout.tsx — order matters
import '@elirobinson/tokens/tokens.css';
import '@elirobinson/react/styles.css';Never re-import per component.
/* app/globals.css */
@import 'tailwindcss';
@import '@elirobinson/tokens/tailwind.css';This is what makes bg-background, text-muted-foreground, border-border, rounded-md and friends resolve to design system tokens. Without it they resolve to Tailwind's defaults, or to nothing at all. Don't hand-roll the mapping — the shadcn/ui variable contract collides with these token names, and the obvious --accent: var(--accent) alias is circular.
Not using Tailwind? Skip this. var(--token) and the .t-* typography classes work anywhere.
next/font never exposes a family under its real name — it generates a hashed one (__Geist_e8ce0c) and hands it over in a CSS variable. The literal 'Geist' in tokens.css matches nothing it loaded, so without this the page silently renders in the system font. One line per family:
/* app/globals.css — unlayered, see below */
:root {
--ds-font-sans-override: var(--font-geist-sans);
--ds-font-mono-override: var(--font-geist-mono);
}Load the font with variable: set and put the class on <html>, not <body> — the tokens resolve at :root, so that is where the framework's variable has to be visible.
Overriding any other token: tokens.css is unlayered, by design. An unlayered declaration beats anything inside a cascade layer no matter the order, so an override written inside @layer base — the conventional place in a Next.js globals.css — will not apply. Put it in a plain :root block outside any layer. The --ds-font-*-override hooks above are exempt: nothing in tokens.css declares them, so they win from anywhere.
// eslint.config.mjs
import designSystem from '@elirobinson/eslint-config';
export default [
// …your existing config
...designSystem(),
];Catches bare @elirobinson/* imports (which never resolve), foreign component libraries, direct Radix imports, and hardcoded colours, radii, shadows and durations. Add @elirobinson/eslint-config/css for the same checks in stylesheets, plus the one constraint only a stylesheet can settle: a control that underlines its own label on a filled surface reads as a hyperlink wearing a button. pnpm ds contracts prints the current list either way.
It also warns on padded functional UI copy — the frequency claims, blame, filler pacing, unprompted reassurance and enthusiasm that pnpm ds patterns bans under UI Copy Is Chrome. That one is a warning rather than an error on purpose: an existing app has copy written before the rule, and it should not fail to build the day it upgrades. Its scope is copy props and chrome components only, never your editorial content. Once the existing copy is clean:
...designSystem({ copy: { severity: 'error' } });pnpm ds init --agentsWrites a Claude Code skill, a Cursor rule, Copilot instructions, and an AGENTS.md block — so whichever tool a teammate drives reaches for the system first. None of them contains an inventory; they all point at ds. Re-running is safe: existing files are left alone unless you pass --force, and the AGENTS.md block updates in place between its markers.
pnpm exec ds-resync artifacts --writeWrites three skills into .claude/skills/: the brand skill (voice, colour, type, assets, UI kits), a version-stamped component reference (llms.txt / llms-full.txt, generated at publish time from the component manifest), and the ds-resync instructions. Read-only without --write.
Re-run it after every upgrade. It refreshes the files it wrote, leaves anything you have edited alone and names them in the output, and warns loudly when the snapshot's @elirobinson/react version is not the one you have installed — a snapshot that quietly describes a different release is how an agent ends up with confidently wrong prop tables.
// e2e/design-system.spec.ts
import { test } from '@playwright/test';
import { expectDesignSystemContracts } from '@elirobinson/ai-patterns/testing/playwright';
test('home page meets the design system contracts', async ({ page }) => {
await page.goto('/');
await expectDesignSystemContracts(page);
});Covers 44×44 touch targets, visible focus, non-overlapping hit areas, and WCAG AA contrast. Needs axe-core alongside your Playwright install.
A plain .ts spec is compiled to CommonJS by Playwright, so this import resolves through require, which needs Node 22.12 or newer. Below that, name the file .spec.mts — Playwright then treats it as ESM and the same import line works unchanged.
<ThemeProvider attribute="data-theme" defaultTheme="system" enableSystem>next-themes defaults to a class strategy. A .dark compatibility selector ships too, so either works — but data-theme is what every component's CSS is written against.
pnpm exec ds-resync # what's out of date, and what changed while you were away
pnpm exec ds-resync --write # apply it
pnpm exec ds-resync artifacts --write # then bring the agent skills alongIf the package isn't installed — a repo scaffolded before any of this existed, or a one-off check — run it straight from the registry instead:
pnpm --package=@elirobinson/ai-patterns dlx ds-resync
pnpm --package=@elirobinson/ai-patterns dlx ds-resync --writeds-resync ships in the same package and answers the other question: ds describes the version you have, ds-resync tells you whether you should have a newer one.
See docs/agents/consumer-tooling.md for the reasoning behind each piece.
| Package | What it gives a consumer |
|---|---|
@elirobinson/react |
Components, hooks, and ./manifest — the machine-readable inventory |
@elirobinson/tokens |
tokens.css, plus tailwind.css mapping Tailwind v4's theme namespaces onto the tokens |
@elirobinson/ai-patterns |
ds and ds-resync, contracts, prompt templates, agent instruction templates, Playwright contract helpers |
@elirobinson/eslint-config |
The statically checkable contracts as a flat config, including no-hardcoded-design-values and no-padded-ui-copy |
pnpm ds prompts adopt-system prints a fill-in-the-blanks brief for handing the migration to an agent: it covers this setup, the order to work in, and a verification checklist.
pnpm changeset
pnpm changeset:versionChangesets versions packages and publishes them to GitHub Packages on merge of the release PR.
Semantic-release runs in CI on main and will:
- create/update
CHANGELOG.md, - create git tags in the form
vX.Y.Z, - and publish GitHub release notes from conventional commits.
Run pnpm ds --version in a consuming app for the versions it actually has installed; a version number written down here is stale the moment the next release PR merges.
Library packages (@elirobinson/tokens, @elirobinson/react, @elirobinson/ai-patterns, @elirobinson/eslint-config) are published to the GitHub Packages npm registry.
- Push to
mainwith a changeset to open/update the release PR. - Merge the release PR to bump versions, publish packages, and generate GitHub releases.
The starter generator is still installed directly from GitHub:
npx github:EliRobinson/design-system/packages/create-elirobinson-design-system my-appAdd .npmrc to the app (the generator scaffolds this):
@elirobinson:registry=https://npm.pkg.github.com
Create a GitHub personal access token with read:packages. The token goes in your
user-level npmrc rather than the project one — pnpm 10 ignores registry credentials
found in a project .npmrc, so the older ${NODE_AUTH_TOKEN} form fails with a 401:
pnpm config set "//npm.pkg.github.com/:_authToken" <your-github-pat>
pnpm add @elirobinson/tokens @elirobinson/reactnpx github:EliRobinson/design-system/packages/create-elirobinson-design-system my-appThe generator scaffolds a Next.js App Router project wired to:
@elirobinson/tokensfor token imports,@elirobinson/reactfor component primitives,- a project
.npmrcfor GitHub Packages auth, - and
pnpmscripts for dev/build/start.