Skip to main content

From Button to a Design System

What you learn in this chapter​

So far, using Button as the subject, you have learned many techniques. In the final chapter, you confirm that what you gained with Button has been the design principles of a design system all along.

What was condensed into Button​

In the introduction, I wrote that "it packs in all the challenges of component design". Here is the answer key.

What you learned with ButtonThe design-system principle
Design tokens (bg-primary)Manage color and spacing by meaning and keep them consistent
Variants with cvaDefine the kinds of a component declaratively
Safe merging with cnLet consumers customize safely
Slot / asChildCompose components
TypeScript typesGuarantee usage with types
focus-visible / roleBuild in accessibility from the start

These are not just for Button. They are design principles common to all components.

The three layers of a design system​

You can organize a design system by thinking in roughly three layers.

③ Patterns / compounds
Forms, card + button, dialogs…
② Primitive components
Button, Input, Badge, Card, Alert…
① Design tokens
Color, spacing, corner radius, typography (@theme / OKLCH)

※ The lower layer (①) supports the upper layer (③)
  • ① Tokens: the values every component references. The values like --primary you learned in the design tokens chapter
  • ② Primitives: the smallest-unit components that use tokens. Button and Input
  • ③ Patterns: larger groupings that combine primitives

Button is exactly ②. It sits on top of ① and gets incorporated into ③.

Where does consistency come from​

In the advanced chapter, you built Button, Badge, and Alert with the same pattern. Consistency comes from the following two kinds of "commonality".

1. Common tokens​

All components reference the same --primary or --destructive.

--primary (defined in one place)
↓ everything references it in common
Button / Badge / Alert … (all reference bg-primary)

Just changing --primary once changes the lead color of all components at the same time.

2. Common patterns​

Every component uses the same way of building: always cn + ...props, plus cva and asChild as needed. When the way of building is uniform, you can build new components without hesitation, and readers understand them right away.

File structure and naming​

shadcn/ui's standard structure directly reflects the three layers of a design system.

src/
├─ index.css # ① design tokens (@theme / :root / .dark)
├─ lib/
│ └─ utils.ts # the cn function (a tool shared by all components)
└─ components/
└─ ui/ # ② primitive components
├─ button.tsx
├─ badge.tsx
├─ input.tsx
├─ card.tsx
└─ alert.tsx

Naming also has a consistent set of rules.

  • Tokens: name / name-foreground (the background and the text on top of it)
  • Variants: variant (the look) / size (the size)
  • data-slot="button" and the like: markers for styling and identification

When the rules are set, you can tell the role just by looking at the code.

Documentation​

As a design system grows, you want a place where you can list "what components exist and what variants they have". A representative tool is Storybook.

// button.stories.tsx (a sketch)
export const AllVariants = () => (
<div className="flex gap-2">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
</div>
)

With such a catalog, you can talk with designers and other developers "while looking at the same thing", and you prevent reinventing the wheel (each person building a similar button).

Operating and extending it as a team​

  • New colors go into tokens first: instead of writing bg-green-500 directly in a component, define a --success token and then use it (the exercise in the hands-on practice chapter was this)
  • New looks go into a cva variant: when extending an existing component, just add one line to variants
  • Accessibility from the start: do not put off focus-visible, role, and aria-*
  • Confirm the impact range of token changes: that one change ripples across the whole is an advantage, but before you change it, look at which components reference that token

Start small and grow the design system​

You do not need to build a perfect design system from the start. Like this book, start from a single Button and add the components you need with the same pattern, and it naturally grows into a consistent system.

Button only → + Input / Badge → + Card / Alert → forms and dialogs…
(start small) (expand gradually)

The next steps​

After finishing this book, you can broaden your learning in directions like the following.

  • shadcn/ui's complex components: Dialog, DropdownMenu, Form, and others that need integration with Radix UI
  • Implementing dark mode: switch the .dark class with next-themes and the like
  • Integration with design tools: align Figma's design tokens with the code's tokens
  • Forms: validation with React Hook Form + Zod

All of them lie on the extension of "tokens, cva, cn, composition, and types" you learned in this book.

Book summary​

Well done. Through this book, you learned the following.

  1. Design tokens: manage color and spacing by meaning (the design tokens chapter)
  2. cva: manage variants declaratively (the cva chapter)
  3. cn: merge classes safely with clsx + tailwind-merge (the cn chapter)
  4. ref: access the DOM in React 19 (the ref chapter)
  5. Slot: compose elements with asChild (the Slot chapter)
  6. TypeScript: type-safe components (the TypeScript chapter)

And finally, you saw that they connect into a single design system.

Design reusable components yourself, following design system best practices (design tokens, declarative variants, safe customization, composition, type safety).

This was the goal set in the introduction. From the small entry point of Button, you should have reached its full picture. Please make use of it in your real projects.