Back to blog

Blog

Designing a design system outside Figma

Design SystemsFigmaCursorTokensMonorepo

The Core Idea

This project started with a question that sounds technical, but is really a product question: what happens if the design system does not live first in a visual tool, but in the same place where the product is built, tested, and consumed?

When I say “designing a design system outside Figma,” I do not mean rejecting Figma. Figma is still a valuable tool for exploring, aligning, and communicating. The difference is the source of truth. In this project, the source of truth stopped being a visual file and became a living system in code: tokens, components, documentation, rules, tests, and automations.

A token is a design decision turned into a reusable variable. For example: a background color, a border radius, a shadow, spacing between elements, or an animation duration. Instead of deciding those values again and again on every screen, the system names them, centralizes them, and reuses them. That matters because it turns design into infrastructure: something that can grow, change, and be maintained with less improvisation.

That was the intention behind Design System Sho: to move from a component gallery to a monorepo capable of supporting a real design system.

The repository began as an initial extraction of a component system. At that first stage, there was already an important foundation: tokens, components, layouts, and a documentation app in Next.js. The gallery was the place where you could see what existed.

That first showcase had a very concrete value: it made inventory possible. Before thinking about advanced architecture, we had to answer basic questions:

  • What components do we have?
  • What do they look like?
  • What variants exist?
  • Which parts are primitives and which are compositions?
  • Which visual decisions repeat?
  • What is documented, and what only exists because someone remembers it?

The gallery was the first bridge between intention and reality. It was not yet a mature system, but it was a mirror. It showed the state of the project as it was, with its strengths and its gaps.

That lesson mattered: a component gallery is not just a nice-looking page. It is an audit tool. It helps reveal inconsistencies, understand the inventory, and start making decisions based on evidence.

The Shift in Question: From “How Does It Look?” to “How Does It Scale?”

At first, the main question was visual: making sure the components looked good, that there was a defined UI, and that the system had a clear identity. But as the project grew, the question changed.

The new question became: how do we make this scale?

Scaling does not mean adding more components without order. Scaling means the system can receive new pieces without losing coherence. It means a color decision is not manually copied across twenty files. It means dark mode does not depend on component-by-component exceptions. It means names are clear, variants are predictable, and anyone can consume the system from another project.

That shift in question was the turning point. The project stopped being only a visible collection and started becoming a platform.

Decision 1: Token-First as a Design Principle

The first major decision was to adopt a token-first logic.

Token-first means components should not invent styles on their own. A button should not decide on a specific blue. A card should not decide on an isolated shadow. An input should not have a border that only lives inside that file. Everything should come from semantically named tokens.

In practice, that led to a clear rule: use --sho-* variables as the visual foundation of the system. Colors, spacing, radii, sizes, shadows, motion, layout, and typography weights started to be organized as a shared language.

This matters for three reasons.

First, because it enables consistency. If the action color changes, you do not search component by component. You update the token.

Second, because it enables themes. Dark mode is not solved with special classes per component, but by redefining the same tokens inside .dark. The component uses the same semantic name; the theme decides the value.

Third, because it enables governance. If the system has clear rules, Cursor can help implement them. AI stops improvising styles and starts working within a framework.

This is where one of the strongest ideas in the project appears: good judgment is not enough. You have to turn that judgment into repository rules.

Decision 2: Building a Native Identity, Not Copying External Naming

Another important decision was to clean up external references and define Sho’s own naming system.

Naming components may seem like a minor detail, but it is not. Names create the system’s mental map. If a component has one name in the documentation, another in the code, and another in team conversations, the system starts losing clarity.

That is why canonical names were defined. Some inherited names were replaced with names that were clearer or more aligned with Sho: Snackbar became Toast, Textfield became Input, BoxSelector became Choicebox, Message became Alert, FeedbackScreen became Result, among others.

The change was not only aesthetic. It was a strategic decision: the system needed to speak with its own voice.

To keep that transition from becoming chaotic, a migration guide was documented, compatibility aliases were kept for a while, and a codemod was prepared. A codemod is a script that helps update code automatically when APIs or names change. In other words, the project did not just decide to rename things; it prepared the path so other projects could migrate.

Decision 3: Moving From a Monolith to Modular Primitives

During one phase of the project, component-system.tsx concentrated much of the public API. That had value at the time: it made it possible to gather, organize, and expose components from one clear place.

But a monolith also has limits. When too many decisions live in a single file, the system becomes harder to audit, test, and extend.

