Documenting a Design System

design-systemsbuilding-in-public

Sirius2 has been powering our products for almost two years now. Sixty-plus components, a full semantic token architecture, three icon libraries, and products from Meera to Codex using it daily. It works. People build UIs with it.

But here’s the thing: people were using it by looking at how other products used it. Copy a Card from Meera, adapt it for Codex. Find a Button variant in Journey, use the same one. It worked, but it meant the design system’s knowledge lived in people’s heads and in scattered examples across codebases. New team members had to learn by osmosis.

That’s not a design system. That’s a component library with tribal knowledge. A design system needs documentation. Not just API reference, but the why behind the decisions.

What we’re documenting

We structured the docs into four pillars:

Foundations: the principles that inform every design decision. Accessibility (we target WCAG 2.1 Level A and AA), Information Architecture (progressive disclosure, one home with many doors), Internationalization (text expansion, cultural differences, locale-aware formatting). These aren’t aspirational. They’re constraints. When someone asks “should this modal auto-focus the close button?” the Accessibility foundation answers it: “never move focus without user input, except for truly blocking interruptions.”

Design: the visual language. Color, Depth, Layout, Typography, Icons. Not a style guide, but a set of design decisions with reasoning. Why is the interface monochromatic? Because color should be signal, not decoration. Why do buttons have beveled shadows? Because depth signals interactivity.

Components: the API reference. Every component documents its props, slots, and shows live examples. But more importantly, every component has notes, best practices and usage guidance that teach the reasoning, not just the syntax.

Reference: the searchable token index and icon browser. Every semantic token with its name, value, and what it maps to. Every icon across all three libraries, searchable by name.

Self-documenting components

The most valuable architectural decision we made for documentation: every component carries its own docs as a Ruby metadata class.

class Sirius2::Metadata::Button
  def self.documentation
    {
      name: "Button",
      group: "actions",
      description: "Buttons are used to trigger actions...",
      examples: [...],
      props: [...],
      slots: [...],
      notes: <<~ORB
        <Dodont>
          <Dodont:Do>Use strong, actionable verbs</Dodont:Do>
          <Dodont:Dont>Use vague labels like "Click here"</Dodont:Dont>
        </Dodont>
      ORB
    }
  end
end

The documentation app reads these metadata classes and renders them into browsable pages: live examples, props tables, slots tables, and best-practice notes. When someone adds a new prop to a component, the documentation update is part of the same commit. When someone adds a new example, it renders live in the docs app without any separate build step.

The Do/Don’t panels are everywhere. Almost every component and every design concept uses them, with explicit visual guidance showing the right way and the wrong way, with explanations for why. “Use color to support states” (do) vs. “Use color to decorate or distract” (don’t). It’s pedagogical, not prescriptive. We want people to understand the reasoning so they can make good decisions in cases the docs don’t cover.

Why this matters

A component library without documentation gets used by people who already know it. They learn through code review, through pairing, through trial and error. New team members are slow to get productive. People make inconsistent choices because they don’t know what the intended patterns are.

Documentation changes that dynamic. When someone wonders “which Button variant should I use for a destructive action?” the docs show the Danger tone with a note explaining when to use it. When someone isn’t sure how to structure a form, the FormLayout component page shows examples of single-column, multi-column, and annotated section layouts with guidance on when each is appropriate.

The documentation app itself is also a top-hatting tool. Fire it up, browse every component in every state, and spot visual regressions or inconsistencies. The Tophat route renders all components on a single page, a visual regression check that takes five seconds.

The MCP unlock

Here’s something we didn’t plan for but that turned out to be one of the biggest payoffs: because every component carries its own documentation as structured Ruby data, we were able to add an MCP server almost trivially. The metadata classes already describe every component’s props, slots, examples, and best practices in a structured format. Exposing that through MCP tools like find_components, get_component_details, search_design_tokens, and search_icons was mostly just wrapping the existing metadata API.

This was a huge unlock for AI-assisted development. AI coding agents that connect to the MCP server understand the Sirius2 design system natively. They know which components exist, what props they accept, how slots work, and what the best practices are. They can write ORB templates that use the correct components with the correct variants, because the design system documentation is available to them as structured tool output, not as a README they might hallucinate about.

The fidelity of AI-generated UIs jumped dramatically. Before the MCP server, agents would guess at component names and prop values. After it, they query the design system and build with the actual API. Self-documenting components made that possible. If the documentation had lived in markdown files separate from the code, building the MCP server would have been a different, harder problem.

We should have written this documentation a year ago. The time spent now is time we’ll save on every future conversation that starts with “how do I…”, whether that conversation is with a human or an AI agent.