---
title: "27 — Design Systems — Deciding Once, Not Every Time"
uid: design-systems
tags: ["design-tokens", "components", "ui", "shadcn", "roadmap:frontend", "design-systems"]
excerpt: "A design system isn't a component library — it's a single source of truth that encodes design decisions once, so every team composes from the same tokens, components, and guidelines."
date: 2026-08-12T18:35:07+0000
source: https://www.aveshina.my.id/en/blog/design-systems
---

"A component library" was how I confused design systems, which made the "system" part invisible. The model that separated them: **a design system is a single source of truth that encodes design decisions once, so every team composes from the same foundations instead of re-deciding them.** [1]

The confusion cost me. On every new screen I'd re-pick a blue, re-argue the button radius, re-guess the gap between a label and its input. Three teams working in parallel produced three slightly different buttons and four slightly different "brand blues." None of it was wrong individually; the whole was incoherent. The fix wasn't more discipline — it was moving the decision up front, into shared foundations, and never making it again [2].

Here's the whole model unfolded into its three layers, before I walk through them one at a time:

```figure
<svg viewBox="0 0 740 300" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="The three layers of a design system. A single source-of-truth box on the left feeds three stacked layers on the right. Top layer: design tokens (the variables) — color, spacing, type scale. Middle layer: component library (the built UI parts) — buttons, inputs, cards. Bottom layer: guidelines and docs (how to use them). All three flow into a consistent set of UI on the far right.">
  <defs>
    <marker id="dsarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>

  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- source of truth -->
    <rect x="30" y="100" width="170" height="100" rx="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="115" y="135" font-size="13" font-weight="700" fill="#1e1b4b" text-anchor="middle">Single source</text>
    <text x="115" y="153" font-size="13" font-weight="700" fill="#1e1b4b" text-anchor="middle">of truth</text>
    <text x="115" y="178" font-size="10.5" fill="#475569" text-anchor="middle">decisions encoded</text>
    <text x="115" y="192" font-size="10.5" fill="#475569" text-anchor="middle">once</text>

    <!-- three layers -->
    <rect x="300" y="20" width="280" height="70" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="440" y="46" font-size="13" font-weight="700" fill="#422006" text-anchor="middle">① Design tokens — the variables</text>
    <text x="440" y="66" font-size="10.5" fill="#475569" text-anchor="middle">color · spacing · type scale · radius</text>
    <text x="440" y="80" font-size="10.5" fill="#475569" text-anchor="middle">the raw decisions, named</text>

    <rect x="300" y="115" width="280" height="70" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="440" y="141" font-size="13" font-weight="700" fill="#500724" text-anchor="middle">② Component library — the parts</text>
    <text x="440" y="161" font-size="10.5" fill="#475569" text-anchor="middle">buttons · inputs · cards · modals</text>
    <text x="440" y="175" font-size="10.5" fill="#475569" text-anchor="middle">built UI, consuming the tokens</text>

    <rect x="300" y="210" width="280" height="70" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="440" y="236" font-size="13" font-weight="700" fill="#052e16" text-anchor="middle">③ Guidelines &amp; docs — how to use</text>
    <text x="440" y="256" font-size="10.5" fill="#475569" text-anchor="middle">when to use what, do's and don'ts</text>
    <text x="440" y="270" font-size="10.5" fill="#475569" text-anchor="middle">the words that keep it consistent</text>

    <!-- connectors from source to each layer -->
    <path d="M200,135 C250,135 260,55 298,55" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#dsarrow)"/>
    <path d="M200,150 L298,150" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#dsarrow)"/>
    <path d="M200,165 C250,165 260,245 298,245" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#dsarrow)"/>

    <!-- consistent output -->
    <text x="680" y="135" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">consistent</text>
    <text x="680" y="151" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">UI across</text>
    <text x="680" y="167" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">every team</text>
    <path d="M582,245 C620,245 640,160 670,150" fill="none" stroke="#64748b" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#dsarrow)"/>
    <path d="M582,150 C620,150 640,150 668,150" fill="none" stroke="#64748b" stroke-width="1.2" stroke-dasharray="4 3"/>
  </g>
</svg>
```

Three layers, one source of truth. The order matters — tokens feed components, components feed guidelines, and together they're what stops a fifth "brand blue" from ever being invented.

## Layer 1 — Design tokens: the variables

The first layer is the part I'd been missing entirely. **Design tokens** are the raw design decisions, captured as named values: the brand color, the spacing unit, the type scale (the set of font sizes), the corner radius [3]. Instead of scattering #3b82f6 and 16px and 0.5rem across a thousand files, you name them once — --color-brand, --space-4, --radius-md — and everything else references the name.

This is the layer that makes "one place to change brand blue" literal. When the brand color shifts, you change one token; every component that consumed it follows. That's the whole point of a token: a decision with a name, referenced instead of repeated. A token isn't a CSS variable specifically — it's the *concept* of a named decision; CSS custom properties, Tailwind theme config, and a Figma variable are all ways the same token gets materialized in different tools.

## Layer 2 — The component library: the built parts

The second layer is the one I used to mistake for the whole system. **The component library** is the set of pre-built UI parts — buttons, inputs, cards, modals, dialogs — constructed *from the tokens* [1][2]. A button doesn't hardcode #3b82f6; it applies --color-brand. So when the token changes, the button changes with it, automatically.

The distinction I had to internalize: a component library alone is *not* a design system. It's one layer. A drawer full of buttons that each hardcode their own blue is a component library with no system underneath — change "brand blue" and you're back to grepping (searching) the codebase by hand. The tokens are what turn a pile of components into a *system*.

