Chapter 8: Performance Optimization and Best Practices

Haiyue
19min

Chapter 8: Performance Optimization and Best Practices

Learning Objectives
  • Master CSS file size optimization techniques
  • Learn to reasonably organize and maintain Tailwind code
  • Understand accessibility best practices
  • Master team collaboration and code standard formulation

CSS File Size Optimization

Deep Optimization with PurgeCSS

// tailwind.config.js - Production environment optimization
module.exports = {
  content: [
    './src/**/*.{html,js,ts,jsx,tsx,vue,svelte}',
    './public/index.html',
    // Ensure all files that might use Tailwind classes are included
    './node_modules/some-library/**/*.js',
  ],
  
  // Configure safelist
  safelist: [
    // Protect dynamically generated class names
    'bg-red-500',
    'bg-green-500',
    'bg-blue-500',
    
    // Use regular expressions to match patterns
    /^bg-(red|green|blue)-(100|200|300|400|500|600|700|800|900)$/,
    /^text-(sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)$/,
    
    // Include responsive and state variants
    {
      pattern: /^(bg|text|border)-(red|green|blue)-(100|500|900)$/,
      variants: ['hover', 'focus', 'lg', 'xl', 'lg:hover', 'xl:focus'],
    },
    
    // Protect class names of third-party components
    {
      pattern: /^react-datepicker/,
    }
  ],
  
  // Blocklist
  blocklist: [
    'container',
    'debug-screens', // Debugging tool
  ],
}

Dynamic Class Name Handling

// ❌ Incorrect: Dynamic string concatenation, PurgeCSS cannot detect
const dynamicClass = (color) => `bg-${color}-500`

// ✅ Correct: Use full class names
const getButtonClass = (color) => {
  const colorMap = {
    red: 'bg-red-500 hover:bg-red-600',
    green: 'bg-green-500 hover:bg-green-600',
    blue: 'bg-blue-500 hover:bg-blue-600'
  }
  return colorMap[color] || 'bg-gray-500 hover:bg-gray-600'
}

// ✅ Correct: Declare in safelist
// tailwind.config.js
safelist: [
  'bg-red-500', 'bg-green-500', 'bg-blue-500',
  'hover:bg-red-600', 'hover:bg-green-600', 'hover:bg-blue-600'
]

Build Optimization Configuration

// postcss.config.js - Production environment optimization
module.exports = {
  plugins: [
    require('tailwindcss'),
    require('autoprefixer'),
    
    // Production compression
    ...(process.env.NODE_ENV === 'production' 
      ? [
          require('cssnano')({
            preset: ['advanced', {
              discardComments: { removeAll: true },
              normalizeWhitespace: false,
              reduceIdents: false, // Avoid breaking animation names
              zindex: false, // Avoid reordering z-index
            }]
          })
        ] 
      : []
    ),
  ],
}

File Splitting Strategy

/* styles/base.css - Base styles */
@tailwind base;

/* Custom base styles */
@layer base {
  body {
    @apply font-sans antialiased;
  }
  
  h1, h2, h3, h4, h5, h6 {
    @apply font-bold;
  }
}
/* styles/components.css - Component styles */
@tailwind components;

@layer components {
  .btn {
    @apply px-4 py-2 font-medium rounded-lg transition-colors;
    @apply focus:outline-none focus:ring-2 focus:ring-offset-2;
  }
  
  .btn-primary {
    @apply btn bg-blue-600 text-white;
    @apply hover:bg-blue-700 focus:ring-blue-500;
  }
  
  .card {
    @apply bg-white rounded-lg shadow-md border border-gray-200;
  }
}
/* styles/utilities.css - Utility classes */
@tailwind utilities;

@layer utilities {
  .text-shadow-sm {
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  }
  
  .text-shadow {
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  }
  
  .scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }
  
  .scrollbar-hide::-webkit-scrollbar {
    display: none;
  }
}
// Import styles on demand
// main.js
import './styles/base.css'
import './styles/components.css'

// Import utility classes only when needed
if (process.env.NODE_ENV === 'development') {
  import('./styles/utilities.css')
}

Code Organization and Maintenance

Project Structure Best Practices

