Chapter 04: Tailwind CSS Styling System
Haiyue
14min
Learning Objectives
- Configure and optimize Tailwind CSS in Astro projects
- Master responsive design and style composition techniques
- Learn to customize Tailwind configuration and themes
- Implement component-level style management and reuse
Key Concepts
Tailwind CSS and Astro Integration
Tailwind CSS is a utility-first CSS framework that perfectly combines with Astro’s static generation capabilities:
- Zero Runtime Overhead: Generates minimized CSS at compile time
- On-Demand Generation: Only includes actually used utility classes
- Developer Experience: Seamlessly integrates with Astro component system
- Build Optimization: Automatically purges unused styles
Responsive Design System
Tailwind provides a mobile-first responsive design approach:
🔄 正在渲染 Mermaid 图表...
Style Composition and Reuse Strategies
- @apply Directive: Combine Tailwind classes into reusable CSS classes
- Component Classes: Create reusable style patterns through CSS component layer
- CSS Variables: Combine with Tailwind to implement dynamic theme switching
Installation and Configuration
Basic Installation
# Install Tailwind CSS and related dependencies
npm install -D tailwindcss @tailwindcss/typography
npx tailwindcss init -p
# Install Astro Tailwind integration
npx astro add tailwind
Tailwind Configuration File
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {
// Custom color system
colors: {
primary: {
50: '#eff6ff',
500: '#3b82f6',
900: '#1e3a8a',
},
accent: '#f59e0b',
},
// Custom fonts
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['Fira Code', 'monospace'],
},
// Custom spacing
spacing: {
'18': '4.5rem',
'88': '22rem',
},
// Custom breakpoints
screens: {
'xs': '475px',
'3xl': '1600px',
}
},
},
plugins: [
require('@tailwindcss/typography'),
],
}
Astro Integration Configuration
// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
export default defineConfig({
integrations: [
tailwind({
// Custom configuration path
config: { path: './custom-config.js' },
// Apply base styles
applyBaseStyles: false,
})
],
});
Responsive Design Practice
Responsive Grid Component
---
// src/components/ResponsiveGrid.astro
export interface Props {
items: any[];
}
const { items } = Astro.props;
---
<div class="
grid gap-4 p-4
grid-cols-1 <!-- Mobile: single column -->
sm:grid-cols-2 <!-- Small screen: two columns -->
md:grid-cols-3 <!-- Medium screen: three columns -->
lg:grid-cols-4 <!-- Large screen: four columns -->
xl:gap-6 <!-- Extra large screen: larger gap -->
">
{items.map((item) => (
<div class="
bg-white rounded-lg shadow-md p-4
hover:shadow-lg transition-shadow duration-300
dark:bg-gray-800 dark:text-white
">
<h3 class="text-lg font-semibold mb-2">{item.title}</h3>
<p class="text-gray-600 dark:text-gray-300">{item.description}</p>
</div>
))}
</div>
Responsive Navigation Component
---
// src/components/Navigation.astro
const navItems = [
{ href: '/', label: 'Home' },
{ href: '/about', label: 'About' },
{ href: '/blog', label: 'Blog' },
{ href: '/contact', label: 'Contact' },
];
---
<nav class="bg-white shadow-sm border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<!-- Logo -->
<div class="flex-shrink-0">
<a href="/" class="text-xl font-bold text-primary-600">
MyBrand
</a>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
{navItems.map((item) => (
<a
href={item.href}
class="
text-gray-600 hover:text-primary-600
px-3 py-2 rounded-md text-sm font-medium
transition-colors duration-200
"
>
{item.label}
</a>
))}
</div>
</div>
<!-- Mobile menu button -->
<div class="md:hidden">
<button
type="button"
class="
text-gray-600 hover:text-primary-600
p-2 rounded-md focus:outline-none
"
aria-label="Open menu"
>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Navigation -->
<div class="md:hidden">
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3 bg-gray-50">
{navItems.map((item) => (
<a
href={item.href}
class="
text-gray-600 hover:text-primary-600
block px-3 py-2 rounded-md text-base font-medium
"
>
{item.label}
</a>
))}
</div>
</div>
</nav>
Custom Theme System
Design System Foundation
/* src/styles/design-system.css */
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
/* Design tokens */
:root {
/* Color system */
--color-primary-50: #eff6ff;
--color-primary-500: #3b82f6;
--color-primary-900: #1e3a8a;
/* Font system */
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
/* Spacing system */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
/* Shadow system */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
/* Dark theme */
[data-theme="dark"] {
--color-bg: #1f2937;
--color-text: #f9fafb;
--color-border: #374151;
}
Component Style Layer
/* Button component styles */
@layer components {
.btn {
@apply px-4 py-2 rounded-md font-medium transition-all duration-200;
@apply focus:outline-none focus:ring-2 focus:ring-offset-2;
}
.btn-primary {
@apply bg-primary-500 text-white hover:bg-primary-600;
@apply focus:ring-primary-500;
}
.btn-secondary {
@apply bg-gray-200 text-gray-900 hover:bg-gray-300;
@apply focus:ring-gray-500;
}
.btn-large {
@apply px-6 py-3 text-lg;
}
.btn-small {
@apply px-3 py-1 text-sm;
}
}
/* Card component styles */
@layer components {
.card {
@apply bg-white rounded-lg shadow-md overflow-hidden;
@apply dark:bg-gray-800;
}
.card-header {
@apply px-6 py-4 border-b border-gray-200;
@apply dark:border-gray-700;
}
.card-body {
@apply px-6 py-4;
}
.card-footer {
@apply px-6 py-4 bg-gray-50 border-t border-gray-200;
@apply dark:bg-gray-700 dark:border-gray-600;
}
}
Practical Application Examples
Blog Article Card
---
// src/components/BlogCard.astro
export interface Props {
title: string;
excerpt: string;
publishDate: string;
author: string;
tags: string[];
slug: string;
}
const { title, excerpt, publishDate, author, tags, slug } = Astro.props;
---
<article class="card hover:shadow-lg transition-shadow duration-300">
<!-- Article header -->
<div class="card-header">
<div class="flex items-center justify-between">
<time class="text-sm text-gray-500 dark:text-gray-400">
{new Date(publishDate).toLocaleDateString('en-US')}
</time>
<span class="text-sm text-gray-500 dark:text-gray-400">
Author: {author}
</span>
</div>
</div>
<!-- Article content -->
<div class="card-body">
<h3 class="text-xl font-semibold mb-3 text-gray-900 dark:text-white">
<a
href={`/blog/${slug}`}
class="hover:text-primary-600 transition-colors duration-200"
>
{title}
</a>
</h3>
<p class="text-gray-600 dark:text-gray-300 mb-4 line-clamp-3">
{excerpt}
</p>
<!-- Tags -->
<div class="flex flex-wrap gap-2">
{tags.map((tag) => (
<span class="
inline-block px-2 py-1 text-xs font-medium
bg-primary-100 text-primary-700 rounded-full
dark:bg-primary-900 dark:text-primary-200
">
#{tag}
</span>
))}
</div>
</div>
<!-- Article footer -->
<div class="card-footer">
<a
href={`/blog/${slug}`}
class="btn btn-primary btn-small inline-flex items-center"
>
Read More
<svg class="w-4 h-4 ml-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</a>
</div>
</article>
Responsive Hero Section
---
// src/components/Hero.astro
export interface Props {
title: string;
subtitle: string;
ctaText: string;
ctaLink: string;
}
const { title, subtitle, ctaText, ctaLink } = Astro.props;
---
<section class="
relative bg-gradient-to-br from-primary-50 to-primary-100
dark:from-gray-900 dark:to-gray-800
py-20 sm:py-24 lg:py-32
">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center">
<!-- Main title -->
<h1 class="
text-4xl sm:text-5xl md:text-6xl font-bold
text-gray-900 dark:text-white
mb-6 leading-tight
">
<span class="block">{title}</span>
</h1>
<!-- Subtitle -->
<p class="
max-w-3xl mx-auto text-lg sm:text-xl
text-gray-600 dark:text-gray-300
mb-8 leading-relaxed
">
{subtitle}
</p>
<!-- CTA button group -->
<div class="
flex flex-col sm:flex-row gap-4
justify-center items-center
">
<a
href={ctaLink}
class="
btn btn-primary btn-large
w-full sm:w-auto
shadow-lg hover:shadow-xl
"
>
{ctaText}
</a>
<a
href="/learn-more"
class="
btn btn-secondary btn-large
w-full sm:w-auto
"
>
Learn More
</a>
</div>
</div>
</div>
<!-- Decorative background elements -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="
absolute -top-40 -right-40 w-80 h-80
bg-primary-200 rounded-full mix-blend-multiply
filter blur-xl opacity-70 animate-blob
"></div>
<div class="
absolute -bottom-40 -left-40 w-80 h-80
bg-accent-200 rounded-full mix-blend-multiply
filter blur-xl opacity-70 animate-blob animation-delay-2000
"></div>
</div>
</section>
<style>
@keyframes blob {
0% { transform: translate(0px, 0px) scale(1); }
33% { transform: translate(30px, -50px) scale(1.1); }
66% { transform: translate(-20px, 20px) scale(0.9); }
100% { transform: translate(0px, 0px) scale(1); }
}
.animate-blob {
animation: blob 7s infinite;
}
.animation-delay-2000 {
animation-delay: 2s;
}
</style>
Performance Optimization Strategies
PurgeCSS Configuration
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}',
// Ensure dynamic content is included
'./src/content/**/*.md',
],
// Safelist important dynamic classes
safelist: [
'prose',
'prose-lg',
/^prose-/,
]
}
CSS Splitting Strategy
---
// src/layouts/BaseLayout.astro
// Load specific styles only when needed
const { pageType } = Astro.props;
---
<html>
<head>
<!-- Base styles -->
<link rel="stylesheet" href="/styles/base.css">
<!-- Conditional style loading -->
{pageType === 'blog' && (
<link rel="stylesheet" href="/styles/blog.css">
)}
{pageType === 'portfolio' && (
<link rel="stylesheet" href="/styles/portfolio.css">
)}
</head>
<body>
<slot />
</body>
</html>
Key Points
- Mobile First: Always start with mobile design and expand to larger screens
- Performance Optimization: Use PurgeCSS to remove unused styles
- Design System: Establish consistent color, font, and spacing standards
- Component-Based: Create reusable component styles through @layer directive
- Dark Mode: Use CSS variables to implement theme switching
Important Considerations
- Avoid using
!importantin Tailwind, prioritize specificity management - Pay attention to safelist configuration for dynamic class names to prevent PurgeCSS from removing them
- Enable CSS compression and optimization in production environments
- Test style compatibility across different devices and browsers