Strict enough that AI cannot drift
AI generates interfaces now. That makes your design system the most important thing in your codebase. But only if it is strict. A loose design system is an invitation for the model to improvise, and it will accept that invitation every single time.
Design systems are no longer optional
Someone wrote a post saying that once models generate your UI, the design system becomes the source of truth the machine builds against. Totally true.
Drift used to happen at human speed. Now it happens at machine speed, because every sloppy generation becomes the reference for the next one.
I wonder then, why do most of us still treat the design system as a nice-to-have? Most of them are not even systems. They are suggestions. A Figma file. A palette nobody enforces. A folder with three ways to build a button. A person can work around that. A model cannot. It will add a fourth button, a new gray and a one-off media query, all in one afternoon.
If your system leaves room to improvise, AI will improvise. Every time.
So I made mine strict. I call it naina. A few CSS files with one job: make wrong output impossible to write.
Colors are four seeds and nothing else
My whole palette comes from four seed hues in one file. Each seed is a chroma, a hue and a lightness. Twelve accent slots cycle through the four. Ten neutrals come from one scale. Everything is oklch, everything is computed. I change a seed, the whole palette follows.
Components get --color-neutral-1 to --color-neutral-10 and --color-color-0 to --color-color-11 and nothing else. No hex, no rgb, no raw values. Those live in one file and one file only.
Dark mode is not a second stylesheet. Same seeds, a lightness delta, a chroma multiplier, and the neutrals flip. One media query. Components never know dark mode exists.
A model cannot pick an almost-right blue in this setup. There is simply no way to write one.
Type belongs to the element, not the component
Size, weight, family and color are set once, on semantic elements. All of it sits on a fluid scale, --font-size-0 up through the headings, a major second at 320px growing into a minor third at 1440px. No breakpoints, the type just flows with the viewport.
Components do not touch fonts. If a heading looks right by default, you write nothing. Not writing a rule is the rule. Overrides happen only when they mean something, like muting a paragraph to --color-neutral-5 or bolding a term in a card, and only through --font-size-* variables, never a raw number.
This is the thing models get wrong the most when left alone. They sprinkle font sizes on everything. Take the property away and the problem is gone.
Space is nine sizes, all computed
Nine t-shirt sizes: --space-3xs, --space-2xs, --space-xs, --space-s, --space-m, --space-l, --space-xl, --space-2xl, --space-3xl. Each is a multiplier on a base that scales with the viewport, so every gap is fluid for free. Between them sit pairs like --space-s-m, one size on small screens growing into another on large ones. Radius is one variable, --border-radius, always 1rem.
Nothing is hardcoded in px or rem. The multipliers, the viewport range and the base sizes live in the same file as the color seeds. One file, one source of truth.
Layout is one grid and no media queries
Every page is one <main class="grid"> with named columns. Content lands in the standard column on its own. Three escape classes, .wide-1, .wide-2 and .full, push a section wider or full bleed. Rows come from .hol-wrapping-row and .hol-column. That is the whole layout story.
No media queries in component code, anywhere. Responsiveness comes from the fluid scales and from auto-fit rows that wrap on their own. A model cannot write a breakpoint hack here. Breakpoints do not exist.
Four CTAs and no inline styles
CTAs are four classes: .primary, .secondary, .tertiary, .delete. Inputs, selects and textareas are styled once, in one place. Inline styles are banned, the only exception being JavaScript setting a transform at runtime.
Each component gets one class on its root. Everything inside is reached through nested child selectors on semantic elements. No utility soup, no BEM underscores, no third way of doing anything.
The ruleset is the prompt
And this is what changed how I work with AI. The rules above are literally what I hand the model, the same rules I would hand a new developer. Never invent a color variable. Never set a font size in a component. Never make a fifth CTA. And when the design seems to need something outside the system, do not solve it quietly, flag it and ask me.
That last rule matters the most. The dangerous failure is not ugly output, it is plausible output that quietly grows the system. A strict rule turns that moment into a decision instead of drift.
We used to judge a design system by how much it let you do. Judge it by how much it forbids. naina is not there to inspire the machine. It is there to leave it no choice.