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
- In the same interface, there should be only one primary button
- Button text should clearly describe the operation result
- Dangerous operations should use the destructive variant
- 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.