The next evolution was to move the real implementations into modules by primitive. A primitive is a foundational piece: Button, Input, Modal, Table, Badge, Toast, Tabs, etc. Each primitive started living in its own folder, with its variants and API.

This is where CVA appears, which stands for Class Variance Authority. It is a library that lets you declare component variants in an organized way. For example, a button can have variants for hierarchy, size, destructive state, or loading. Instead of spreading style conditions throughout the component, CVA helps define that variant matrix explicitly.

The practical decision was this: component-system.tsx stopped being the place where everything lives and became a barrel, meaning a file that re-exports components from their real source.

That change made the system more maintainable. Each component could grow without contaminating the others. And each variant decision could be reviewed in its own context.

Decision 4: Layout Also Had to Be a Foundation

Design systems often focus on color, typography, and components, but leave layout to local decisions. This project took another path.

A layout foundation was defined around an 8px mini unit, fluid columns by breakpoint, and primitives like Grid, GridColumn, Stack, Cluster, and PageShell.

This matters because layout is where many interfaces break. A component can be well designed in isolation, but if every screen uses different gaps, columns, margins, and containers, the whole product feels inconsistent.

The 2x layout decision was about turning the product’s geometry into part of the system. Not just designing buttons, but also designing the space where those buttons live.

The docs app evolved a lot. At first, it was a gallery. Then it started behaving like an internal product.

A marketing home appeared, along with navigation by foundations and components, a playbook of real scenarios, search with a command palette, and previews with a dark mode toggle. Showcases were also split into lazy-loaded files so the system would not depend on a single giant file.

This change is important because design system documentation does not only explain the system. It also tests it.

An isolated component can look good. But the playbook asks another question: how does it behave when combined with other components in real flows? Forms, checkout, scheduling, feedback, tables, filters, empty states. That kind of documentation brings the system closer to product reality.

In that sense, documentation became a second layer of validation. It did not only show components. It showed decisions working together.

Decision 6: Automated Governance

The word governance can sound heavy, but the idea is simple: how do we prevent the system from degrading over time?

A design system degrades when rules live only in one person’s memory. It degrades when someone adds a direct color because they are in a hurry. It degrades when a new component does not respect the tokens. It degrades when dark mode is solved with file-by-file patches. It degrades when there is no quick way to know whether one decision broke another.

To avoid that, the project incorporated several layers of control:

  • Cursor rules for working with Sho tokens.
  • An internal skill for building components according to the system checklist.
  • Verification scripts for tokens, styles, and layout.
  • Custom ESLint to block disallowed styles.
  • Dependency Cruiser to protect boundaries between layers.
  • Typecheck, lint, tests, smoke tests, and CI.

The key idea is that the system should not depend only on manual review. Human review still matters, but now it is supported by tools that detect deviations.

This is one of the project’s central lessons: if you are going to use AI to build a design system, you need to give it verifiable rules. Cursor was powerful not because it replaced judgment, but because it could work within a framework I was defining.

Decision 7: Migrating to a Monorepo

The move to a monorepo was one of the most important decisions.

A monorepo is a repository that contains multiple related packages or applications. In this case, the project was organized with apps/ and packages/:

  • @sho/react for components, primitives, and layouts.
  • @sho/styles for tokens and publishable CSS.
  • @sho/docs for documentation and showcase.
  • @sho/playground for testing isolated consumption.
  • Tooling packages like @sho/codemod, @sho/eslint-config, and @sho/tsconfig.

This structure matters because it separates responsibilities. Documentation is no longer the same thing as the library. Styles are no longer mixed into the app. Components can be built, versioned, and consumed as a package.

The monorepo was the step that turned the project into something closer to reusable infrastructure. It was no longer just “a repo where you can see components.” It was a foundation that other projects could consume Sho from.

The Tension of Migration: Preserving the UI Without Losing the New Structure

One of the most important conversations happened after the migration to a monorepo. The new structure was cleaner, but some components no longer looked like they used to. Styles had changed, parts were broken, and color was missing.

That moment was key because it revealed a real tension in any migration: good architecture is useless if it loses the visual experience that had already been achieved.

The goal was not to go back. The goal was to recover the UI defined before the migration while keeping the new structure.

That lesson is very important to share: architecture should protect design, not replace it. A successful migration is not the one that only leaves cleaner folders behind. It is the one that preserves the behavior and appearance that already worked while improving maintainability.

That kind of decision is exactly where Cursor’s plan mode was useful. Before editing, we had to understand what had happened: imports, tokens, modularized CSS, Tailwind detection, package dist, old routes, stale server, components pointing to previous paths. The work was not “fixing colors”; it was diagnosing the system.

