Building a Design System with Tailwind CSS

Published: (December 18, 2025 at 12:49 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

Overview

“Tailwind is utility classes. Does it work for design systems?”
Answer: Yes. Actually, it works great.

The core of a design system is consistency—colors, spacing, typography, and other tokens following defined rules. Tailwind enforces that consistency:

  • p-4 is always 16 px.
  • text-sm is always 14 px.

No developer variance.

Basic Structure

Design Tokens (CSS Variables)

Tailwind Config (tailwind.config)

Utility Classes (bg-primary, text-sm)

Components

1. Color Tokens

// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#eff6ff',
          100: '#dbeafe',
          500: '#3b82f6', // base
          600: '#2563eb', // hover
          700: '#1d4ed8', // active
        },
        gray: {
          50: '#f9fafb',
          100: '#f3f4f6',
          500: '#6b7280',
          900: '#111827',
        },
      },
    },
  },
};

Now you can use bg-primary-500, text-gray-900.

CSS Variables Are Better

/* globals.css */
:root {
  --color-primary-500: #3b82f6;
  --color-primary-600: #2563eb;
}

.dark {
  --color-primary-500: #60a5fa;
  --color-primary-600: #3b82f6;
}
// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        primary: {
          500: 'var(--color-primary-500)',
          600: 'var(--color-primary-600)',
        },
      },
    },
  },
};

Dark‑mode switching now happens automatically.

2. Typography

// tailwind.config.ts
export default {
  theme: {
    extend: {
      fontSize: {
        'display-lg': ['48px', { lineHeight: '1.2', fontWeight: '700' }],
        'display-md': ['36px', { lineHeight: '1.2', fontWeight: '700' }],
        'heading-lg': ['24px', { lineHeight: '1.4', fontWeight: '600' }],
        'heading-md': ['20px', { lineHeight: '1.4', fontWeight: '600' }],
        'body-lg':    ['18px', { lineHeight: '1.6' }],
        'body-md':    ['16px', { lineHeight: '1.6' }],
        'body-sm':    ['14px', { lineHeight: '1.6' }],
      },
    },
  },
};
// Example usage in a component
<h1 className="text-display-lg">Large Title</h1>
<h2 className="text-display-md">Medium Title</h2>
<p className="text-body-md">Body text</p>

Responsive Typography

CSS variables make responsiveness easy:

:root {
  --fs-display-lg: 36px;
}

@media (min-width: 1024px) {
  :root {
    --fs-display-lg: 48px;
  }
}
// tailwind.config.ts
export default {
  theme: {
    extend: {
      fontSize: {
        'display-lg': ['var(--fs-display-lg)', { lineHeight: '1.2' }],
      },
    },
  },
};

Now a single text-display-lg class handles both mobile and desktop sizes.

3. Spacing

Tailwind’s default spacing works for most cases, but you can define a custom scale:

// tailwind.config.ts
export default {
  theme: {
    extend: {
      spacing: {
        xs: '4px',
        sm: '8px',
        md: '16px',
        lg: '24px',
        xl: '32px',
        '2xl': '48px',
        '3xl': '64px',
      },
    },
  },
};
{/* padding: 16px, margin‑bottom: 24px */}

In practice, Tailwind’s built‑in spacing (p-4, mb-6, etc.)—an 8 px grid—is often sufficient.

4. Building Components

With tokens set up, you can create reusable components.

// Button.tsx
const variants = {
  primary:   'bg-primary-500 text-white hover:bg-primary-600',
  secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
  outline:   'border border-primary-500 text-primary-500 hover:bg-primary-50',
};

const sizes = {
  sm: 'h-8  px-3 text-body-sm',
  md: 'h-10 px-4 text-body-md',
  lg: 'h-12 px-6 text-body-lg',
};

export function Button({ variant = 'primary', size = 'md', ...props }) {
  return (
    // component implementation here
  );
}

Type‑Safe with cva

import { cva, type VariantProps } from 'class-variance-authority';

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md font-medium',
  {
    variants: {
      variant: {
        primary:   'bg-primary-500 text-white hover:bg-primary-600',
        secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
      },
      size: {
        sm: 'h-8  px-3 text-body-sm',
        md: 'h-10 px-4 text-body-md',
        lg: 'h-12 px-6 text-body-lg',
      },
    },
    defaultVariants: {
      variant: 'primary',
      size:    'md',
    },
  }
);

type ButtonProps = VariantProps<typeof buttonVariants>;
// { variant?: 'primary' | 'secondary'; size?: 'sm' | 'md' | 'lg' }

Autocomplete and type‑checking are now built‑in.

5. Distribute as a Preset

Create a reusable preset for multiple projects or a team:

// my-preset.ts
const myPreset = {
  theme: {
    extend: {
      colors:   { /* … */ },
      fontSize: { /* … */ },
    },
  },
};

export default myPreset;
// tailwind.config.ts
import myPreset from './my-preset';

export default {
  presets: [myPreset],
  // project‑specific additions go here
};

Design System Checklist

  • Color tokens (primary, secondary, gray, danger, success)
  • Typography scale (display, heading, body, label)
  • Spacing system (8 px grid recommended)
  • Border radius
  • Shadows
  • Base components (Button, Input, Card, etc.)

For Government Projects

When working on Korean government projects you need to follow KRDS (Korean Design System).
All color codes, font sizes, spacing, etc., are strictly defined, and setting them up manually can take a full day.

HANUI – KRDS preset for TailwindCSS

HANUI provides KRDS tokens pre‑applied as a Tailwind preset, so you can get a compliant UI up and running instantly.

Tailwind configuration

// tailwind.config.ts
import hanuiPreset from '@hanui/react/tailwind.preset';

export default {
  presets: [hanuiPreset],
};

Using KRDS classes in your components

// Example component
<div className="text-krds-body">
  KRDS styled text
</div>

<button className="krds-button">
  KRDS button
</button>

What You Get

  • Colors – fully KRDS‑compliant palette
  • Typography – correct font families, sizes, and line‑heights
  • Responsive design – built‑in breakpoints that follow KRDS guidelines
  • Tech stack – TailwindCSS, React, CSS variables, CVA (class‑variance‑authority)

All of the above lets you focus on building features instead of painstakingly recreating the design system.

Back to Blog

Related posts

Read more »

Introducing Platform Elements

As part of the new product, you can now use a set of prebuilt UI blocks and actions to add functionality directly to your application.Vercel for Platforms of pr...

Please Just Try Htmx

Article URL: http://pleasejusttryhtmx.com/ Comments URL: https://news.ycombinator.com/item?id=46312973 Points: 104 Comments: 107...