src/
├── styles/
│   ├── base.css          # Base style resets
│   ├── components.css    # Reusable component styles
│   ├── utilities.css     # Custom utility classes
│   └── main.css         # Main entry file
├── components/
│   ├── ui/              # Basic UI components
│   │   ├── Button.jsx
│   │   ├── Card.jsx
│   │   └── Input.jsx
│   └── layout/          # Layout components
│       ├── Header.jsx
│       └── Sidebar.jsx
├── utils/
│   ├── cn.js           # Class name merging utility
│   └── tailwind.js     # Tailwind related utilities
└── config/
    └── tailwind.js     # Tailwind configuration extension

Component Design Patterns

// components/ui/Button.jsx - Component variant pattern
import React from 'react'
import { cva } from 'class-variance-authority'
import { cn } from '@/utils/cn'

// Define button variants
const buttonVariants = cva(
  // Base styles
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
        outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
        secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2",
        sm: "h-8 rounded-md px-3 text-xs",
        lg: "h-10 rounded-md px-8",
        icon: "h-9 w-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

const Button = React.forwardRef(({ 
  className, 
  variant, 
  size, 
  asChild = false, 
  ...props 
}, ref) => {
  const Comp = asChild ? "span" : "button"
  return (
    <Comp
      className={cn(buttonVariants({ variant, size, className }))}
      ref={ref}
      {...props}
    />
  )
})

Button.displayName = "Button"

export { Button, buttonVariants }

Style Combination Utility

// utils/cn.js - Class name merging utility
import { clsx } from "clsx"
import { twMerge } from "tailwind-merge"

/**
 * Merges and de-duplicates Tailwind CSS class names
 * @param {...(string | object | array)} inputs - Class name inputs
 * @returns {string} - Merged class name string
 */
export function cn(...inputs) {
  return twMerge(clsx(inputs))
}

// Usage example
cn('px-2 py-1', 'px-3', { 'py-2': true })
// Result: 'px-3 py-2' (px-2 is overridden by px-3)

Theme Configuration Extraction

// config/theme.js - Theme configuration
export const colors = {
  primary: {
    50: '#eff6ff',
    100: '#dbeafe',
    500: '#3b82f6',
    600: '#2563eb',
    900: '#1e3a8a',
  },
  gray: {
    50: '#f9fafb',
    100: '#f3f4f6',
    500: '#6b7280',
    900: '#111827',
  }
}

export const spacing = {
  '18': '4.5rem',
  '72': '18rem',
  '84': '21rem',
  '96': '24rem',
}

export const fontFamily = {
  sans: ['Inter', 'system-ui', 'sans-serif'],
  mono: ['JetBrains Mono', 'Menlo', 'monospace'],
}
// tailwind.config.js - Use extracted configuration
const { colors, spacing, fontFamily } = require('./config/theme')

module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {
      colors,
      spacing,
      fontFamily,
    },
  },
  plugins: [],
}

Accessibility Best Practices

Semantic HTML with Tailwind

<!-- ❌ Incorrect: Lacks semantic meaning -->
<div class="cursor-pointer bg-blue-500 text-white px-4 py-2 rounded">
  Click Me
</div>

<!-- ✅ Correct: Use semantic elements -->
<button 
  class="bg-blue-500 hover:bg-blue-600 text-white font-medium px-4 py-2 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
  type="button"
>
  Click Me
</button>

<!-- ✅ Correct: Links use a tag -->
<a 
  href="/about"
  class="inline-block bg-blue-500 hover:bg-blue-600 text-white font-medium px-4 py-2 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
  Learn More
</a>

Focus Management

/* styles/accessibility.css */
@layer utilities {
  /* Custom focus styles */
  .focus-visible-custom {
    @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2;
  }
  
  /* High contrast mode support */
  @media (prefers-contrast: high) {
    .focus-visible-custom {
      @apply focus-visible:ring-4 focus-visible:ring-yellow-400;
    }
  }
  
  /* Skip link */
  .skip-link {
    @apply absolute -top-8 left-4 bg-white text-blue-600 px-3 py-1 rounded z-50;
    @apply focus:top-4 transition-all;
  }
}
<!-- Skip navigation -->
<a href="#main-content" class="skip-link">
  Skip to main content
</a>

