learn.colinkim.dev

Building a small design system

Learn how tokens, layers, components, variants, states, and utilities work together in plain CSS.

A design system is a set of reusable decisions. It does not need to be large. Even a small site benefits from consistent tokens, components, and states.

Start with layers

@layer reset, tokens, base, components, utilities;

This gives each kind of CSS a place.

Tokens

@layer tokens {
  :root {
    --font-body: system-ui, sans-serif;
    --color-page: oklch(99% 0.01 250);
    --color-text: oklch(22% 0.02 250);
    --color-muted: oklch(45% 0.03 250);
    --color-border: oklch(88% 0.02 250);
    --color-accent: oklch(56% 0.18 255);
    --radius-sm: 0.375rem;
    --radius-md: 0.5rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-6: 1.5rem;
  }
}

Tokens should describe design choices you expect to reuse or vary.

Base styles

@layer base {
  body {
    margin: 0;
    font-family: var(--font-body);
    background: var(--color-page);
    color: var(--color-text);
    line-height: 1.6;
  }

  button,
  input,
  textarea,
  select {
    font: inherit;
  }
}

Base styles make raw HTML usable.

Component: button

@layer components {
  .button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-block-size: 2.75rem;
    padding-inline: var(--space-4);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--button-bg, var(--color-accent));
    color: var(--button-text, white);
    text-decoration: none;
    transition:
      background-color 160ms ease,
      translate 160ms ease;
  }

  .button:hover {
    background: color-mix(in oklab, var(--button-bg, var(--color-accent)), black 10%);
  }

  .button:active {
    translate: 0 1px;
  }

  .button[data-variant="secondary"] {
    --button-bg: transparent;
    --button-text: var(--color-text);
    border-color: var(--color-border);
  }
}

Variants change local variables instead of duplicating the component.

Component: card

@layer components {
  .card {
    container: card / inline-size;
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-page);
  }

  .card > * {
    margin-block: 0;
  }

  .card > * + * {
    margin-block-start: var(--space-3);
  }

  @container card (width >= 28rem) {
    .card[data-layout="media"] {
      display: grid;
      grid-template-columns: 8rem 1fr;
      gap: var(--space-4);
    }
  }
}

Utilities

Use utilities for small, stable exceptions:

@layer utilities {
  .cluster {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    align-items: center;
  }

  .visually-hidden {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

Utilities should be boring and predictable.

What to carry forward

  • a design system is reusable decisions, not just a component library
  • layers keep resets, tokens, base styles, components, and utilities in order
  • tokens should be semantic and reusable
  • component variants can change local custom properties
  • utilities are best for small repeated layout or accessibility patterns
  • plain CSS can support component-oriented architecture without Sass or a framework

The final lesson asks you to style a semantic multi-page site using the whole course.

Progress

Quick checks

No quick checks in this lesson.

Mark lesson manually or answer quick checks to track progress.