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
  1. Mobile First: Always start with mobile design and expand to larger screens
  2. Performance Optimization: Use PurgeCSS to remove unused styles
  3. Design System: Establish consistent color, font, and spacing standards
  4. Component-Based: Create reusable component styles through @layer directive
  5. Dark Mode: Use CSS variables to implement theme switching
Important Considerations
  • Avoid using !important in 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