Building a shadcn/ui Style Component Library for Korean Government Design System

Published: (December 9, 2025 at 08:16 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

shadcn/ui changed how we think about component libraries. Instead of installing a package, you copy the source code into your project. You own it. You customize it.

I built HANUI using the same approach—for the Korean Government Design System (KRDS).

Architecture

┌─────────────────────────────────────────────┐
│  CLI (npx hanui add button)                 │
└─────────────────┬───────────────────────────┘


┌─────────────────────────────────────────────┐
│  Component Registry (components.json)       │
│  - Component definitions                    │
│  - Dependencies                             │
│  - File paths                               │
└─────────────────┬───────────────────────────┘


┌─────────────────────────────────────────────┐
│  Your Project                               │
│  components/ui/button.tsx  ← copied here    │
└─────────────────────────────────────────────┘

CLI

The CLI is the entry point. It handles:

  • init – Sets up your project
  • add – Copies components to your project
  • diff – Shows what changed (coming soon)
npx hanui init
# Creates:
# - components.json (config)
# - Copies variables.css (design tokens)
# - Updates tailwind.config.ts (adds preset)
npx hanui add button modal input
# Copies:
# - components/ui/button.tsx
# - components/ui/modal.tsx
# - components/ui/input.tsx
# - Any dependencies (like cn utility)

Component Structure

Each component follows a consistent pattern:

// button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
  // Base styles
  'inline-flex items-center justify-center rounded font-medium transition-colors',
  {
    variants: {
      variant: {
        primary: 'bg-krds-primary-base text-white hover:bg-krds-primary-60',
        secondary: 'bg-krds-gray-10 text-krds-gray-90 hover:bg-krds-gray-20',
        tertiary: 'bg-transparent text-krds-primary-base hover:bg-krds-primary-5',
        danger: 'bg-krds-danger-base text-white hover:bg-krds-danger-60',
      },
      size: {
        sm: 'h-8 px-3 text-krds-label-sm',
        md: 'h-10 px-4 text-krds-label-md',
        lg: 'h-12 px-6 text-krds-label-lg',
      },
    },
    defaultVariants: {
      variant: 'primary',
      size: 'md',
    },
  }
);

interface ButtonProps
  extends React.ButtonHTMLAttributes,
    VariantProps {
  loading?: boolean;
}

export function Button({
  className,
  variant,
  size,
  loading,
  children,
  ...props
}: ButtonProps) {
  return (
    
      {loading && }
      {children}
    
  );
}

Key patterns

  • cva for type‑safe variants
  • cn utility for merging class names
  • KRDS tokens in Tailwind classes (krds-primary-base, krds-label-md)
  • Built‑in accessibility (aria-busy)

Design Token System

KRDS defines specific color, typography, and spacing values. HANUI implements them as CSS variables plus a Tailwind preset.

CSS Variables (variables.css)

:root {
  /* Colors */
  --krds-color-light-primary-50: #256ef4;
  --krds-color-light-primary-60: #0b50d0;

  /* Semantic tokens */
  --krds-primary-base: var(--krds-color-light-primary-50);
  --krds-primary-text: var(--krds-color-light-primary-60);

  /* Typography */
  --krds-fs-body-md: 17px;
  --krds-fs-heading-lg: 24px;
}

.dark {
  /* Dark mode overrides */
  --krds-primary-base: var(--krds-color-light-primary-50);
  --krds-primary-text: var(--krds-color-light-primary-40);
}

Tailwind Preset (tailwind.preset.ts)

const hanuiPreset = {
  theme: {
    extend: {
      colors: {
        'krds-primary': {
          DEFAULT: 'var(--krds-primary-base)',
          base: 'var(--krds-primary-base)',
          text: 'var(--krds-primary-text)',
          50: 'var(--krds-color-light-primary-50)',
          60: 'var(--krds-color-light-primary-60)',
        },
      },
      fontSize: {
        'krds-body-md': ['17px', { lineHeight: '150%' }],
        'krds-heading-lg': [
          'var(--krds-fs-heading-lg)',
          { lineHeight: '150%', fontWeight: '700' },
        ],
      },
    },
  },
};

Now you can use Tailwind classes that map directly to KRDS values:


KRDS‑compliant text

Radix UI Integration

Complex components (Modal, Select, Tabs) are built on Radix UI primitives.

Why Radix?

  • Accessibility – WAI‑ARIA compliant out of the box
  • Unstyled – Allows us to apply KRDS styling
  • Composable – Flexible compound component API
// modal.tsx
import * as Dialog from '@radix-ui/react-dialog';

export function Modal({ children, ...props }) {
  return (
    
      
        
        
          {children}
        
      
    
  );
}

export const ModalTitle = Dialog.Title;
export const ModalDescription = Dialog.Description;
export const ModalClose = Dialog.Close;

Resulting features:

  • Focus trap
  • Escape‑key handling
  • Screen‑reader announcements
  • Scroll lock

All provided for free; we only add KRDS styling.

Why This Approach?

  1. Full Control – You own the code. Need to change how a button looks? Edit button.tsx directly.
  2. No Runtime Dependency – Components are copied, not imported from node_modules. Your bundle includes only what you use.
  3. Tailwind Native – Everything uses Tailwind, matching modern React projects.
  4. Type Safety – TypeScript + cva gives autocomplete for variants and compile‑time checks.
  5. Accessible by Default – Radix primitives + ARIA attributes provide WCAG compliance out of the box.

Trade‑offs

  • No automatic updates – You must manually update components when upstream changes.
  • Larger initial setup – More files are added to your project.
  • Learning curve – Understanding the component structure and the CLI takes some time.

For KRDS projects where customization is inevitable, these trade‑offs are worthwhile.

Get Started

npx hanui init
npx hanui add button input modal select tabs

GitHub:
Documentation:

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...

Introduction to React

What is React? React is a JavaScript library for building user interfaces. It was developed by Facebook Meta and is now open‑source, widely used in web develop...