khairold
← Back to Blog

Your design system is a document

·
Design SystemsAIArchitectureSystems Thinking

I built design systems for three products on three different platforms this year. A static marketing site in Astro. A mobile SaaS app in React Native. A multi-surface kiosk system in a Vite monorepo. Different brands, different tech stacks, different interaction paradigms.

The same pattern emerged in all three. And it wasn’t the component library.

The accidental discovery

The first project was a corporate marketing site. Dozens of pages, complex navigation, mega menus. I set up a design system the way you’d expect — tokens in CSS, primitives in folders, a registry, documentation pages. I also wrote a CONVENTIONS.md file: rules for how components should be built, which color tokens to use, how to assemble class names.

The conventions file was an afterthought. A reference doc for the AI agent so it wouldn’t use raw hex values or break the spacing grid.

Then I started the second project — a mobile quoting app for contractors. Completely different stack. React Native, NativeWind, Expo Router. I needed a design system again, so I wrote another CONVENTIONS.md. Same structure. Same sections. Colors, typography, spacing, component hierarchy, escape hatches.

By the third project — a kiosk platform serving three different UI surfaces from a shared component library — I noticed what was happening. I wasn’t copying components between projects. I wasn’t sharing a design system package. I was copying the document structure. The conventions file had become the actual deliverable.

What’s in the document

Every conventions file across these three projects has the same skeleton:

Colors — not just “use tokens.” Explicit ✅/❌ examples. Which tokens exist, what they’re for, which Tailwind defaults are permitted. The mobile app uses semantic tokens (bg-card, text-foreground). The marketing site uses brand-prefixed tokens (bg-brand-accent-orange). The kiosk system uses both. Different vocabularies, same structure.

Typography — the marketing site uses Tailwind text classes directly. The mobile app wraps everything in a <Text variant="headline"> component that maps to the iOS HIG type scale. The kiosk platform uses raw Tailwind classes but constrains which sizes are permitted. Three approaches to the same problem: “how do we make sure text looks intentional?”

Component hierarchy — all three enforce the same dependency direction:

tokens → primitives → composites → patterns → pages

Nothing imports upward. A primitive never uses a composite. This rule is the same whether you’re building Astro components, React Native views, or a shared UI package consumed by two Vite apps.

The escape hatch — every file has the same section:

1. Comment with @ds-override explaining why
2. Keep it minimal
3. File an issue to add the missing token/component

Same wording. Same pattern. @ds-override is grepable. I can search any of these codebases and see every deviation from the system.

Why the document matters more than the components

Here’s the thing I didn’t expect: components are ephemeral. I’ve rewritten primitives, deleted composites, restructured entire pattern folders. The Button component in the mobile app has been through three iterations. The kiosk platform’s ChatView pattern got split, merged, and restructured.

The conventions file barely changed after the first week. It’s the stable core.

This makes sense when you think about what an AI agent actually needs. It doesn’t need to see the Button component to build a new Card component. It needs to know:

  • What color tokens exist
  • What the spacing grid is
  • How class names should be assembled
  • Where new components should be placed
  • What the dependency rules are

That’s all in the document. The components are just artifacts of someone (or something) following those rules.

The mobile app took it further

The mobile project added something the other two didn’t have: screen shells.

The conventions file defines a pattern where every screen is split into a presentational shell and a thin orchestrator. The shell is a pure component — no data fetching, no routing, no authentication. It receives props and renders UI. The orchestrator wires hooks and router calls into those props.

Shell rules:
✅  Import from src/components/ (design system primitives)
✅  useState for local UI state (accordion expand)

❌  useQuery / useMutation (Convex)
❌  useRouter / router.push (navigation)
❌  Import from convex/ (any Convex types)

This isn’t a component. It’s a protocol for how screens are structured. And it’s defined in the same conventions document alongside the color tokens and spacing rules. The document doesn’t just govern visual style — it governs architectural decisions.

The result: every screen in the app — all 17 of them — follows the exact same structure. An agent building screen #18 doesn’t need to guess at the pattern. It reads the conventions, sees the five steps for creating a screen shell, and follows them. The consistency comes from the document, not from the developer’s memory.

The kiosk system proved it scales

The kiosk project is the most complex: a monorepo with a tablet-app and a staff-app sharing a UI package. Three rendering surfaces — a dark-themed customer kiosk, a light-themed admin panel, and a light-themed staff dashboard — all built from the same tokens.

The conventions file handles this by defining surface-specific rules:

SurfaceModeInputTouch Target
KioskDarkTouch48px minimum
AdminLightMouse + keyboardStandard
StaffLightTouch-friendly40px minimum

Same tokens, different constraints. The agent knows that a kiosk button needs min-h-12 (48px) while a staff button can be h-10 (40px). This isn’t encoded in the Button component — it’s encoded in the document that governs how buttons are used in context.

The shared UI package (packages/ui/) exports primitives and composites. App-specific patterns stay in each app. This split is defined in the conventions file, not in some architectural decision record that nobody reads:

WhatWhereShared?
Primitivespackages/ui/src/primitives/✅ Both apps
Compositespackages/ui/src/composites/✅ Both apps
Patternsapps/tablet-app/src/design-system/patterns/❌ App-specific

The boundary between shared and app-specific code is a table in a markdown file. And because the agent reads this file before every session, the boundary holds.

The convention as protocol

What I’ve ended up with is something like a protocol. Not a library to install. Not a Figma file to reference. A document that defines:

  1. What exists — the token vocabulary (colors, type, spacing, shadows)
  2. How to use it — explicit ✅/❌ examples, not abstract guidelines
  3. Where things go — component hierarchy with concrete folder paths
  4. How to deviate — the @ds-override escape hatch with grep-ability
  5. How to extend — the steps for adding a new component or screen

An agent that reads this document can build a component in any of these three codebases and produce something that looks like it belongs. Not because it’s copying existing components, but because it understands the constraints.

This is different from a style guide. A style guide tells you what things should look like. A conventions file tells the builder — human or AI — how to make decisions. “When you need a color, look here. When you need a new component, put it here. When the system doesn’t cover your need, do this.”

What this means

I think we’ve been thinking about design systems wrong. We treat the component library as the product and the documentation as supporting material. But in an AI-native workflow, it’s inverted. The document is the product. The components are generated artifacts — valuable, but replaceable.

A well-written conventions file is portable across:

  • Tech stacks — the same structure works for Astro, React Native, and Vite
  • Brands — swap the token values, keep the rules
  • Platforms — web, mobile, kiosk all follow the same hierarchy
  • Time — the document outlasts any individual component

The three codebases I built this year share zero components. They don’t import from each other. They don’t share a package. But they share a shape — a document structure that makes any AI agent immediately productive in any of them.

The design system isn’t the components. It’s the document that tells you how to build them.