AI coding assistants need project-specific guidance to use your design system consistently. Put the component APIs and token choices in Markdown beside the code, then use an instruction file such as AGENTS.md to tell the assistant what to read before changing UI. Automated checks can catch some violations before review.
Ask an assistant for a settings page without that guidance, and you can get something that renders but misses your conventions: a hardcoded accent color, an unsupported button variant, or a toggle rebuilt from scratch. An agent can inspect your repository, but it may miss rules spread across Figma, Storybook, and review comments.
This tutorial shows how to document a small React and Tailwind design system, connect the files to Claude Code, Cursor, and GitHub Copilot, and check the resulting code. It assumes you already have components worth reusing. Markdown gives the assistant task context; it does not train the model or guarantee that it follows every rule.
The demo repository contains two settings pages and the documentation behind them. Its README identifies the “before” page as a constructed example of unguided output. We’ll use the pages to illustrate design-system drift, rather than treat them as a controlled test of an AI assistant.
To reproduce the examples, use Node.js v24 and the checked-in lockfile:
git clone https://github.com/am-miracle/ds-markdown-demo.git cd ds-markdown-demo git checkout ea3f01ccd4f3d293d17896602679f5b29681f178 npm ci npm run dev
Open the local URL printed by Vite. The controls at the top switch between the two pages. Both are UI examples: the digest toggle changes local state, while Save changes and Delete workspace have no persistence or deletion behavior.
The repository uses React v19 and Tailwind CSS v4. Its source files provide the components; the Markdown explains how to use them.
Open src/pages/before/SettingsPage.tsx. The page compiles, but it contains several choices that conflict with the demo’s design system.
The Save changes button uses bg-[#4f46e5], an indigo value. The system defines a teal brand color through --color-brand. Regardless of why an assistant might choose indigo, the code needs an explicit route to the approved token.
The page also rebuilds primitives inline. Its toggle starts with this opening tag:
<div
onClick={() => setDigest(!digest)}
className={`flex h-6 w-11 cursor-pointer items-center rounded-full px-0.5 ${
digest ? "bg-[#4f46e5]" : "bg-gray-300"
}`}
>
That div has no switch role, keyboard interaction, or accessible name. The repository’s Switch uses a native button with role="switch", aria-checked, and an aria-label, covering those requirements of the WAI-ARIA switch pattern.
Other differences are less obvious. p-[18px] bypasses the documented spacing convention, while rounded-xl and shadow-lg bypass the named radius and shadow tokens. Even when a default utility happens to match a token’s current value, it won’t follow future changes to that token.
The repository includes a small scanner for these patterns:
npm run check:before
Its output includes:
src/pages/before/SettingsPage.tsx:15 arbitrary value: p-[18px] src/pages/before/SettingsPage.tsx:57 hex value: #4f46e5 src/pages/before/SettingsPage.tsx:69 default palette class: bg-red-600 ... 25 problem(s). 1 file(s) scanned in src/pages/before
This command intentionally exits with an error. The count represents regex findings, which can overlap: bg-[#4f46e5] triggers both the hex-value and arbitrary-value rules. It gives us a repeatable check for specific conventions, not a complete measure of UI quality.
Useful design-system documentation tells an assistant which component to import and which values it accepts. Examples should also explain the context in which a choice makes sense. In this demo, three reference files support a short AGENTS.md entry point.
tokens.md connects each utility to its purpose. Here is an excerpt:
| Token | Utilities | Use for |
|---|---|---|
--color-surface |
bg-surface |
Cards, inputs, popovers |
--color-fg-muted |
text-fg-muted |
Secondary text, hints, placeholders |
--color-brand |
bg-brand, outline-brand |
Primary actions, focus rings |
--color-brand-hover |
hover:bg-brand-hover |
Hover state of brand fills |
--color-danger |
bg-danger, text-danger |
Destructive actions, error text |
The utilities come from the @theme declarations in src/index.css. In Tailwind CSS v4, a theme variable such as --color-brand makes corresponding color utilities available. Tailwind’s theme-variable documentation explains the mapping.
State where raw values belong. In this project, hex values define tokens in the theme file; page code should use the semantic utilities. The restriction on Tailwind’s default palette is a project convention, and the default classes remain available in the demo’s build.
Document interaction states alongside the base token. A rule that says “use bg-brand” leaves the hover state open to interpretation; the hover:bg-brand-hover entry supplies the missing choice.
components.md describes each component’s API and usage rules. The demo’s Button entry starts with:
| Prop | Accepted values | Default |
|---|---|---|
variant |
"primary", "secondary", "ghost", "danger" |
"primary" |
size |
"sm", "md" |
"md" |
Button also accepts native button attributes and defaults to type="button". Its usage rules specify one primary action per card or section, with the danger variant reserved for destructive actions.
Be precise about unsupported values: outline and link are unavailable variants, lg is an unavailable size, and there is no custom loading prop. Use variant="secondary" for the bordered button. Naming the supported alternative gives the assistant something concrete to use.
Document labeling requirements too. Here, an Input belongs inside a Field whose htmlFor matches the input’s id. Switch has a different contract: its required label prop supplies its accessible name.
patterns.md provides composition recipes. The settings-page recipe uses one Card per settings group, with destructive actions in a separate card at the end.
The following adaptation adds a page heading above the cards. Each CardHeader renders an h2, so the page needs a clear heading above those sections:
<main className="mx-auto max-w-2xl space-y-6 px-6 py-10">
<h1 className="text-base font-semibold text-fg">Workspace settings</h1>
<Card>
<CardHeader title="Workspace details" description="Manage your workspace." />
<CardBody>{/* Fields */}</CardBody>
<CardFooter>
<Button>Save changes</Button>
</CardFooter>
</Card>
</main>
The repository’s original recipe prohibits an h1 outside a card, leaving the “after” page without one. Revise that rule when adopting the example. Documentation deserves the same review as the components it describes.
Include layout details that are easy to miss in a screenshot. CardBody already applies padding and field spacing; adding more at the call site changes the intended layout. A toggle row places its description on the left and Switch on the right.
Keep shared instructions in AGENTS.md and put detailed contracts in the reference files. This is a focused use of repository context for coding agents.
Here is an adapted instruction file that makes the demo’s labeling rules and missing-component behavior explicit:
# UI instructions Before creating or changing UI, read: - docs/design-system/tokens.md - docs/design-system/components.md - docs/design-system/patterns.md Import UI primitives from src/ds. Reuse them instead of rebuilding them. Use documented semantic color utilities in page code. Keep raw color values in the token definitions. Use documented spacing, radii, and shadows. Do not add arbitrary values. Wrap each Input in a Field with matching htmlFor and id values. Give every Switch a descriptive label prop. If a required token, component, or variant is missing, report the gap. Use an existing option only when its meaning fits the task. Otherwise, ask how to proceed before extending the design system. Run npm run typecheck, npm run check:docs, and npm run check:after. Update the relevant documentation when changing the design system.
A missing-component rule gives the assistant a useful next step. If the system has no warning badge, it should report that gap before choosing a tone with a different meaning.
Keep the shared rules in one place, then configure the tools your team uses. The demo has this layout:
AGENTS.md CLAUDE.md .cursor/rules/design-system.mdc .github/copilot-instructions.md docs/design-system/ tokens.md components.md patterns.md
Support differs by tool and feature. Loading an instruction file also doesn’t guarantee that every file it mentions has been read.
| Tool | Instruction file | Loading behavior to account for |
|---|---|---|
| Codex | AGENTS.md |
Discovers instructions along the project path; more specific files can add guidance |
| Claude Code | CLAUDE.md, or supported AGENTS.md loading |
An explicit @AGENTS.md import keeps shared rules available when using CLAUDE.md |
| Cursor Agent | AGENTS.md or .cursor/rules/*.mdc |
alwaysApply: true includes the rule body; referenced documents require a file read |
| GitHub Copilot | .github/copilot-instructions.md; AGENTS.md in supported features |
Support varies across IDEs, Chat, code review, and agents |
Codex documents its discovery behavior in its instruction-file guidance. Check the relevant tool’s settings when an instruction appears to be missing.
The demo’s CLAUDE.md contains one line:
@AGENTS.md
Claude Code expands this import into context. You can add Claude-specific instructions below it while maintaining the shared rules in AGENTS.md.
Current Claude Code documentation also describes direct AGENTS.md support starting with v2.1.277. By default, that fallback applies when no project CLAUDE.md or CLAUDE.local.md exists in the working directory or its ancestors. Availability and settings matter, so the explicit import remains useful for this demo.
The reference documents named inside AGENTS.md still need to be opened. For this small system, you can load them explicitly by adding these imports to CLAUDE.md:
@docs/design-system/tokens.md @docs/design-system/components.md @docs/design-system/patterns.md
Use /memory to inspect the loaded instruction files. A plain sentence telling Claude to read a file relies on the agent opening it; @path uses the documented import mechanism.
The demo’s Cursor rule lives at .cursor/rules/design-system.mdc:
--- description: Design system rules for all UI work alwaysApply: true --- Read and follow AGENTS.md at the repo root. Read the relevant files in docs/design-system/ before writing UI.
Cursor’s rules documentation distinguishes the rule body from files referenced within it. alwaysApply: true includes the body in Agent’s context; it does not inline the referenced documents. Cursor also supports a root AGENTS.md, so a simple project can use that directly without an additional rule file. These rules apply to Agent, not Cursor Tab.
For Copilot, the demo places the same reading instruction in .github/copilot-instructions.md. Consult GitHub’s custom-instruction support table for your IDE and feature. A pointer to another document should not be treated as a portable import mechanism.
Keep essential constraints in the instruction file your chosen feature loads. Before relying on the setup, use a small UI task and inspect the assistant’s file reads or available instruction references. Confirm that it found the component documentation as well as the entry point.
For a larger library, split component documentation into individual files and load the relevant contracts for each task. Loading all 60 component descriptions for a small edit adds context and maintenance overhead without helping that edit.
Both pages address this task from the repository’s README:
Build a workspace settings page with a workspace name field, a billing email field, a weekly digest toggle, a plan badge, a Save button and a Delete workspace button.
The “after” page imports the shared components. This excerpt shows its top card, with the toggle row omitted:
<Card>
<CardHeader
title="Workspace settings"
description="Manage how your workspace appears and how we contact you."
action={<Badge tone="success">Pro plan</Badge>}
/>
<CardBody>
<Field label="Workspace name" htmlFor="workspace-name">
<Input id="workspace-name" defaultValue="Acme Inc" />
</Field>
<Field
label="Billing email"
htmlFor="billing-email"
hint="Invoices and payment notices go here."
>
<Input id="billing-email" type="email" defaultValue="[email protected]" />
</Field>
{/* Toggle row omitted from this excerpt */}
</CardBody>
<CardFooter>
<Button variant="primary">Save changes</Button>
</CardFooter>
</Card>
The source files show these differences:
| Area | “Before” example | “After” example |
|---|---|---|
| Toggle | Clickable div |
Native button with switch role, state, and accessible name |
| Colors | Hardcoded indigo and default palette classes | Semantic token utilities |
| Input labels | Labels lack matching control IDs | Field labels connect through htmlFor and id |
| Card styling | rounded-xl, shadow-lg |
Shared Card uses rounded-card, shadow-card |
| Scanner findings | 25 | 0 |
Run the second check:
npm run check:after
OK. 1 file(s) scanned in src/pages/after
The scanner checks .ts and .tsx files under the supplied directory. It matches selected palette classes, hex values, bracketed arbitrary values, and radius or shadow utilities. It does not inspect the imported components, verify labeling, or decide whether tone="success" makes sense for a subscription badge.
A passing result also leaves accessibility work to review. In the demo, Field displays hint text without connecting it to the input through aria-describedby. Component reuse carries forward the component’s limitations along with its useful behavior.
To measure the effect on your own assistant, run the same task in separate sessions with and without the documentation. Record the tool and model versions, exact prompt, available context, and resulting files. Compare several runs before drawing conclusions about reliability.
Outdated documentation can direct an assistant toward a prop that no longer exists. Review documentation changes alongside the implementation, and automate the checks you can define precisely.
The demo’s AGENTS.md asks the assistant to update the relevant document whenever it changes the design system. Its check:docs script adds a mechanical check: it looks for color, radius, and shadow token names in tokens.md, plus named component exports in components.md.
The token portion works like this:
const tokens = [...css.matchAll(/^\s*(--(?:color|radius|shadow)-[\w-]+):/gm)]
.map((m) => m[1]);
const errors = tokens
.filter((t) => !tokensDoc.includes(`\`${t}\``))
.map((t) => `token ${t} is not documented in tokens.md`);
The full script reads src/index.css, src/ds/index.ts, and the corresponding documents. At the pinned commit, it reports:
npm run check:docs
OK. 15 tokens, 9 components checked
Adding --color-warning to the theme without documenting it makes the command exit with an error. To enforce that check in CI, add these steps after checkout, Node.js setup, and dependency installation in your workflow:
- run: npm run typecheck - run: npm run check:docs - run: npm run check:after
The repository contains the scripts but no CI workflow. Adapt the drift command to your production page directories; check:after only scans the demo’s “after” directory. Keep the intentionally failing “before” example outside a required passing check.
The documentation check verifies that names appear in text. It misses stale entries for removed tokens, changed prop defaults, and incorrect usage advice. It also excludes font tokens and prop definitions. For a larger system, generate API tables from TypeScript and use structured token data to check additions and removals in both directions.
Markdown instructions fit a team with an existing component library and recurring review comments about how it gets used. Start by documenting the decisions developers repeatedly correct, then add examples for the pages your team builds most often.
| Situation | Practical approach |
|---|---|
| Small, stable design system | Keep shared rules brief and load the reference files for UI tasks |
| Large component library | Maintain an index and separate component contracts; generate repetitive API documentation |
| Existing, accurate Storybook or API docs | Point to accessible source documents and add concise usage rules where needed |
| Prototype with no agreed conventions | Establish the component APIs and design choices before encoding strict instructions |
These files add no browser runtime by themselves. Their cost sits in the authoring workflow: someone must maintain the guidance, and loading more text consumes assistant context. Existing documentation should remain authoritative wherever it already answers the question.
Start with one page your team reviews often. Document the tokens and component contracts it needs, then add a composition example and configure your assistant to read it. Run the typecheck and drift scanner on the resulting code, and review the page’s behavior and accessibility separately.
The complete demo provides the files and check scripts to adapt. Keep the instructions close to the implementation so that changes to your design system also update the guidance your assistant receives.

Build a 3D hero with GPU Style Sheets and compare its code, bundle size, and rendering costs with Three.js.

Learn how to use browser AI APIs for summarization, translation, and language detection with practical examples and implementation tips.

Build a live task board with Topcoat and Rust, using reactive signals, server-rendered search, and WebSocket updates.

Learn how to build and install three Claude Code mods to monitor usage, protect secrets, and track changes.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now