Theme Studio: Closing the Visual Loop Inside the Same System

Another important milestone was Theme Studio.

Theme Studio brings a powerful idea into the project: if tokens are the source of truth, there should also be a visual way to edit, test, and export them inside the same environment where the system lives.

This brings the project closer to a real alternative to Figma Variables for certain workflows. Not because it replicates all of Figma, but because it allows visual theme iteration in the browser and connects that iteration to the tokens the app actually consumes.

The promise is clear: see, adjust, test, and export without breaking the chain between design and development.

The Role of Cursor: Not Magic, but Amplified Judgment

Cursor was a central tool in this process, but not as a replacement for experience.

AI accelerates when there is direction. Without direction, it can produce code that appears to work but breaks the system. With clear rules, it can become an extension of the team’s judgment.

In this project, plan mode played a strategic role. It helped think before touching code, separate symptoms from causes, discuss tradeoffs, and turn a broad intention into concrete steps.

Previous experience was also key. Knowing what to ask for, what to review, what to block, what to accept, and what to turn into a rule is part of the work. Cursor helped execute, explore, audit, and migrate. But the important decisions remained product and system decisions.

The sentence that sums this up is: I did not use Cursor to decide for me; I used it to multiply my ability to turn decisions into structure.

What Was Actually Built

Seen from the outside, the result could be summarized as a monorepo with React, tokens, and docs. But that falls short.

What was built was a way of working:

  • A token-first system where design lives as variables, not loose values.
  • A set of modular primitives with explicit variants.
  • A native naming system for Sho.
  • Documentation that works as inventory, test, and internal product.
  • A layout foundation to organize space, not just components.
  • A Theme Studio to iterate visually with real tokens.
  • A monorepo prepared for consumption from other projects.
  • A governance layer so humans and agents work with the same rules.

There was also concrete scale: around 26 commits during an intense phase, more than 500 files touched, close to 42 modular primitives, a matrix of 45 components, P0-P2 variants, canonical renames, WCAG 2.2 AA accessibility improvements, CI, tests, and @sho/* packaging.

Those numbers matter not because of volume, but because they show the work stopped being a mockup. It became a platform.

Lessons for the Audience

The first lesson is that a design system does not start when everything is perfect. It starts when you decide to make what exists visible. The gallery was that first act of honesty.

The second lesson is that tokens are more than variables. They are decisions. When a team names its tokens well, it is naming its way of thinking.

The third lesson is that architecture should not work against the UI. If a migration breaks what already worked, the migration needs to be corrected, not the experience sacrificed.

The fourth lesson is that documentation is not something you write afterward. Documentation can be a way of designing. The showcase, the playbook, and the variant matrices helped decide, not just explain.

The fifth lesson is that AI needs governance. Cursor was useful because the project had rules, skills, checks, and a clear vision. AI does not eliminate the need for judgment; it makes it more urgent.

The sixth lesson is that Figma does not have to be the only center of the system. For many teams, code can be the source of truth when the goal is for the system to be consumable, verifiable, and versionable.

The Story in One Sentence

Design System Sho went from being a component gallery to becoming a design system in code: token-first, modular, documented, verifiable, and ready for other projects.

The story is not just “we made a monorepo.” The story is that design started living where execution also lives. And thanks to Cursor, plan mode, and accumulated experience, that work could move fast without losing intention.

Possible Closing for the Presentation

If I had to close this story in front of an audience, I would say this:

I was not trying to remove Figma from the conversation. I was trying to prove that a design system can have another source of truth.

Figma helps us imagine. But this project needed something more: it needed rules, packages, tokens, tests, documentation, migrations, and real consumption.

The gallery was the first mirror. The tokens were the language. The components were the raw material. The monorepo was the infrastructure. Cursor was the copilot. And plan mode was the space where decisions stopped being loose intuitions and became strategy.

That is what I have built with Design System Sho: not just a collection of components, but a way to turn design judgment into a system.

Internal Sources Reviewed

  • README.md
  • CHANGELOG.md
  • CONTRIBUTING.md
  • docs/architecture.md
  • docs/MIGRATION.md
  • docs/component-checklist.md
  • docs/component-variants-roadmap.md
  • docs/component-variants-audit-report.md
  • docs/layout-foundation.md
  • .cursor/rules/token-first-components.mdc
  • .cursor/skills/build-sho-component/SKILL.md
  • Repository commit history
  • Previous work transcripts, including the summary of the last two weeks and the visual recovery plan after the monorepo migration