Master advanced best practices for building maintainable, scalable, and production-ready applications with Tailwind CSS. Learn code organization strategies, team workflows, design system architecture, accessibility guidelines, and professional development patterns.
Best practices are essential for maintaining Tailwind CSS projects at scale. As your application grows, proper organization, consistent patterns, and team conventions become critical for long-term success.
In this final lesson, you'll learn professional best practices that distinguish production-ready Tailwind implementations from prototype code. You'll discover how to structure code for teams, build scalable design systems, ensure accessibility, and avoid common pitfalls that plague large-scale projects.
Proper code organization is fundamental to maintaining large Tailwind projects. A well-structured project makes it easier to find, update, and refactor code.
Recommended Project Structure:
1src/
2├── styles/
3│ ├── base.css # Base styles and resets
4│ ├── components.css # Component-level @apply rules
5│ ├── utilities.css # Custom utilities
6│ └── main.css # Main entry point
7│
8├── components/
9│ ├── ui/ # Reusable UI components
10│ │ ├── Button/
11│ │ │ ├── Button.tsx
12│ │ │ ├── Button.variants.ts
13│ │ │ ├── Button.styles.ts
14│ │ │ └── index.ts
15│ │ ├── Card/
16│ │ ├── Input/
17│ │ └── index.ts # Export all UI components
18│ │
19│ ├── layout/ # Layout components
20│ │ ├── Header/
21│ │ ├── Footer/
22│ │ └── Sidebar/
23│ │
24│ └── features/ # Feature-specific components
25│ ├── Auth/
26│ └── Dashboard/
27│
28├── lib/
29│ ├── utils/
30│ │ ├── cn.ts # Class name utility
31│ │ └── variants.ts # CVA helpers
32│ │
33│ └── constants/
34│ └── styles.ts # Shared style constants
35│
36└── config/
37 └── tailwind.config.jsBase Styles Organization:
1/* styles/base.css */
2@tailwind base;
3
4@layer base {
5 /* Typography */
6 h1 {
7 @apply text-4xl font-bold tracking-tight;
8 }
9
10 h2 {
11 @apply text-3xl font-semibold tracking-tight;
12 }
13
14 /* Focus states */
15 *:focus-visible {
16 @apply outline-none ring-2 ring-blue-500 ring-offset-2;
17 }
18
19 /* Scrollbar styling */
20 * {
21 scrollbar-width: thin;
22 scrollbar-color: theme('colors.gray.400') theme('colors.gray.100');
23 }
24}
25
26/* styles/components.css */
27@tailwind components;
28
29@layer components {
30 /* Only use @apply for truly reusable components */
31 .btn-base {
32 @apply inline-flex items-center justify-center rounded-lg font-semibold transition-all;
33 @apply focus:outline-none focus:ring-2 focus:ring-offset-2;
34 }
35
36 .card-base {
37 @apply bg-white rounded-xl shadow-md overflow-hidden;
38 }
39
40 .input-base {
41 @apply w-full px-4 py-2 border-2 border-gray-300 rounded-lg;
42 @apply focus:border-blue-500 focus:ring-2 focus:ring-blue-200;
43 @apply transition-colors;
44 }
45}
46
47/* styles/utilities.css */
48@tailwind utilities;
49
50@layer utilities {
51 /* Custom utilities that don't exist in Tailwind */
52 .text-balance {
53 text-wrap: balance;
54 }
55
56 .scrollbar-hide {
57 -ms-overflow-style: none;
58 scrollbar-width: none;
59 }
60
61 .scrollbar-hide::-webkit-scrollbar {
62 display: none;
63 }
64}Component File Organization:
1// components/ui/Button/Button.variants.ts
2import { cva, type VariantProps } from 'class-variance-authority';
3
4export const buttonVariants = cva(
5 'inline-flex items-center justify-center rounded-lg font-semibold transition-all focus:outline-none focus:ring-2 focus:ring-offset-2',
6 {
7 variants: {
8 variant: {
9 primary: 'bg-blue-500 text-white hover:bg-blue-600 focus:ring-blue-500',
10 secondary: 'bg-gray-500 text-white hover:bg-gray-600 focus:ring-gray-500',
11 outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-50 focus:ring-blue-500',
12 ghost: 'text-blue-500 hover:bg-blue-50 focus:ring-blue-500',
13 destructive: 'bg-red-500 text-white hover:bg-red-600 focus:ring-red-500',
14 },
15 size: {
16 sm: 'text-sm px-3 py-1.5',
17 md: 'text-base px-4 py-2',
18 lg: 'text-lg px-6 py-3',
19 },
20 disabled: {
21 true: 'opacity-50 cursor-not-allowed',
22 },
23 },
24 defaultVariants: {
25 variant: 'primary',
26 size: 'md',
27 },
28 }
29);
30
31export type ButtonVariants = VariantProps<typeof buttonVariants>;
32
33// components/ui/Button/Button.tsx
34import { forwardRef } from 'react';
35import { cn } from '@/lib/utils/cn';
36import { buttonVariants, type ButtonVariants } from './Button.variants';
37
38export interface ButtonProps
39 extends React.ButtonHTMLAttributes<HTMLButtonElement>,
40 ButtonVariants {}
41
42export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
43 ({ className, variant, size, disabled, ...props }, ref) => {
44 return (
45 <button
46 ref={ref}
47 className={cn(buttonVariants({ variant, size, disabled }), className)}
48 disabled={disabled}
49 {...props}
50 />
51 );
52 }
53);
54
55Button.displayName = 'Button';
56
57// components/ui/Button/index.ts
58export { Button } from './Button';
59export type { ButtonProps } from './Button';
60export { buttonVariants } from './Button.variants';Utility Functions:
1// lib/utils/cn.ts - Class name utility
2import { clsx, type ClassValue } from 'clsx';
3import { twMerge } from 'tailwind-merge';
4
5export function cn(...inputs: ClassValue[]) {
6 return twMerge(clsx(inputs));
7}
8
9// Usage
10<div className={cn(
11 'base-classes',
12 condition && 'conditional-classes',
13 className // Allow external overrides
14)} />
15
16// lib/constants/styles.ts - Shared constants
17export const FOCUS_RING = 'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2';
18export const TRANSITION_DEFAULT = 'transition-all duration-200';
19export const CARD_SHADOW = 'shadow-md hover:shadow-lg transition-shadow';
20
21// Usage
22const classes = `${FOCUS_RING} ${TRANSITION_DEFAULT}`;Building a design system with Tailwind ensures consistency across your application and enables teams to work efficiently with shared components and patterns.
Tailwind Configuration as Design System:
1// tailwind.config.js - Design system foundation
2module.exports = {
3 theme: {
4 // Design tokens
5 colors: {
6 // Semantic color naming
7 primary: {
8 50: '#eff6ff',
9 100: '#dbeafe',
10 500: '#3b82f6',
11 600: '#2563eb',
12 900: '#1e3a8a',
13 },
14 secondary: {
15 // ...
16 },
17 success: {
18 light: '#d1fae5',
19 DEFAULT: '#10b981',
20 dark: '#065f46',
21 },
22 error: {
23 light: '#fee2e2',
24 DEFAULT: '#ef4444',
25 dark: '#991b1b',
26 },
27 warning: {
28 light: '#fef3c7',
29 DEFAULT: '#f59e0b',
30 dark: '#92400e',
31 },
32 },
33
34 // Typography scale
35 fontSize: {
36 xs: ['0.75rem', { lineHeight: '1rem' }],
37 sm: ['0.875rem', { lineHeight: '1.25rem' }],
38 base: ['1rem', { lineHeight: '1.5rem' }],
39 lg: ['1.125rem', { lineHeight: '1.75rem' }],
40 xl: ['1.25rem', { lineHeight: '1.75rem' }],
41 '2xl': ['1.5rem', { lineHeight: '2rem' }],
42 '3xl': ['1.875rem', { lineHeight: '2.25rem' }],
43 '4xl': ['2.25rem', { lineHeight: '2.5rem' }],
44 '5xl': ['3rem', { lineHeight: '1' }],
45 },
46
47 // Spacing scale (8px base)
48 spacing: {
49 px: '1px',
50 0: '0',
51 0.5: '0.125rem', // 2px
52 1: '0.25rem', // 4px
53 2: '0.5rem', // 8px
54 3: '0.75rem', // 12px
55 4: '1rem', // 16px
56 5: '1.25rem', // 20px
57 6: '1.5rem', // 24px
58 8: '2rem', // 32px
59 10: '2.5rem', // 40px
60 12: '3rem', // 48px
61 16: '4rem', // 64px
62 20: '5rem', // 80px
63 24: '6rem', // 96px
64 },
65
66 // Shadow system
67 boxShadow: {
68 sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
69 DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
70 md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
71 lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
72 xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
73 inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
74 },
75
76 // Border radius system
77 borderRadius: {
78 none: '0',
79 sm: '0.125rem', // 2px
80 DEFAULT: '0.25rem', // 4px
81 md: '0.375rem', // 6px
82 lg: '0.5rem', // 8px
83 xl: '0.75rem', // 12px
84 '2xl': '1rem', // 16px
85 '3xl': '1.5rem', // 24px
86 full: '9999px',
87 },
88
89 // Animation durations
90 transitionDuration: {
91 DEFAULT: '200ms',
92 75: '75ms',
93 100: '100ms',
94 150: '150ms',
95 200: '200ms',
96 300: '300ms',
97 500: '500ms',
98 700: '700ms',
99 1000: '1000ms',
100 },
101
102 extend: {
103 // Custom design tokens
104 fontFamily: {
105 sans: ['Inter var', 'system-ui', 'sans-serif'],
106 mono: ['JetBrains Mono', 'monospace'],
107 },
108
109 // Z-index scale
110 zIndex: {
111 dropdown: '1000',
112 sticky: '1020',
113 fixed: '1030',
114 'modal-backdrop': '1040',
115 modal: '1050',
116 popover: '1060',
117 tooltip: '1070',
118 },
119
120 // Custom animations
121 keyframes: {
122 'fade-in': {
123 '0%': { opacity: '0' },
124 '100%': { opacity: '1' },
125 },
126 'slide-up': {
127 '0%': { transform: 'translateY(10px)', opacity: '0' },
128 '100%': { transform: 'translateY(0)', opacity: '1' },
129 },
130 },
131 animation: {
132 'fade-in': 'fade-in 200ms ease-in-out',
133 'slide-up': 'slide-up 300ms ease-out',
134 },
135 },
136 },
137};Component Library Structure:
1// components/ui/index.ts - Central export
2export { Button } from './Button';
3export { Card } from './Card';
4export { Input } from './Input';
5export { Modal } from './Modal';
6export { Toast } from './Toast';
7// ... all components
8
9export type { ButtonProps } from './Button';
10export type { CardProps } from './Card';
11// ... all prop types
12
13// components/design-system.ts - Design system documentation
14export const DesignSystem = {
15 colors: {
16 primary: 'Use for primary actions and brand elements',
17 secondary: 'Use for secondary actions',
18 success: 'Use for positive feedback',
19 error: 'Use for errors and destructive actions',
20 warning: 'Use for warnings and caution',
21 },
22
23 spacing: {
24 description: '8px base unit for consistent spacing',
25 usage: 'Use multiples of 8px: 8, 16, 24, 32, 40, 48, etc.',
26 },
27
28 typography: {
29 headings: {
30 h1: 'text-4xl font-bold - Page titles',
31 h2: 'text-3xl font-semibold - Section headings',
32 h3: 'text-2xl font-semibold - Subsection headings',
33 },
34 body: {
35 large: 'text-lg - Prominent body text',
36 default: 'text-base - Standard body text',
37 small: 'text-sm - Secondary text',
38 xs: 'text-xs - Helper text, labels',
39 },
40 },
41};Design Tokens Documentation:
1// docs/design-tokens.ts
2export const designTokens = {
3 colors: {
4 primary: {
5 token: 'primary-500',
6 value: '#3b82f6',
7 usage: 'Primary buttons, links, active states',
8 accessibility: 'WCAG AAA compliant on white background',
9 },
10 // ... more tokens
11 },
12
13 spacing: {
14 unit: '8px',
15 scale: [2, 4, 8, 12, 16, 20, 24, 32, 40, 48],
16 usage: 'All spacing should use the 8px grid system',
17 },
18
19 typography: {
20 scale: 'Minor third (1.2 ratio)',
21 baseFontSize: '16px',
22 baseLineHeight: '1.5',
23 },
24};Establishing clear workflows and standards ensures consistency when multiple developers work on Tailwind projects.
Coding Standards Document:
1# Tailwind CSS Coding Standards
2
3## Class Name Ordering
4Use a consistent order for Tailwind classes:
5
61. Layout (display, position, float)
72. Box Model (width, height, margin, padding)
83. Typography (font, text color, alignment)
94. Visual (background, border, shadow)
105. Misc (cursor, opacity, transform)
11
12Example:
13\`\`\`jsx
14<div className="
15 flex justify-center items-center
16 w-full h-64 p-4 m-2
17 text-lg font-semibold text-white text-center
18 bg-blue-500 rounded-lg shadow-md
19 hover:bg-blue-600 cursor-pointer
20" />
21\`\`\`
22
23## Component Patterns
24
25### ✅ DO: Extract variants with CVA
26\`\`\`typescript
27const buttonVariants = cva('base-classes', {
28 variants: { variant: {...}, size: {...} }
29});
30\`\`\`
31
32### ❌ DON'T: Use @apply excessively
33\`\`\`css
34/* Avoid */
35.button {
36 @apply px-4 py-2 bg-blue-500 text-white rounded;
37}
38\`\`\`
39
40## File Naming
41- Components: PascalCase (Button.tsx, UserCard.tsx)
42- Utilities: camelCase (cn.ts, formatDate.ts)
43- Styles: kebab-case (base-styles.css, components.css)
44
45## Component Structure
46\`\`\`typescript
47// 1. Imports
48import { forwardRef } from 'react';
49import { cn } from '@/lib/utils';
50
51// 2. Types
52interface Props {}
53
54// 3. Component
55export const Component = forwardRef<HTMLElement, Props>(
56 ({ className, ...props }, ref) => {
57 return <element className={cn('base', className)} ref={ref} {...props} />;
58 }
59);
60
61// 4. Display name
62Component.displayName = 'Component';
63\`\`\`ESLint and Prettier Configuration:
1// .eslintrc.js
2module.exports = {
3 extends: [
4 'next/core-web-vitals',
5 'plugin:tailwindcss/recommended',
6 ],
7 plugins: ['tailwindcss'],
8 rules: {
9 // Tailwind-specific rules
10 'tailwindcss/classnames-order': 'warn',
11 'tailwindcss/no-custom-classname': 'warn',
12 'tailwindcss/no-contradicting-classname': 'error',
13
14 // Enforce class name utilities
15 'tailwindcss/enforces-negative-arbitrary-values': 'warn',
16 'tailwindcss/enforces-shorthand': 'warn',
17 },
18 settings: {
19 tailwindcss: {
20 callees: ['cn', 'cva'],
21 config: 'tailwind.config.js',
22 },
23 },
24};
25
26// .prettierrc
27{
28 "plugins": ["prettier-plugin-tailwindcss"],
29 "tailwindConfig": "./tailwind.config.js",
30 "tailwindFunctions": ["cn", "cva"]
31}Git Hooks for Quality:
1// package.json
2{
3 "husky": {
4 "hooks": {
5 "pre-commit": "lint-staged"
6 }
7 },
8 "lint-staged": {
9 "*.{js,jsx,ts,tsx}": [
10 "eslint --fix",
11 "prettier --write"
12 ],
13 "*.css": [
14 "prettier --write"
15 ]
16 }
17}Component Review Checklist:
1// docs/component-checklist.md
2export const ComponentChecklist = {
3 functionality: [
4 '✓ Component works as intended',
5 '✓ All props are typed correctly',
6 '✓ Handles edge cases gracefully',
7 ],
8
9 accessibility: [
10 '✓ Keyboard navigation works',
11 '✓ Screen reader compatible',
12 '✓ ARIA attributes present',
13 '✓ Focus states visible',
14 ],
15
16 styling: [
17 '✓ Uses design system tokens',
18 '✓ Responsive on all breakpoints',
19 '✓ Dark mode compatible (if applicable)',
20 '✓ No hardcoded values',
21 ],
22
23 performance: [
24 '✓ No unnecessary re-renders',
25 '✓ Large lists virtualized',
26 '✓ Images optimized',
27 ],
28
29 code_quality: [
30 '✓ Follows naming conventions',
31 '✓ Properly commented',
32 '✓ No console.log statements',
33 '✓ TypeScript strict mode passes',
34 ],
35};Ensuring accessibility is crucial for inclusive applications. Tailwind provides utilities that make it easier to build accessible interfaces.
Focus Management:
1/* Global focus styles */
2@layer base {
3 *:focus-visible {
4 @apply outline-none ring-2 ring-blue-500 ring-offset-2;
5 }
6
7 /* Skip to main content link */
8 .skip-to-main {
9 @apply sr-only focus:not-sr-only;
10 @apply fixed top-4 left-4 z-50;
11 @apply bg-white px-4 py-2 rounded-lg shadow-lg;
12 @apply focus:ring-2 focus:ring-blue-500;
13 }
14}Accessible Components:
1// Accessible Button
2interface ButtonProps {
3 children: React.ReactNode;
4 'aria-label'?: string;
5 'aria-describedby'?: string;
6 disabled?: boolean;
7}
8
9export function Button({ children, disabled, ...ariaProps }: ButtonProps) {
10 return (
11 <button
12 className={cn(
13 'px-4 py-2 rounded-lg font-semibold',
14 'focus:outline-none focus:ring-2 focus:ring-blue-500',
15 disabled && 'opacity-50 cursor-not-allowed'
16 )}
17 disabled={disabled}
18 aria-disabled={disabled}
19 {...ariaProps}
20 >
21 {children}
22 </button>
23 );
24}
25
26// Accessible Modal
27export function Modal({ isOpen, onClose, title, children }: ModalProps) {
28 return (
29 <div
30 className={cn(
31 'fixed inset-0 z-50',
32 isOpen ? 'block' : 'hidden'
33 )}
34 role="dialog"
35 aria-modal="true"
36 aria-labelledby="modal-title"
37 >
38 {/* Backdrop */}
39 <div
40 className="fixed inset-0 bg-black/50"
41 aria-hidden="true"
42 onClick={onClose}
43 />
44
45 {/* Modal */}
46 <div className="fixed inset-0 flex items-center justify-center p-4">
47 <div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
48 <h2 id="modal-title" className="text-2xl font-bold mb-4">
49 {title}
50 </h2>
51 {children}
52 </div>
53 </div>
54 </div>
55 );
56}
57
58// Accessible Form Input
59export function Input({ label, error, ...props }: InputProps) {
60 const id = useId();
61 const errorId = `${id}-error`;
62
63 return (
64 <div className="space-y-1">
65 <label
66 htmlFor={id}
67 className="block text-sm font-medium text-gray-700"
68 >
69 {label}
70 </label>
71 <input
72 id={id}
73 className={cn(
74 'w-full px-4 py-2 border-2 rounded-lg',
75 'focus:outline-none focus:ring-2 focus:ring-blue-500',
76 error
77 ? 'border-red-500 focus:ring-red-500'
78 : 'border-gray-300'
79 )}
80 aria-invalid={error ? 'true' : 'false'}
81 aria-describedby={error ? errorId : undefined}
82 {...props}
83 />
84 {error && (
85 <p id={errorId} className="text-sm text-red-600" role="alert">
86 {error}
87 </p>
88 )}
89 </div>
90 );
91}Screen Reader Utilities:
1// Screen reader only text
2<span className="sr-only">Additional context for screen readers</span>
3
4// Icon with accessible label
5<button aria-label="Close menu">
6 <XIcon className="w-6 h-6" aria-hidden="true" />
7</button>
8
9// Skip navigation link
10<a href="#main-content" className="sr-only focus:not-sr-only">
11 Skip to main content
12</a>
13
14// Loading state
15<button aria-busy="true" aria-live="polite">
16 <span className="sr-only">Loading...</span>
17 <Spinner aria-hidden="true" />
18</button>Color Contrast:
1// tailwind.config.js - Ensure WCAG compliance
2module.exports = {
3 theme: {
4 extend: {
5 colors: {
6 // All color combinations tested for WCAG AAA
7 primary: {
8 // 7:1 contrast ratio on white
9 DEFAULT: '#0066cc', // AAA compliant
10 },
11 text: {
12 primary: '#1a1a1a', // AAA on white
13 secondary: '#4a4a4a', // AAA on white
14 tertiary: '#6b6b6b', // AA on white
15 },
16 },
17 },
18 },
19};Understanding what NOT to do is as important as knowing best practices. Avoid these common mistakes in Tailwind projects.
❌ Anti-Pattern 1: Overusing @apply:
1/* BAD - Defeating the purpose of Tailwind */
2.button {
3 @apply px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600;
4}
5
6/* GOOD - Use Tailwind classes directly */
7<button className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
8 Click me
9</button>
10
11/* GOOD - Or use CVA for variants */
12const buttonVariants = cva('px-4 py-2 rounded-lg', {
13 variants: { variant: { primary: '...' } }
14});❌ Anti-Pattern 2: Dynamic Class Construction:
1// BAD - Classes won't be purged correctly
2const color = 'blue';
3<div className={`bg-${color}-500`} />
4
5// GOOD - Full class names
6const colors = {
7 blue: 'bg-blue-500',
8 red: 'bg-red-500',
9};
10<div className={colors[color]} />
11
12// GOOD - Safelist pattern in config
13// tailwind.config.js
14safelist: [{ pattern: /bg-(blue|red)-(100|500|900)/ }]❌ Anti-Pattern 3: Inconsistent Spacing:
1// BAD - Random spacing values
2<div className="mt-3 mb-7 pl-5 pr-9" />
3
4// GOOD - Consistent 8px grid
5<div className="mt-4 mb-8 px-6" /> // 16px, 32px, 24px❌ Anti-Pattern 4: Ignoring Responsive Design:
1// BAD - Fixed layouts
2<div className="grid grid-cols-4 gap-4">
3
4// GOOD - Mobile-first responsive
5<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">❌ Anti-Pattern 5: Hardcoding Colors:
1// BAD - Arbitrary values everywhere
2<div className="bg-[#ff5733] text-[#123456]" />
3
4// GOOD - Design system tokens
5<div className="bg-primary-500 text-gray-900" />❌ Anti-Pattern 6: Prop Drilling Styles:
1// BAD - Passing too many style props
2<Button textColor="blue" bgColor="white" padding="large" />
3
4// GOOD - Predefined variants
5<Button variant="outline" size="lg" />❌ Anti-Pattern 7: Not Using Composition:
1// BAD - Huge className string
2<div className="flex flex-col items-center justify-center w-full h-screen bg-gradient-to-r from-blue-500 to-purple-600 p-8 rounded-lg shadow-xl" />
3
4// GOOD - Composed components
5<Container>
6 <Card variant="gradient">
7 <Content centered />
8 </Card>
9</Container>✅ Best Practice Patterns:
1// 1. Use cn() utility for conditional classes
2<div className={cn(
3 'base-classes',
4 isActive && 'active-classes',
5 className // Allow external overrides
6)} />
7
8// 2. Extract reusable variants
9const cardVariants = cva('rounded-lg shadow', {
10 variants: {
11 variant: {
12 default: 'bg-white',
13 primary: 'bg-blue-500 text-white',
14 },
15 },
16});
17
18// 3. Compose smaller components
19const Card = ({ children, variant }) => (
20 <div className={cardVariants({ variant })}>
21 {children}
22 </div>
23);
24
25// 4. Use TypeScript for type safety
26interface ComponentProps {
27 variant: 'primary' | 'secondary';
28 size: 'sm' | 'md' | 'lg';
29}
30
31// 5. Document component APIs
32/**
33 * Button component with variants
34 * @param variant - Button style variant
35 * @param size - Button size
36 * @example
37 * <Button variant="primary" size="lg">Click me</Button>
38 */Optimizing the developer experience ensures your team can work efficiently with Tailwind CSS.
VS Code Configuration:
1// .vscode/settings.json
2{
3 "editor.quickSuggestions": {
4 "strings": true
5 },
6 "tailwindCSS.experimental.classRegex": [
7 ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
8 ["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
9 ],
10 "tailwindCSS.includeLanguages": {
11 "typescript": "javascript",
12 "typescriptreact": "javascript"
13 },
14 "files.associations": {
15 "*.css": "tailwindcss"
16 },
17 "editor.formatOnSave": true,
18 "editor.defaultFormatter": "esbenp.prettier-vscode"
19}
20
21// .vscode/extensions.json
22{
23 "recommendations": [
24 "bradlc.vscode-tailwindcss",
25 "esbenp.prettier-vscode",
26 "dbaeumer.vscode-eslint"
27 ]
28}IntelliSense Enhancement:
1// types/tailwind.d.ts
2// Enable autocomplete for custom utilities
3declare module 'tailwindcss' {
4 interface Config {
5 theme: {
6 extend: {
7 colors: {
8 primary: Record<string, string>;
9 secondary: Record<string, string>;
10 };
11 };
12 };
13 }
14}Component Development Tools:
1// scripts/generate-component.js
2const fs = require('fs');
3const path = require('path');
4
5function generateComponent(name) {
6 const componentDir = path.join(__dirname, '../components/ui', name);
7
8 // Create component directory
9 fs.mkdirSync(componentDir, { recursive: true });
10
11 // Generate files
12 const files = {
13 [`${name}.tsx`]: componentTemplate(name),
14 [`${name}.variants.ts`]: variantsTemplate(name),
15 'index.ts': exportTemplate(name),
16 };
17
18 Object.entries(files).forEach(([filename, content]) => {
19 fs.writeFileSync(
20 path.join(componentDir, filename),
21 content
22 );
23 });
24
25 console.log(`✅ Component ${name} generated!`);
26}
27
28function componentTemplate(name) {
29 return `import { forwardRef } from 'react';
30import { cn } from '@/lib/utils/cn';
31import { ${name.toLowerCase()}Variants } from './ ${name}.variants';
32
33export interface ${name}Props
34 extends React.HTMLAttributes<HTMLDivElement> {}
35
36export const ${name} = forwardRef<HTMLDivElement, ${name}Props>(
37 ({ className, ...props }, ref) => {
38 return (
39 <div
40 ref={ref}
41 className={cn(${name.toLowerCase()}Variants(), className)}
42 {...props}
43 />
44 );
45 }
46);
47
48${name}.displayName = '${name}';
49`;
50}
51
52// Usage: node scripts/generate-component.js CardStorybook Integration:
1// .storybook/preview.js
2import '../src/styles/main.css';
3
4export const parameters = {
5 actions: { argTypesRegex: '^on[A-Z].*' },
6 controls: {
7 matchers: {
8 color: /(background|color)$/i,
9 date: /Date$/,
10 },
11 },
12};
13
14// components/ui/Button/Button.stories.tsx
15import type { Meta, StoryObj } from '@storybook/react';
16import { Button } from './Button';
17
18const meta: Meta<typeof Button> = {
19 title: 'UI/Button',
20 component: Button,
21 tags: ['autodocs'],
22 argTypes: {
23 variant: {
24 control: 'select',
25 options: ['primary', 'secondary', 'outline', 'ghost'],
26 },
27 size: {
28 control: 'select',
29 options: ['sm', 'md', 'lg'],
30 },
31 },
32};
33
34export default meta;
35type Story = StoryObj<typeof Button>;
36
37export const Primary: Story = {
38 args: {
39 variant: 'primary',
40 children: 'Button',
41 },
42};Documentation Generation:
1// scripts/generate-docs.js
2// Auto-generate component documentation from TypeScript types
3
4import * as ts from 'typescript';
5import * as fs from 'fs';
6
7function extractComponentDocs(filename: string) {
8 const program = ts.createProgram([filename], {});
9 const sourceFile = program.getSourceFile(filename);
10
11 // Extract component props, descriptions, examples
12 // Generate markdown documentation
13
14 return {
15 props: extractProps(sourceFile),
16 examples: extractExamples(sourceFile),
17 };
18}A complete example demonstrating all best practices in a production-ready component library with proper organization, accessibility, TypeScript, and documentation.
1// ===================================
2// Project Structure Overview
3// ===================================
4
5/*
6src/
7├── components/
8│ └── ui/
9│ ├── Button/
10│ │ ├── Button.tsx
11│ │ ├── Button.variants.ts
12│ │ ├── Button.test.tsx
13│ │ ├── Button.stories.tsx
14│ │ └── index.ts
15│ └── index.ts
16├── lib/
17│ └── utils/
18│ └── cn.ts
19└── styles/
20 └── main.css
21*/
22
23// ===================================
24// lib/utils/cn.ts - Class Name Utility
25// ===================================
26
27import { clsx, type ClassValue } from 'clsx';
28import { twMerge } from 'tailwind-merge';
29
30/**
31 * Merges class names with Tailwind CSS classes
32 * Handles conditional classes and prevents conflicts
33 */
34export function cn(...inputs: ClassValue[]) {
35 return twMerge(clsx(inputs));
36}
37
38// ===================================
39// components/ui/Button/Button.variants.ts
40// ===================================
41
42import { cva, type VariantProps } from 'class-variance-authority';
43
44export const buttonVariants = cva(
45 // Base styles
46 [
47 'inline-flex items-center justify-center',
48 'rounded-lg font-semibold',
49 'transition-all duration-200',
50 'focus:outline-none focus:ring-2 focus:ring-offset-2',
51 'disabled:opacity-50 disabled:cursor-not-allowed',
52 ],
53 {
54 variants: {
55 variant: {
56 primary: [
57 'bg-blue-500 text-white',
58 'hover:bg-blue-600',
59 'focus:ring-blue-500',
60 'active:bg-blue-700',
61 ],
62 secondary: [
63 'bg-gray-500 text-white',
64 'hover:bg-gray-600',
65 'focus:ring-gray-500',
66 'active:bg-gray-700',
67 ],
68 outline: [
69 'border-2 border-blue-500 text-blue-500',
70 'hover:bg-blue-50',
71 'focus:ring-blue-500',
72 'active:bg-blue-100',
73 ],
74 ghost: [
75 'bg-transparent text-blue-500',
76 'hover:bg-blue-50',
77 'focus:ring-blue-500',
78 'active:bg-blue-100',
79 ],
80 destructive: [
81 'bg-red-500 text-white',
82 'hover:bg-red-600',
83 'focus:ring-red-500',
84 'active:bg-red-700',
85 ],
86 },
87 size: {
88 sm: 'text-sm px-3 py-1.5',
89 md: 'text-base px-4 py-2',
90 lg: 'text-lg px-6 py-3',
91 },
92 fullWidth: {
93 true: 'w-full',
94 },
95 },
96 compoundVariants: [
97 {
98 variant: 'outline',
99 size: 'sm',
100 class: 'border-1',
101 },
102 ],
103 defaultVariants: {
104 variant: 'primary',
105 size: 'md',
106 },
107 }
108);
109
110export type ButtonVariants = VariantProps<typeof buttonVariants>;
111
112// ===================================
113// components/ui/Button/Button.tsx
114// ===================================
115
116import { forwardRef } from 'react';
117import { cn } from '@/lib/utils/cn';
118import { buttonVariants, type ButtonVariants } from './Button.variants';
119
120export interface ButtonProps
121 extends React.ButtonHTMLAttributes<HTMLButtonElement>,
122 ButtonVariants {
123 /**
124 * Loading state - shows spinner and disables button
125 */
126 isLoading?: boolean;
127 /**
128 * Icon to display before button text
129 */
130 leftIcon?: React.ReactNode;
131 /**
132 * Icon to display after button text
133 */
134 rightIcon?: React.ReactNode;
135}
136
137/**
138 * Button component with multiple variants and sizes
139 *
140 * @example
141 * // Primary button
142 * <Button variant="primary" size="md">Click me</Button>
143 *
144 * @example
145 * // Button with loading state
146 * <Button isLoading>Processing...</Button>
147 *
148 * @example
149 * // Button with icons
150 * <Button leftIcon={<ChevronLeft />}>Back</Button>
151 */
152export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
153 (
154 {
155 className,
156 variant,
157 size,
158 fullWidth,
159 isLoading,
160 leftIcon,
161 rightIcon,
162 disabled,
163 children,
164 ...props
165 },
166 ref
167 ) => {
168 return (
169 <button
170 ref={ref}
171 className={cn(
172 buttonVariants({ variant, size, fullWidth }),
173 className
174 )}
175 disabled={disabled || isLoading}
176 aria-busy={isLoading}
177 {...props}
178 >
179 {isLoading && (
180 <svg
181 className="animate-spin -ml-1 mr-2 h-4 w-4"
182 xmlns="http://www.w3.org/2000/svg"
183 fill="none"
184 viewBox="0 0 24 24"
185 aria-hidden="true"
186 >
187 <circle
188 className="opacity-25"
189 cx="12"
190 cy="12"
191 r="10"
192 stroke="currentColor"
193 strokeWidth="4"
194 />
195 <path
196 className="opacity-75"
197 fill="currentColor"
198 d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
199 />
200 </svg>
201 )}
202 {!isLoading && leftIcon && (
203 <span className="mr-2" aria-hidden="true">
204 {leftIcon}
205 </span>
206 )}
207 {children}
208 {!isLoading && rightIcon && (
209 <span className="ml-2" aria-hidden="true">
210 {rightIcon}
211 </span>
212 )}
213 </button>
214 );
215 }
216);
217
218Button.displayName = 'Button';
219
220// ===================================
221// components/ui/Button/index.ts
222// ===================================
223
224export { Button } from './Button';
225export type { ButtonProps } from './Button';
226export { buttonVariants } from './Button.variants';
227
228// ===================================
229// components/ui/index.ts - Barrel Export
230// ===================================
231
232export { Button } from './Button';
233export type { ButtonProps } from './Button';
234
235// ===================================
236// Usage Examples
237// ===================================
238
239import { Button } from '@/components/ui';
240
241function App() {
242 return (
243 <div className="space-y-4">
244 {/* Basic usage */}
245 <Button variant="primary" size="md">
246 Primary Button
247 </Button>
248
249 {/* With loading state */}
250 <Button variant="primary" isLoading>
251 Loading...
252 </Button>
253
254 {/* With icons */}
255 <Button
256 variant="outline"
257 leftIcon={<ChevronLeftIcon />}
258 >
259 Back
260 </Button>
261
262 {/* Full width */}
263 <Button variant="primary" fullWidth>
264 Full Width Button
265 </Button>
266
267 {/* Custom className (properly merged) */}
268 <Button
269 variant="primary"
270 className="shadow-lg hover:shadow-xl"
271 >
272 With Custom Shadow
273 </Button>
274
275 {/* Disabled state */}
276 <Button variant="primary" disabled>
277 Disabled
278 </Button>
279
280 {/* Accessible with ARIA */}
281 <Button
282 variant="destructive"
283 aria-label="Delete account"
284 >
285 Delete
286 </Button>
287 </div>
288 );
289}
290
291// ===================================
292// tailwind.config.js - Design System
293// ===================================
294
295module.exports = {
296 content: ['./src/**/*.{js,jsx,ts,tsx}'],
297 theme: {
298 extend: {
299 colors: {
300 primary: {
301 50: '#eff6ff',
302 500: '#3b82f6',
303 600: '#2563eb',
304 700: '#1d4ed8',
305 },
306 },
307 fontFamily: {
308 sans: ['Inter var', 'sans-serif'],
309 },
310 },
311 },
312 plugins: [],
313};
314
315// ===================================
316// .eslintrc.js - Quality Enforcement
317// ===================================
318
319module.exports = {
320 extends: [
321 'next/core-web-vitals',
322 'plugin:tailwindcss/recommended',
323 ],
324 plugins: ['tailwindcss'],
325 rules: {
326 'tailwindcss/classnames-order': 'warn',
327 'tailwindcss/no-custom-classname': 'warn',
328 'tailwindcss/no-contradicting-classname': 'error',
329 },
330 settings: {
331 tailwindcss: {
332 callees: ['cn', 'cva'],
333 },
334 },
335};Congratulations on completing the Tailwind CSS Complete course! You now have a comprehensive understanding of Tailwind CSS from basics to advanced production techniques.
Next steps for continuing your journey:
Keep building beautiful, accessible, and performant user interfaces!
Your progress is saved in this browser, on this device only.