Building a Design System with Tailwind and Radix
Back to Blog

Building a Design System with Tailwind and Radix

March 21, 20262 min read55 views

A design system isn't just components—it's constraints that enable consistency and speed. Combining Tailwind's utility-first approach with Radix's accessible primitives creates a powerful foundation.

Design Tokens: The Foundation

// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#faf5ff',
          500: '#8b5cf6',
          900: '#4c1d95',
        },
        semantic: {
          success: '#22c55e',
          warning: '#f59e0b',
          error: '#ef4444',
        },
      },
      spacing: {
        'component-xs': '0.25rem',
        'component-sm': '0.5rem',
        'component-md': '1rem',
        'component-lg': '1.5rem',
      },
      borderRadius: {
        'component': '0.5rem',
      },
    },
  },
}

Component Architecture with Radix

// components/ui/button.tsx
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-component font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        primary: 'bg-brand-500 text-white hover:bg-brand-600',
        secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
        ghost: 'hover:bg-gray-100',
        destructive: 'bg-semantic-error text-white hover:bg-red-600',
      },
      size: {
        sm: 'h-8 px-3 text-sm',
        md: 'h-10 px-4',
        lg: 'h-12 px-6 text-lg',
      },
    },
    defaultVariants: {
      variant: 'primary',
      size: 'md',
    },
  }
)

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

const Button = React.forwardRef(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button'
    return (
      
    )
  }
)

export { Button, buttonVariants }

Accessible Primitives with Radix

// Dialog with full accessibility
import * as Dialog from '@radix-ui/react-dialog'

export function Modal({ trigger, title, children }) {
  return (
    
      {trigger}
      
        
        
          {title}
          {children}
          
            
          
        
      
    
  )
}

Documentation with Storybook

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './button'

const meta: Meta = {
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'ghost', 'destructive'],
    },
    size: {
      control: 'select',
      options: ['sm', 'md', 'lg'],
    },
  },
}

export default meta
type Story = StoryObj

export const Primary: Story = {
  args: { children: 'Primary Button', variant: 'primary' },
}

export const AllVariants: Story = {
  render: () => (
    
), }

Key Takeaways

Design tokens establish the visual language. Colors, spacing, and typography should be systematic.

CVA manages variant complexity. Class Variance Authority makes multi-variant components maintainable.

Radix provides accessibility for free. Focus management, keyboard navigation, and ARIA are built in.

Documentation is part of the system. Storybook ensures components are discoverable and usable.

Share this article