<!-- Focus trap example -->
<div class="modal" role="dialog" aria-labelledby="modal-title" aria-modal="true">
  <div class="modal-content bg-white rounded-lg shadow-xl max-w-md mx-auto p-6">
    <h2 id="modal-title" class="text-lg font-semibold mb-4">
      Confirm Operation
    </h2>
    <p class="text-gray-600 mb-6">
      Are you sure you want to delete this item? This action cannot be undone.
    </p>
    <div class="flex justify-end space-x-3">
      <button 
        type="button"
        class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg focus-visible-custom"
        onclick="closeModal()"
      >
        Cancel
      </button>
      <button 
        type="button"
        class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg focus-visible-custom"
        onclick="confirmDelete()"
      >
        Confirm Delete
      </button>
    </div>
  </div>
</div>

Color Contrast Optimization

// utils/contrast.js - Color contrast check utility
export function getContrastRatio(color1, color2) {
  const getLuminance = (color) => {
    // Convert color to RGB
    const rgb = color.match(/\d+/g).map(Number)
    const [r, g, b] = rgb.map(c => {
      c = c / 255
      return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
    })
    return 0.2126 * r + 0.7152 * g + 0.0722 * b
  }
  
  const l1 = getLuminance(color1)
  const l2 = getLuminance(color2)
  const lighter = Math.max(l1, l2)
  const darker = Math.min(l1, l2)
  
  return (lighter + 0.05) / (darker + 0.05)
}

// Validate color contrast
export function isContrastSufficient(foreground, background, level = 'AA') {
  const ratio = getContrastRatio(foreground, background)
  const thresholds = {
    'AA': 4.5,
    'AAA': 7
  }
  return ratio >= thresholds[level]
}
/* High contrast color scheme */
@layer base {
  :root {
    /* Ensure sufficient contrast */
    --text-primary: 17 24 39;    /* gray-900 */
    --text-secondary: 75 85 99;  /* gray-600 */
    --bg-primary: 255 255 255;   /* white */
    --bg-secondary: 249 250 251; /* gray-50 */
  }
  
  /* Dark mode */
  .dark {
    --text-primary: 249 250 251;  /* gray-50 */
    --text-secondary: 209 213 219; /* gray-300 */
    --bg-primary: 17 24 39;       /* gray-900 */
    --bg-secondary: 31 41 55;     /* gray-800 */
  }
}

.text-primary {
  color: rgb(var(--text-primary));
}

.bg-primary {
  background-color: rgb(var(--bg-primary));
}

Responsive Font Sizes

@layer utilities {
  /* Font size based on user preference */
  @media (prefers-reduced-motion: no-preference) {
    .text-responsive {
      @apply text-sm sm:text-base lg:text-lg;
    }
  }
  
  @media (prefers-reduced-motion: reduce) {
    .text-responsive {
      @apply text-base;
    }
  }
  
  /* Support user font size preference */
  .text-fluid {
    font-size: clamp(0.875rem, 0.75rem + 0.5vw, 1.125rem);
  }
}

Team Collaboration and Code Standards

ESLint Rule Configuration

// .eslintrc.js
module.exports = {
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
  ],
  plugins: [
    'tailwindcss'
  ],
  rules: {
    // Tailwind CSS related rules
    'tailwindcss/classnames-order': 'warn',
    'tailwindcss/no-custom-classname': 'warn',
    'tailwindcss/no-contradicting-classname': 'error',
    
    // Disallow deprecated class names
    'tailwindcss/no-arbitrary-value': 'off', // Allow arbitrary values
    
    // Enforce shorthand class names
    'tailwindcss/enforces-shorthand': 'warn',
    
    // Disallow unnecessary arbitrary values
    'tailwindcss/no-unnecessary-arbitrary-value': 'warn',
  },
  settings: {
    tailwindcss: {
      config: './tailwind.config.js',
      callees: ['cn', 'clsx', 'ctl'],
      classRegex: '^class(Name)?$',
    }
  }
}

Prettier Configuration

// .prettierrc.js
module.exports = {
  plugins: ['prettier-plugin-tailwindcss'],
  tailwindConfig: './tailwind.config.js',
  semi: false,
  singleQuote: true,
  tabWidth: 2,
  trailingComma: 'es5',
  printWidth: 80,
  
  // Tailwind CSS class name sorting
  tailwindFunctions: ['clsx', 'cn', 'cva'],
}

Class Naming Convention

