跳至主要內容

Tailwind CSS 自定義設計系統:從 Token 到元件庫

10 分鐘閱讀 1,100 字

Tailwind CSS 自定義設計系統:從 Token 到元件庫

Tailwind CSS 不只是一堆工具類別,它更是一個高度可客製化的設計系統框架。透過 tailwind.config.js 的自定義,你可以建立一套完全符合品牌規範的設計系統,同時保留 Tailwind 帶來的開發效率。

為什麼要自定義設計系統

直接使用 Tailwind 預設值雖然快速,但在真實產品中通常需要:

  • 使用品牌特定的色彩(非 Tailwind 預設色板)
  • 統一的間距和字體規範
  • 專案特定的 Breakpoints
  • 自訂的動畫效果

設計 Token 的概念

設計 Token 是設計決策的最小單位,如顏色、間距、字體大小。在 Tailwind 中,tailwind.config.js 就是你的 Token 定義檔:

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./src/**/*.{html,js,ts,vue,tsx}'],
  theme: {
    // extend 表示在預設值之上新增,不寫 extend 則完全覆蓋
    extend: {
      colors: {
        // 品牌色系
        brand: {
          50:  '#f0f9ff',
          100: '#e0f2fe',
          200: '#bae6fd',
          300: '#7dd3fc',
          400: '#38bdf8',
          500: '#0ea5e9',  // 主色
          600: '#0284c7',
          700: '#0369a1',
          800: '#075985',
          900: '#0c4a6e',
          950: '#082f49',
        },
        // 語意色彩
        surface: {
          DEFAULT: '#ffffff',
          secondary: '#f8fafc',
          tertiary: '#f1f5f9',
        },
        content: {
          DEFAULT: '#0f172a',
          secondary: '#475569',
          tertiary: '#94a3b8',
          inverse: '#ffffff',
        }
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
        mono: ['JetBrains Mono', 'monospace'],
      },
      fontSize: {
        // 使用 clamp() 實現響應式字體
        'display-lg': ['clamp(2.5rem, 5vw, 4rem)', { lineHeight: '1.1' }],
        'display-md': ['clamp(2rem, 4vw, 3rem)', { lineHeight: '1.2' }],
      },
      spacing: {
        '4.5': '1.125rem',  // 18px,填補 4(16px)和 5(20px)的空缺
        '13': '3.25rem',
        '18': '4.5rem',
      },
      borderRadius: {
        '4xl': '2rem',
      },
      boxShadow: {
        'soft': '0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04)',
        'card': '0 0 0 1px rgba(0,0,0,.03), 0 2px 4px rgba(0,0,0,.05), 0 12px 24px rgba(0,0,0,.05)',
      },
      animation: {
        'fade-in': 'fadeIn 0.3s ease-in-out',
        'slide-up': 'slideUp 0.3s ease-out',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        slideUp: {
          '0%': { transform: 'translateY(10px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        }
      }
    }
  },
  plugins: []
}

CSS 變數整合:深色模式支援

/* src/styles/tokens.css */
:root {
  --color-brand: theme('colors.brand.500');
  --color-surface: theme('colors.surface.DEFAULT');
  --color-content: theme('colors.content.DEFAULT');
}

.dark {
  --color-surface: #0f172a;
  --color-surface-secondary: #1e293b;
  --color-content: #f8fafc;
  --color-content-secondary: #94a3b8;
}
// tailwind.config.js
extend: {
  colors: {
    // 使用 CSS 變數,自動響應深色模式
    adaptive: {
      surface: 'var(--color-surface)',
      content: 'var(--color-content)',
    }
  }
}

元件層(Components Layer)

使用 @layer components 建立可複用的元件類別,保留 Tailwind 的 JIT 和 PurgeCSS 優化:

/* src/styles/components.css */
@layer components {
  /* 按鈕基礎樣式 */
  .btn {
    @apply inline-flex items-center justify-center
           px-4 py-2 rounded-lg font-medium text-sm
           transition-all duration-200 cursor-pointer
           focus:outline-none focus:ring-2 focus:ring-offset-2
           disabled:opacity-50 disabled:cursor-not-allowed;
  }

  .btn-primary {
    @apply btn bg-brand-500 text-white
           hover:bg-brand-600 active:bg-brand-700
           focus:ring-brand-500;
  }

  .btn-secondary {
    @apply btn bg-surface-secondary text-content
           border border-slate-200
           hover:bg-surface-tertiary
           focus:ring-brand-500;
  }

  .btn-ghost {
    @apply btn text-content-secondary
           hover:bg-surface-secondary
           focus:ring-brand-500;
  }

  /* 卡片 */
  .card {
    @apply bg-white rounded-2xl shadow-card p-6;
  }

  /* 表單輸入 */
  .input {
    @apply w-full px-3 py-2 text-sm
           border border-slate-200 rounded-lg
           bg-white text-content
           placeholder-content-tertiary
           focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent
           transition-shadow duration-200;
  }

  .input-error {
    @apply input border-red-300 focus:ring-red-500;
  }
}

工具類別層(Utilities Layer)

@layer utilities {
  /* 文字截斷 */
  .text-clamp-2 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* 滾動行為 */
  .scroll-smooth-x {
    scroll-behavior: smooth;
    overflow-x: auto;
    scrollbar-width: thin;
  }
}

在 Vue 元件中使用

<template>
  <div class="card animate-fade-in">
    <h2 class="text-display-md text-content font-bold mb-4">
      {{ title }}
    </h2>
    <p class="text-content-secondary text-clamp-2">
      {{ description }}
    </p>
    <div class="flex gap-3 mt-6">
      <button class="btn-primary">
        開始使用
      </button>
      <button class="btn-ghost">
        了解更多
      </button>
    </div>
  </div>
</template>

建立設計系統文件

搭配 Storybook 或簡單的靜態頁面記錄設計系統:

# 安裝 Storybook(Vue)
npx storybook@latest init

# 安裝 Tailwind 插件
npm install -D @storybook/addon-styling-webpack

Tailwind Plugins:擴充功能

// tailwind.config.js
import plugin from 'tailwindcss/plugin'

export default {
  plugins: [
    // 官方插件
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),

    // 自訂插件:新增 text-shadow 工具類
    plugin(function({ addUtilities, theme }) {
      addUtilities({
        '.text-shadow-sm': { textShadow: '0 1px 2px rgba(0,0,0,0.1)' },
        '.text-shadow': { textShadow: '0 2px 4px rgba(0,0,0,0.15)' },
        '.text-shadow-none': { textShadow: 'none' },
      })
    })
  ]
}

總結

Tailwind 的強大不在於預設的工具類別,而在於它讓設計 Token 與程式碼緊密整合的架構。透過 tailwind.config.js 定義品牌色彩和間距、用 @layer components 封裝元件樣式、搭配 CSS 變數支援深色模式,你可以在保持 Tailwind 開發效率的同時,建立出一套完整、一致的設計系統。

分享這篇文章