DEV Community

Thesius Code
Thesius Code

Posted on • Originally published at datanest-stores.pages.dev

Tailwind Component Library

Tailwind Component Library

A collection of 80+ production-ready, accessible UI components built with Tailwind CSS and React. Every component follows WAI-ARIA authoring practices, supports keyboard navigation, includes dark mode variants, and is fully typed with TypeScript. Covers the full spectrum of application UI: form controls, data tables with sorting and pagination, modal dialogs, navigation patterns, card layouts, data visualization widgets, and feedback components. Copy-paste into your project and customize with Tailwind's utility classes — no component library dependency, no CSS-in-JS runtime, just clean markup you own.

Key Features

  • 80+ Components — Forms, tables, modals, navigation, cards, alerts, badges, avatars, tooltips, and data visualization
  • Fully Accessible — WAI-ARIA compliant with proper role, aria-* attributes, focus management, and keyboard navigation
  • Dark Mode Built In — Every component includes dark: variants that respect the system prefers-color-scheme
  • TypeScript Strict — Complete prop types with discriminated unions, generic components, and as prop polymorphism
  • Responsive by Default — Mobile-first design with breakpoint-aware layouts; tested at 320px, 768px, and 1280px
  • No Dependencies — Pure Tailwind CSS utility classes + React; no Radix, Headless UI, or runtime CSS required
  • Composable Architecture — Compound component patterns (e.g., <Table>, <Table.Header>, <Table.Row>) for maximum flexibility
  • Copy-Paste Ready — Each component is a single file; no build step, no package to install, just copy and use

Quick Start

  1. Ensure Tailwind CSS is installed in your project:
npm install -D tailwindcss @tailwindcss/forms @tailwindcss/typography
Enter fullscreen mode Exit fullscreen mode
  1. Copy components from components/ into your project's component directory.

  2. Import and use immediately:

import { Button } from '@/components/ui/Button';
import { Modal } from '@/components/ui/Modal';
import { DataTable } from '@/components/ui/DataTable';

export function UserManagement() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <Button variant="primary" size="md" onClick={() => setIsOpen(true)}>
        Add User
      </Button>
      <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Add New User">
        <UserForm onSubmit={(data) => { /* handle */ }} />
      </Modal>
      <DataTable
        columns={columns}
        data={users}
        sortable
        paginated
        pageSize={10}
      />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Architecture / How It Works

tailwind-component-library/
├── components/
│   ├── forms/           # Button, Input, Select, Checkbox, RadioGroup, Toggle, Textarea, FileUpload
│   ├── data-display/    # DataTable, Card, Badge, Avatar, Stat, Timeline
│   ├── feedback/        # Alert, Toast, Progress, Skeleton, EmptyState
│   ├── navigation/      # Sidebar, Breadcrumb, Tabs, Pagination, CommandPalette
│   ├── overlays/        # Modal, Dropdown, Tooltip, Popover
│   └── layout/          # Container, Grid, Divider
└── theme/               # Extended tailwind.config.ts with design tokens
Enter fullscreen mode Exit fullscreen mode

Usage Examples

Button Component with Variants

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
}

const styles = {
  primary: 'bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-500',
  secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-100',
  ghost: 'text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800',
  danger: 'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500',
} as const;

const sizes = { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2 text-sm', lg: 'px-6 py-3 text-base' } as const;

export function Button({ variant = 'primary', size = 'md', loading, children, ...props }: ButtonProps) {
  return (
    <button className={`inline-flex items-center justify-center rounded-lg font-medium transition-colors
      focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
      disabled:pointer-events-none disabled:opacity-50 ${styles[variant]} ${sizes[size]}`}
      disabled={loading || props.disabled} {...props}>
      {loading && <Spinner className="mr-2 h-4 w-4" />}
      {children}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Accessible Modal

export function Modal({ isOpen, onClose, title, children }: ModalProps) {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
    if (isOpen) document.addEventListener('keydown', handler);
    return () => document.removeEventListener('keydown', handler);
  }, [isOpen, onClose]);

  if (!isOpen) return null;
  return (
    <div ref={ref} className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
      onClick={(e) => e.target === ref.current && onClose()} role="dialog" aria-modal="true" aria-labelledby="modal-title">
      <div className="w-full max-w-lg rounded-xl bg-white p-6 shadow-xl dark:bg-gray-900">
        <h2 id="modal-title" className="text-lg font-semibold">{title}</h2>
        <div className="mt-4">{children}</div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Configuration

Extended Tailwind Theme

import type { Config } from 'tailwindcss';

export default {
  darkMode: 'class',
  content: ['./src/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: { brand: { 50: '#eff6ff', 500: '#3b82f6', 600: '#2563eb', 700: '#1d4ed8' } },
      animation: { 'slide-in': 'slideIn 0.2s ease-out', 'fade-in': 'fadeIn 0.15s ease-out' },
      keyframes: {
        slideIn: { from: { transform: 'translateY(-8px)', opacity: '0' }, to: { transform: 'translateY(0)', opacity: '1' } },
        fadeIn: { from: { opacity: '0' }, to: { opacity: '1' } },
      },
    },
  },
  plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')],
} satisfies Config;
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use focus-visible not focusfocus-visible only shows focus rings on keyboard navigation, not mouse clicks
  • Always add dark: variants — even if you don't support dark mode yet; it's much harder to retrofit later
  • Prefer aria-labelledby over aria-label — visible labels are better for accessibility and maintenance
  • Use compound components for complex UI<Tabs><Tabs.List><Tabs.Tab> is more flexible than a single <Tabs items={[]}>
  • Set disabled:pointer-events-none — prevents click events on disabled buttons that only change opacity
  • Use ring utilities for focus indicatorsfocus-visible:ring-2 focus-visible:ring-offset-2 gives consistent, visible focus

Troubleshooting

Issue Cause Fix
Tailwind classes not applying Component files not in content array Add component directory to content in tailwind.config.ts
Dark mode doesn't toggle Using media strategy instead of class Set darkMode: 'class' and toggle .dark class on <html>
Modal doesn't close on backdrop click Click event targets child elements Compare e.target === overlayRef.current to detect backdrop clicks
Dropdown menu clips at viewport edge Absolute positioning without boundary check Use position: fixed with viewport boundary calculations

This is 1 of 11 resources in the Frontend Developer Pro toolkit. Get the complete [Tailwind Component Library] with all files, templates, and documentation for $39.

Get the Full Kit →

Or grab the entire Frontend Developer Pro bundle (11 products) for $129 — save 30%.

Get the Complete Bundle →


Related Articles

Top comments (0)