Design Systems
How I Build Design Systems
Hands-on work across Ictinus, Pounce, Lungo and ProxyDigest. I connect the needs of a product to its foundations, components and patterns, working closely with engineering.
Experience across four domains
The product comes
before the system.
I start with the people using the product, the work they need to do and the constraints of their industry. Those findings shape what the system needs to support.
- OrfiumIctinus
- A shared interface language for music rights, from catalog and matching to claims and earnings.
- PantherPounce
- A dark-first system for security teams working with dense log data, alerts and investigations.
- DialecticaLungo
- A multi-theme system connecting data-heavy products across internal teams and external audiences.
- ProxyFoodsProxyDigest
- A system for food and beverage professionals moving from spreadsheets into AI-assisted formulation.
Design systems with AI enablement
Make the system usable
by people and AI.
I turn the decisions behind the library into shared guidance. Clear examples help people apply it; explicit rules help AI follow it. Both need to understand when to use a component, how it should behave and where it fits. I document that intent alongside the implementation, so new work can be checked against the same standard.
Shared foundations
Semantic tokens and component contracts define a common language for appearance, behavior, accessibility and supported variations.
Guidance for people
Examples and practical guidance explain when to use each component, how it behaves and where it fits.
Instructions for AI
Markdown rules, structured definitions and reusable skills help AI select existing components, compose them and propose extensions.
Reviewed evolution
New work is checked in context. Design and engineering review missing capabilities before they enter the shared system.
From requirements
to production.
Research and engineering requirements set the direction. I choose the foundation that fits, build the token logic, then scaffold and refine the components in Storybook.
Documentation develops alongside the library. I keep the Markdown guidance, custom skills and rules current so agents understand what to build and how to build it.
Engineering reviews every commit. Alpha testing and an experimental, closed beta exercise the edge cases before production. Feedback feeds the backlog and the next round of refinement.
.mdSkillsRulesResearch sets the direction. Engineering agrees the stack, repository access and a shared backlog.
What I build:
a clear token model.
Before components, I define how decisions connect. Primitives hold raw values, semantic tokens describe their purpose, and group tokens assign those roles to a component family. This is the three-tier structure I used for Lungo.
Names describe intent. Aliases preserve the relationships. Themes define the visual language; modes adapt it to context, from light and dark to spacing across devices.
A component keeps its role and ownership as its values change. People and AI can trace each decision back to its source.
blue.300action.primarytrigger.fillTrigger example. Studio theme, dark mode. group.trigger.color.fill.action.primary references semantic.color.fill.action.primary, which resolves to primitive color.blue.300, #85B6FF.
How I set it up:
AI with clear rules.
I translate the model into custom prompts: the hierarchy, naming rules, component ownership and required themes and modes. I use Codex and Claude to draft the initial token structure and alias mappings from those constraints.
I refine the prompts against the output, adding examples, resolving ambiguous names and correcting broken references. Each iteration makes the intended logic more explicit.
Style Dictionary turns the reviewed tokens into CSS or Tailwind foundations. I test them in React and Storybook, checking states and contrast with engineering before the changes enter the shared library.
- Define the modelPrimitive valueSemantic purposeGroup ownerHierarchyRules
- Write the custom promptBuild from our token model.› Preserve semantic aliases.› Scope by component.› Keep mode mappings.CodexClaudeSkills
- Draft the token structure
{ "action.primary": { "$value": "{primitive.color.blue.300}"} }JSONTokens - Refine themes and modesLightAaDarkOne role. Different values.AaAliasesOwnership
- Generate the code
--action-primary: var(--blue-300);bg-action-primaryStyle DictionaryTailwind - Review with engineeringStates · contrast · ownershipReactStorybookGit
Aegis · My experimental design system
A place to put
the principles to work.
I’m building Aegis as an independent case study: a working library where I can develop the foundations, write the components and test how the system holds together.
The experiment connects design decisions, React implementation and a customized Storybook. Each makes the others easier to inspect and refine.
- My scope
- Token architecture, component design, implementation, documentation and agent instructions.
- The question
- Can the same rules stay useful from a single control to a complete application?
- The output
- A component package, an interactive reference and working compositions, evolving together.
Every visual decision
has a traceable owner.
I use three token tiers: primitives hold values, semantics give them purpose, and groups assign those roles to component categories.
- Clear ownership
- A Card uses Surface tokens. The Button inside it keeps its Trigger tokens, wherever the Card places it.
- Two themes
- Fundamental and Fundamental Floating change the treatment of surfaces, borders and elevation.
- Two modes
- Light and dark are a separate choice. Spacing, control sizes and typography stay consistent across all four combinations.
Change the presentation without changing the interaction or losing someone’s work.
All four theme and mode combinations resolve to the same 34px primitive.
its own contract.
Show the parts.
Expose the decisions.
I made the component itself the starting point. Numbered anatomy callouts explain its parts; the token mapping shows where its visual decisions come from.
- Name each part
- The surface, icons, label and loading indicator are connected to the rendered Button through numbered callouts.
- Trace each value
- A property leads back through its group and semantic role to a primitive and the value resolved for the chosen theme and mode.
- Keep it together
- Usage, states, accessibility, searchable API details and examples live in the same component reference.
Owns the hit area, intent, emphasis, size and focus state.
| Group token | Semantic role | Value |
|---|---|---|
Heighttrigger.button.height.regular | size.compact-content.xxs | 34px |
Radiustrigger.button.radius.current | radius.l | 6px |
Gaptrigger.button.gap.compact | space.m | 8px |
Try a change.
See what it changes.
I made the reference a place to try the component. The playground exposes the same choices the implementation supports, so a designer or engineer can inspect a combination before using it.
- Independent choices
- Intent describes the action; emphasis controls its visual weight. Changing one does not silently change the other.
- Real behavior
- Size, icons, disabled and loading states use the component’s own rules. Loading keeps its footprint and prevents another activation.
- A shared vocabulary
- The visible choices map to the public props. The example and the implementation describe the same component.
<Button
intent="function" emphasis="primary"
size="md"
>
Create project
</Button>Explain the choice.
Make the contract searchable.
I customized the documentation around the questions people bring to a component: when to use it, how it behaves and what the code accepts.
- Usage guidance
- Action labels, hierarchy and keyboard behavior sit beside the working example, where the guidance can be tried.
- States
- Ready, disabled and loading are documented as behavior. The reference explains what changes for both the person and the interface.
- API from source
- Searchable properties, types and explicit defaults come from the TypeScript API. Inherited platform properties remain available in the full reference.
Make the action predictable.
No save action pending.
- Label
- Use a verb that predicts the result. Use Link when the purpose is navigation.
- Hierarchy
- Reserve the strongest emphasis for the main action in a group.
- Behavior
- The default type is
button. Settype="submit"explicitly for a form. - Keyboard
- Native Enter and Space activation are retained.
An example should
let you follow the behavior.
I wrote examples around small, complete interactions. They make the less visible parts of the system available to inspect, including validation, asynchronous actions and where focus returns.
- Action and recovery
- An export offers related formats, blocks duplicate requests while pending and exposes a retryable error when the operation fails.
- Connected fields
- A label, helper text, error and control share their accessible relationships. The validation example updates them as the value changes.
- Continuity
- Clearing a field updates its value and returns focus to the input. The person can continue from the same place.
One action. Several routes.
Export CSV, open the format menu or try the unavailable-service state.
No export requested
<SplitButton
onAction={() => exportData("CSV")}
actions={exportFormats}
>
Export CSV
</SplitButton>Put the components
under real pressure.
Isolated examples only take the system so far. I built a workspace application and richer component presentations to exercise the decisions together.
- Connected states
- The DataGrid combines search, filters, column controls, selection, bulk updates and editable record details.
- Application context
- Navigation, forms, tables and panels need to work with real content lengths and responsive layouts.
- AI interactions
- Local simulations let me explore attached context, stopping, retrying and retaining partial responses before connecting a provider.
- FilterNeeds review
- InspectPRJ-01Navigation
review - ActStatusReady
The same contract,
available to agents.
I wrote reusable skills for creating components, tokens and layouts. Storybook presents the actual repository instructions, with the shared design contract included when they are copied.
- Start from the system
- Inspect existing components, preserve category ownership and carry each semantic role through all four theme and mode combinations.
- Keep the boundaries
- Headless interaction behavior stays separate from styling. Compositions preserve the contracts of their nested components.
- Make it inspectable
- Machine-readable component definitions and a token graph sit beside the human reference, giving tools another way to inspect the same system.
The instructions end in review: keyboard behavior, states, resolved values and the composition in context.
Measure the system’s impact over time.
The figures below are outcomes from the Pounce design-system work. They are not measured gains from AI. For AI-assisted contributions, I would also track review effort, rework and recurring system gaps alongside adoption and quality.
- 50%+
- Delivery velocity (Pounce)
- 30%
- Fewer data-processing errors (Pounce)
Adoption
Share of UI built with the system.
Compliance
Share of values that are approved tokens, not raw hex.
Coverage
Components and patterns the system actually provides.
Accessibility
Contrast pass rate across supported modes and themes.
Contribution
Activity and number of contributors.
Outcomes
Delivery velocity, rollovers, and error reduction.
Explore the system in Storybook.
See how the token model becomes working components, documented states and reusable patterns in Aegis. Open Storybook to explore the system in context.
Aegis and the related projects are independent explorations of this approach, separate from the historical results above.