Skip to main content

ADR-005: Tailwind CSS with Shadcn/ui

Status

Accepted

Context

The application needs a styling approach that:

  1. Supports theming: Multi-tenant branding
  2. Provides components: Pre-built, accessible UI primitives
  3. Minimizes bundle size: No unused CSS
  4. Developer experience: Fast iteration, good DX

Options considered:

  • CSS Modules: Scoped styles but no design system
  • Styled Components: Runtime overhead, CSS-in-JS complexity
  • Tailwind + Headless UI: Utility-first with accessibility
  • Tailwind + Shadcn/ui: Copy-paste components, full control

Decision

We will use Tailwind CSS with Shadcn/ui components:

  1. Tailwind CSS: Utility-first styling, purged for production
  2. Shadcn/ui: Copy-paste components (not npm dependency)
  3. CSS Variables: Theming via custom properties
  4. cn() utility: Conditional class merging
// src/lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

Component Pattern

// src/components/ui/button.tsx
import { cn } from '@/lib/utils';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'destructive' | 'outline';
}

export function Button({ className, variant = 'default', ...props }: ButtonProps) {
return (
<button
className={cn(
'inline-flex items-center justify-center rounded-md',
variant === 'default' && 'bg-primary text-primary-foreground',
variant === 'destructive' && 'bg-destructive text-destructive-foreground',
className
)}
{...props}
/>
);
}

Theming

CSS variables enable runtime theming:

/* globals.css */
:root {
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
}

[data-theme="dark"] {
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
}

Consequences

Positive

  • Full control: Components are in your codebase
  • No runtime: Styles compiled at build time
  • Type-safe: TypeScript props and variants
  • Accessible: Radix UI primitives

Negative

  • Verbose HTML: Many utility classes
  • Learning curve: Tailwind class names
  • Manual updates: Shadcn components don't auto-update

Neutral

  • Bundle size: Purged CSS is minimal
  • Design consistency: Must maintain your own design tokens