// Class name organization convention
const TAILWIND_CLASS_ORDER = [
  // 1. Layout
  'flex', 'grid', 'block', 'inline', 'relative', 'absolute',
  
  // 2. Positioning
  'top-*', 'right-*', 'bottom-*', 'left-*', 'inset-*', 'z-*',
  
  // 3. Sizing
  'w-*', 'h-*', 'min-w-*', 'min-h-*', 'max-w-*', 'max-h-*',
  
  // 4. Spacing
  'm-*', 'mx-*', 'my-*', 'mt-*', 'mr-*', 'mb-*', 'ml-*',
  'p-*', 'px-*', 'py-*', 'pt-*', 'pr-*', 'pb-*', 'pl-*',
  
  // 5. Typography and Text
  'font-*', 'text-*', 'leading-*', 'tracking-*', 'align-*',
  
  // 6. Colors
  'text-*', 'bg-*', 'border-*',
  
  // 7. Borders
  'border', 'border-*', 'rounded-*',
  
  // 8. Effects
  'shadow-*', 'opacity-*', 'blur-*',
  
  // 9. Transitions and Animations
  'transition-*', 'duration-*', 'ease-*', 'animate-*',
  
  // 10. Interactivity
  'cursor-*', 'select-*', 'resize-*',
]

Component Library Documentation Standards

// components/Button.stories.js - Storybook example
import { Button } from './Button'

export default {
  title: 'UI/Button',
  component: Button,
  parameters: {
    docs: {
      description: {
        component: 'Generic button component supporting multiple variants and states.'
      }
    }
  },
  argTypes: {
    variant: {
      control: { type: 'select' },
      options: ['default', 'outline', 'ghost', 'link'],
      description: 'Button style variant'
    },
    size: {
      control: { type: 'select' },
      options: ['sm', 'default', 'lg'],
      description: 'Button size'
    },
    disabled: {
      control: 'boolean',
      description: 'Whether the button is disabled'
    }
  }
}

export const Default = {
  args: {
    children: 'Default Button',
    variant: 'default'
  }
}

export const Variants = () => (
  <div className="flex gap-4">
    <Button variant="default">Default</Button>
    <Button variant="outline">Outline</Button>
    <Button variant="ghost">Ghost</Button>
    <Button variant="link">Link</Button>
  </div>
)

export const Sizes = () => (
  <div className="flex items-center gap-4">
    <Button size="sm">Small Button</Button>
    <Button size="default">Default Button</Button>
    <Button size="lg">Large Button</Button>
  </div>
)

Design System Documentation

# Design System - Button Component

## Overview
The button component is one of the most commonly used interactive elements in user interfaces. Our button component provides a consistent look and feel.

## Variants

### Default Button (Primary)
Used for primary actions, such as form submission, confirmation dialogs, etc.
```jsx
<Button variant="default">Save</Button>

Outline Button (Secondary)

Used for secondary actions, such as cancel, reset, etc.

<Button variant="outline">Cancel</Button>

Accessibility

  • All buttons support keyboard navigation
  • Includes appropriate ARIA labels
  • Complies with WCAG 2.1 Level AA standards
  • Supports screen readers

Usage Guidelines

  1. In the same interface, there should be only one primary button
  2. Button text should clearly describe the operation result
  3. Dangerous operations should use the destructive variant
  4. Buttons should be disabled and show a loading indicator when loading

### Code Review Checklist

```markdown
# Tailwind CSS Code Review Checklist

## Performance Optimization
- [ ] Check for unused class names
- [ ] Confirm dynamic class names are added to safelist
- [ ] Validate CSS file size in production build

## Code Quality
- [ ] Class names are ordered according to conventions
- [ ] Use semantic component abstractions
- [ ] Avoid redundant style combinations
- [ ] Use consistent naming conventions

## Accessibility
- [ ] Color contrast complies with WCAG standards
- [ ] Focus states are clearly visible
- [ ] Supports keyboard navigation
- [ ] Includes necessary ARIA attributes

## Responsive Design
- [ ] Test on all breakpoints
- [ ] Text is readable on mobile devices
- [ ] Interactive elements have sufficient touch target size

## Browser Compatibility
- [ ] Test in target browsers
- [ ] Check CSS property support
- [ ] Verify PostCSS configuration is correct

Summary

Through this chapter, you should have mastered:

  • Performance Optimization: CSS file size control and build optimization strategies
  • Code Organization: Project structure and component design best practices
  • Accessibility: Implementation of standard-compliant accessible design
  • Team Collaboration: Code standards and toolchain configuration

These skills ensure that you can efficiently and reliably use Tailwind CSS in a production environment, and collaborate with team members to develop high-quality user interfaces.