On this page
Most teams that say they have a design system have a component library. That is a good start. A library tells you what exists. It does not tell you when to use a part, what a colour means, or which combinations are wrong. People fill that gap with memory and Slack threads. AI tools cannot.
This note follows one idea through every layer: a design decision should be written down once, with its meaning, and every layer below it should refer to it rather than copy it.
01A component library is not a design system yet
A library is an inventory. A system is an inventory plus the decisions behind it: the principles, the values, the rules for combining parts, and a clear owner for each change. The difference shows up the first time someone asks a question the library cannot answer, such as "which button do I use for delete?"
Answers one question: what exists? Everything else lives in people's heads.
- Principleswhy we design this way
- Tokenswhich value, for which purpose
- Componentswhat exists, and its contract
- Patternshow parts combine for a task
- Governancewho decides, how it changes
Answers what, when, why and who decides. Each layer refers to the one above it.
The layers matter for a practical reason. When a decision lives in only one place, it can change in one place. When it is copied into fifty components and two hundred screens, every change becomes a search-and-replace exercise, and drift is guaranteed.
02Primitive tokens store values. Semantic tokens store decisions.
Design tokens are named design values: colours, sizes, spacing, radii, type styles, durations. Most mature systems use three tiers, and the tiers do different jobs.
- Primitive tokens name the raw palette.
blue.600is#1f4fd1. A primitive says nothing about where it may be used. - Semantic tokens name a purpose and point at a primitive.
color.action.primary.backgroundrefers toblue.600. This is where the design decision lives. - Component tokens are optional. They let one component deviate in a controlled way, for example
button.primary.background, which normally just refers to the semantic token.
| Tier | Example | Answers | Changes when |
|---|---|---|---|
| Primitive | red.600 = #b42318 | Which values do we have? | The brand palette changes |
| Semantic | color.feedback.danger.text | What is this value for? | A design decision changes |
| Component | alert.danger.icon | Where does this part deviate? | One component needs an exception |
Two rules keep the tiers useful. Components and screens never reference primitives directly. And semantic tokens are added for a purpose, not for a colour someone happened to like, otherwise the semantic layer turns into a second, longer palette.
03Semantic names carry design intent
A token name is the shortest piece of documentation in the system, and it is the piece that people and tools see most. A good name says what the value is for, so the person or tool reading it can tell when it applies and when it does not.
colorcategoryfeedbackconceptdangervarianttextpropertyhoverstateCompare the names a drifting system tends to collect with names that carry intent:
| Name by appearance or location | Problem | Name by intent |
|---|---|---|
blue-600 used in components | Says nothing about use. A tool will apply it to anything blue. | color.action.primary.background |
red-text | Breaks when the error colour is no longer red, and invites use for decoration. | color.feedback.danger.text |
sidebar-grey | Tied to one screen. The next panel invents its own grey. | color.surface.subtle |
spacing-13 | Off-scale value with no reason attached. | space.inset.sm (12px) |
Intent-based names pay off twice. Themes become a mapping exercise: dark mode or a sub-brand remaps semantic tokens to other primitives, and no component changes. And the names become searchable by purpose. Someone, or something, looking for "the colour for an error message" can find it without knowing the palette.
If you can rename the primitive without the semantic name becoming a lie, the semantic name is doing its job. color.text.link survives a rebrand. color.text.blue does not.
04Components consume tokens and expose meaning
A component is where tokens meet behaviour. Two things make a component part of a system rather than a reusable snippet: it gets every visual value from tokens, and it exposes options in the language of intent, not appearance.
That second point is easy to miss. A button with a color property invites every team to pick its own. A button with an intent property, limited to primary, secondary and danger, asks a better question: what is this action? The component then decides how that looks.
- intent
primary·secondary·danger- size
sm·md- loading
- boolean, keeps width, blocks repeat clicks
- label
- required, starts with a verb
- Rules
- One
primaryper view.dangeronly for actions that destroy data, always behind a confirmation. - Not exposed
- colour, padding, radius, font
The contract is the part most libraries leave unwritten. It includes the properties and their allowed values, the slots for content, the combinations that are not allowed, the accessibility behaviour, and the rules of use. Written down, it turns review comments into checks. "We don't put two primary buttons in a view" stops being taste and becomes a rule that a linter, or an AI tool, can test.
05Documentation for people, metadata for machines
Guidelines written for people usually live in a documentation site: when to use a component, examples, do and don't. That is still needed. But prose in a web page is hard for a tool to use reliably. The same decisions can also be stored as structured data next to the tokens and components.
For tokens, the Design Tokens Community Group format is a good default. It is plain JSON with a value, a type and an optional description, and references to other tokens use curly braces:
// tokens/semantic.tokens.json { "color": { "action": { "danger": { "background": { "$type": "color", "$value": "{red.600}", "$description": "Fill for actions that destroy data. Needs a confirm step." } } } } }
The description is short, specific and written as a rule, because that is how it will be used.
Components need the same treatment. A component manifest records what the documentation site says, in fields a tool can query:
// components/button.meta.json { "name": "Button", "purpose": "Triggers an action on the current page.", "useInstead": { "navigation": "Link" }, "props": { "intent": { "enum": ["primary", "secondary", "danger"], "default": "secondary" }, "size": { "enum": ["sm", "md"], "default": "md" } }, "rules": [ "Max one intent=primary per view.", "intent=danger requires a ConfirmDialog.", "Label starts with a verb and names the object." ], "tokens": ["color.action.*", "space.inset.md", "radius.control"], "a11y": "Native button element. Visible focus ring. Loading state sets aria-busy." }
The format matters less than three properties: it is versioned with the code, it is generated or checked from the source rather than typed twice, and it uses the same names as the design file and the code. In Figma, that means variables named like the tokens, with descriptions and code syntax filled in, and components mapped to their code counterparts, for example with Code Connect.
06How an AI tool can use all of this
An AI tool that generates interface code has two options. It can guess from patterns in its training data, or it can read your system. Given only a component list, it guesses: it picks plausible props, fills gaps with raw values, and produces something that looks close but is wrong in the details that matter.
Given tokens with meaning, component contracts and rules, the same request follows a different path:
<button
style="background:#d92d20;
padding:10px 18px">
Delete
</button>
A close red, an off-scale padding, a vague label and no confirmation. It passes a glance and fails a review.
<ConfirmDialog
title="Delete client file?">
<Button intent="danger">
Delete client file
</Button>
</ConfirmDialog>
Real components, the right intent, a rule-compliant label. A brand change later updates this code for free.
None of this requires a special AI product. Tools that connect assistants to a design file or a repository, such as MCP servers, pass along whatever structure exists. If the structure is a flat list of hex values and unnamed frames, that is what the assistant gets.
A system is not ready for AI because it has tokens. Primitive-only tokens give a tool a palette with no rules. What makes the difference is explicit semantics (names and descriptions that state purpose), component contracts (allowed options and rules), documentation in structured form, and parity between design and code. If Figma says intent=danger and code says variant="red", the tool has to guess which one is true.
07Worked example: one destructive action, every layer
Here is the full chain for the delete action from the previous section, from palette value to rendered dialog. Every number in the dialog maps to a named decision.
This removes the file for Jansen Holding and its 14 documents. 2This cannot be undone.
- 1
text.heading.smtype.size.300 · weight.semibold - 2
color.feedback.danger.text→ red.600 · #b42318 - 3
color.action.danger.background→ red.600 · via button.danger.background - 4
space.inset.lg·radius.container→ space.6 · 24px · radius.4 · 12px
Read the chain from top to bottom:
- Primitive.
red.600holds the value. It is used nowhere directly. - Semantic.
color.action.danger.backgroundsays "this is the fill for actions that destroy data" and points atred.600. Its description carries the rule about confirmation. - Component.
Buttonmapsintent="danger"tobutton.danger.background, which refers to the semantic token. The contract limits danger to destructive actions. - Pattern.
ConfirmDialogdocuments when confirmation is needed and requires the button label to repeat the object: "Delete client file", not "OK". - Screen. A designer, a developer or an AI tool asks for a destructive action and gets all of the above without remembering any of it.
Now change something. Accessibility testing shows the red is too light on a tinted surface. You change one primitive, or point the semantic token at red.700. Every danger button, error message and alert icon updates, and nothing else does. That is the practical test of a system: the cost of a change matches the size of the decision.
08A readiness checklist
Use this to judge where a system stands. Most systems are strong on the first two items and thin on the rest; the last four are the ones that make a system legible to tools.
- Components and screens never use primitive tokens or raw values directly.
- Semantic tokens are named by purpose and survive a rebrand without renaming.
- Every semantic token has a one-line description written as a rule of use.
- Components expose intent-based options with closed lists of allowed values.
- Contracts state rules of use and forbidden combinations, not only properties.
- Token and component metadata is stored as data, versioned with the code.
- Names in the design file and in code match, and a check fails when they drift.
- Each layer has an owner and a documented way to propose a change.
None of these items is specific to AI. They are what a good system always needed. AI tools just stop you from relying on the people who used to carry the missing parts in their heads.