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 learn the perspective of bringing them into "a single design system". Let's 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 "Button packs in the elements of a design system". 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 "pillars of good design" 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 foundation of everything. 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. This is the power of putting tokens at the center.

2. Common patterns

Every component uses the same way of building: cn + ...props as the foundation, 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

Here are the points for using and growing a design system 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-*
  • Be aware of the impact range of token changes: that one change ripples across the whole is an advantage, but when you change it, confirm the impact range

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.

You can design reusable components in line with design-system best practices.

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.