Most things called a design system are a page of components in a design file. That is a sticker sheet. It is useful, and it is not a system.
A system is the set of decisions that were made once so nobody has to make them again, written down clearly enough that a developer who was not in the room reaches the same conclusion you would have. The components are the visible output. The decisions are the thing that actually saves time.
Tokens are the bottom layer
Before components, name the values. Colours, type sizes, spacing steps, corner radii, shadows, and the durations of transitions. Give each a name that describes its job rather than its appearance.
colour-accent survives a rebrand. purple-500 does not, and neither does the eleventh slightly different purple somebody added because the right one was hard to find.
Keep the sets deliberately small. A spacing scale of six steps gets used correctly. A scale of twenty becomes a suggestion, and within a quarter you have three values within two pixels of each other doing the same job in different places. The constraint is the feature.
Type is the same story. Five or six sizes with line heights attached to them, chosen so they work together, beats a free numeric field every time.
Components are defined by their states
This is where systems fail, and it is almost always the same failure: the default state is beautifully specified and the other seven are left to whoever builds the screen.
A button is not one thing. It is default, hover, focus, active, disabled, and loading. A form field adds filled, invalid, and read only. A list adds empty, loading, and error. If those are not designed, they will be invented separately by every person who needs one, and your consistent product develops a rash.
The awkward ones are worth naming explicitly.
- Focus. Keyboard users need to see where they are. Removing the focus outline because it looks untidy makes a product unusable for people navigating without a mouse. Design one that fits the aesthetic rather than deleting it.
- Loading. Decide whether it is a spinner, a skeleton, or an optimistic update, and decide it once. Mixed approaches read as an unfinished product more than a slow one.
- Disabled. Usually a sign that the interface has not explained why. If a button is disabled, say what would enable it.
The empty state is real design work
The first thing a new user sees is your product with nothing in it. It is routinely the least designed screen in the whole system, which is a strange place to skimp.
An empty state has a job: explain what belongs here, and give one obvious way to put something there. "No results" is a dead end. "No leads yet. They will show up here after your first call" tells somebody what to expect and reassures them nothing is broken.
The same applies to errors. An error message should say what happened, whether the person did anything wrong, and what to try next. Most cannot manage two of the three. Writing those properly is a design decision, not a string a developer fills in at the end.
Contrast is not a preference
Text needs enough contrast against its background to be readable by people with imperfect vision, on imperfect screens, in imperfect light. WCAG puts that at a ratio of 4.5 to 1 for normal text and 3 to 1 for large text.
Check it at the token stage, when it costs nothing to change. Grey placeholder text on a white field, light grey secondary labels, and white text on a mid-tone accent colour are the three that fail most often, and all three are pale enough that whoever chose them was looking at a bright screen in a bright room.
Getting this right at the bottom layer means every component inherits it. Getting it wrong means auditing hundreds of screens later.
Write down why, not just what
A colour value tells the next person what. It does not tell them when. That gap is why systems drift.
Alongside each meaningful decision, record what it is for and where it does not apply. The accent colour is for the single primary action in a view, so two accent buttons side by side means the hierarchy is unresolved. The tightest spacing step is for elements that belong to the same object, not for making things fit.
Three sentences per decision is enough. It reads like a small log of rulings, and it turns the system from a set of assets into something that can answer questions when you are not there. When a case comes up that the rules do not cover, the log tells you how similar cases were resolved, which is exactly what a system is meant to provide.
Hand off in the vocabulary that will be built
A system that stops at the design file has done half a job. The names in the design file and the names in the code should be the same names.
If the design calls it space-3 and the stylesheet calls it --gap-md, every conversation needs a translation and the translation is where mistakes live. Agreeing the vocabulary early is a small piece of coordination that pays for itself continuously.
The same goes for behaviour that cannot be drawn. What happens to this layout at a narrow width, how long this transition runs, whether this list keeps its scroll position. If the answer is not written down, it gets decided by whoever builds it, at speed, on a Thursday.
Systems rot unless somebody prunes them
Every system accumulates a fourth button variant, a one-off card, a spacing value that was needed for one screen. Left alone, it slowly becomes a catalogue of exceptions and people stop trusting it.
Two habits prevent that. Someone owns it, with the authority to say no. And every so often you look at what has actually been used, promote the patterns that keep recurring, and delete the ones nobody chose.
A shrinking system is usually a healthy one. Our UI/UX design work aims at something a team can keep using without us, which means it has to be small enough to hold in your head and clear enough to argue with.
If you have components but no rules, or rules nobody follows, tell us what is going wrong. It often turns out to be a naming and ownership problem rather than a design one.