This is exactly the split I see in this very portfolio. The project uses **shadcn/ui** for components and **Tailwind** for tokens [4]. Tailwind's theme config is the token layer — named colors, spacing, radii, type scale — and shadcn/ui components are built to consume those tokens rather than fight them. I don't hand-tune a button's color or radius; I reach for the component, which already references the theme. That pairing is a design system in miniature: tokens (Tailwind) feeding a component library (shadcn/ui), even without a formal docs site.

## Layer 3 — Guidelines and docs: how to use them

The third layer is the one engineers most often skip, and it's why "we have a component library" still produces inconsistent UI. **Guidelines and documentation** are the rules of composition: when to use a primary button versus a secondary, when a modal is appropriate and when it isn't, what the spacing rhythm should be between elements [2].

Components and tokens give teams the *ingredients*. Guidelines are what stop two engineers from assembling the same ingredients into two different-looking screens. A button component exists, but without a rule like "one primary action per surface," every surface ends up with three competing CTAs (calls to action). The docs are where "consistent at scale" actually gets enforced — not by the code, but by shared agreement on how the code is used.

This is also the layer that explains those sub-nodes on the roadmap I started from — Astro, Next.js, Vuepress, Eleventy, Nuxt.js [5]. Those aren't design-system concepts; they're the *site-builders* teams reach for to publish this documentation layer. A docs site is just the rendered, browsable form of the guidelines — the place where tokens, components, and usage rules are shown side by side. Next.js or Nuxt.js might host it as an interactive app; Eleventy or Vuepress render it as fast static markdown; Astro can do either with partial hydration (shipping JavaScript only for the interactive bits). The framework is where the docs *live*, not what makes the system a system.

## Why this is worth the overhead

The honest trade-off: a design system is real overhead. You pay for it in upfront design work, a docs site, and a maintenance owner. For a single screen built once, it's overkill.

The payoff is **consistency at scale and one place to change anything** [1][2]. Once more than one team — or more than one surface — ships UI from the same foundations, the math flips:

- **Consistency.** Every screen composes from the same tokens and components, so the product looks like one thing, not a collage.
- **One place to change.** Rebrand, accessibility pass, dark mode — each is a change to the token or component layer, not a thousand-file find-and-replace.
- **Speed.** Teams stop re-deciding spacing and color and start at "compose from the parts," which is where the actual product work begins.

I felt this directly on the portfolio. Once Tailwind tokens and shadcn/ui components were in place, adding a new page stopped being a design exercise and became assembly. The decisions had already been made — I just compose from them.

## How I use this

The habit these notes left me with is a single pause before I write a style: *has this decision already been made?* If there's a token for the color, the spacing, or the radius, I use it; if there's a component for the part, I reach for it; if neither exists, I ask whether it should — because hardcoding it now means re-deciding it later, somewhere else. A design system isn't a tool you install. It's the discipline of deciding once and referencing forever, and the three layers are just where those once-made decisions live.

## References

[1] Figma, "Design systems 101: What is a design system?," Figma Blog, 2024. [Online]. Available: [https://www.figma.com/blog/design-systems-101-what-is-a-design-system/](https://www.figma.com/blog/design-systems-101-what-is-a-design-system/)

[2] Nielsen Norman Group, "Design systems 101," NN/g, 2024. [Online]. Available: [https://www.nngroup.com/articles/design-systems-101/](https://www.nngroup.com/articles/design-systems-101/)

[3] W3C Community Group, "Design Tokens Format Module," 2024. [Online]. Available: [https://www.w3.org/community/design-tokens/](https://www.w3.org/community/design-tokens/)

[4] "ave-space — project instructions," CLAUDE.md, 2024. [Online]. Available: [https://github.com/aveshina/ave-space](https://github.com/aveshina/ave-space)

[5] roadmap.sh, "Design Systems," 2024. [Online]. Available: [https://roadmap.sh/frontend/design-systems](https://roadmap.sh/frontend/design-systems)

```quiz
Q: A component library and a design system are the same thing.
- Yes — they're interchangeable terms
- No — the component library is one layer; tokens and guidelines are the other two
correct: 1
explain: A component library is the built UI parts. A design system is tokens + components + guidelines together. The library alone is one layer, not the whole system.

Q: What is a design token?
- A named design decision (color, spacing, radius) referenced instead of repeated
- A JavaScript framework for building components
- A type of CSS preprocessor
correct: 0
explain: A token is a decision with a name — brand color, spacing unit, corner radius. Everything references the name, so changing the token updates every consumer.

Q: You need to change the brand color across the whole app. With a token-based system you…
- grep the codebase and replace every hex value by hand
- change the single color token; every component that consumes it follows
correct: 1
explain: That's the whole point of a token: one named decision, referenced everywhere. Change the source and every consumer updates automatically.

Q: On the roadmap, Next.js, Astro, Vuepress, Eleventy, and Nuxt.js appear under Design Systems. What's their actual role?
- They are design-system methodologies
- They are site-builders commonly used to publish the documentation layer of a design system
correct: 1
explain: Those frameworks render and host the docs site — the browsable form of the guidelines. They're where the documentation lives, not what defines the system itself.

Q: Why do teams still get inconsistent UI after adopting a component library?
- The library was buggy
- They skipped the guidelines layer, so engineers compose the same parts into different-looking screens
correct: 1
explain: Components and tokens are ingredients. Without guidelines — when to use what — two engineers assemble the same parts differently. The docs layer is what enforces consistency at scale.
```
