25/04/2026 02:47น.

EP 8: ปรับแต่ง Tailwind ให้เป็นของตัวเอง: Theme และ Plugin แบบเซียน
#Tailwind CSS
#theme customization
#css variables
#Design Systems
#custom colors
ถึงเวลาแล้วที่เราจะก้าวขึ้นสู่ระดับมืออาชีพของ Tailwind CSS! หลังจากที่เรียนรู้พื้นฐานและเทคนิคต่างๆ มาแล้ว วันนี้เราจะมาเรียนรู้การปรับแต่ง Tailwind CSS ให้ตอบโจทย์โปรเจ็กต์ของเราอย่างสมบูรณ์แบบ
การปรับแต่ง Tailwind CSS ทำได้หลายวิธี ตั้งแต่การใช้ไฟล์ tailwind.config.js ไปจนถึงการสร้าง Custom CSS และการใช้ Plugin ต่างๆ มาดูกันว่าเราจะทำให้ Tailwind CSS เป็นของเราได้อย่างไร
การปรับแต่ง Theme ใน Tailwind CSS
การใช้ tailwind.config.js
ไฟล์ tailwind.config.js เป็นหัวใจสำคัญในการปรับแต่ง Tailwind CSS ให้เหมาะกับโปรเจ็กต์:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{html,js,jsx,ts,tsx,vue}',
'./pages/**/*.{html,js,jsx,ts,tsx,vue}',
'./components/**/*.{html,js,jsx,ts,tsx,vue}'
],
theme: {
extend: {
// Custom Colors
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
950: '#172554'
},
secondary: {
50: '#fdf4ff',
100: '#fae8ff',
200: '#f5d0fe',
300: '#f0abfc',
400: '#e879f9',
500: '#d946ef',
600: '#c026d3',
700: '#a21caf',
800: '#86198f',
900: '#701a75',
950: '#4a044e'
},
gray: {
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827',
950: '#030712'
}
},
// Custom Fonts
fontFamily: {
sans: ['Inter Variable', 'Inter', 'ui-sans-serif', 'system-ui', 'sans-serif'],
serif: ['Playfair Display Variable', 'Playfair Display', 'ui-serif', 'serif'],
mono: ['JetBrains Mono Variable', 'JetBrains Mono', 'ui-monospace', 'monospace'],
display: ['Cal Sans', 'Inter Variable', 'Inter', 'ui-sans-serif', 'system-ui', 'sans-serif']
},
// Custom Spacing
spacing: {
'18': '4.5rem',
'88': '22rem',
'128': '32rem'
},
// Custom Shadows
boxShadow: {
'soft': '0 2px 15px rgba(0, 0, 0, 0.08)',
'medium': '0 4px 25px rgba(0, 0, 0, 0.15)',
'hard': '0 10px 40px rgba(0, 0, 0, 0.2)',
'glow': '0 0 20px rgba(59, 130, 246, 0.3)',
'glow-green': '0 0 20px rgba(34, 197, 94, 0.3)',
'glow-red': '0 0 20px rgba(239, 68, 68, 0.3)'
},
// Custom Border Radius
borderRadius: {
'xl': '1rem',
'2xl': '1.5rem',
'3xl': '2rem',
'4xl': '2.5rem'
},
// Custom Animations
animation: {
'fade-in': 'fadeIn 0.5s ease-out',
'slide-up': 'slideUp 0.5s ease-out',
'slide-down': 'slideDown 0.5s ease-out',
'slide-left': 'slideLeft 0.5s ease-out',
'slide-right': 'slideRight 0.5s ease-out',
'bounce-in': 'bounceIn 0.6s ease-out',
'float': 'float 3s ease-in-out infinite'
},
// Custom Keyframes
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' }
},
slideUp: {
'0%': { opacity: '0', transform: 'translateY(30px)' },
'100%': { opacity: '1', transform: 'translateY(0)' }
},
slideDown: {
'0%': { opacity: '0', transform: 'translateY(-30px)' },
'100%': { opacity: '1', transform: 'translateY(0)' }
},
slideLeft: {
'0%': { opacity: '0', transform: 'translateX(30px)' },
'100%': { opacity: '1', transform: 'translateX(0)' }
},
slideRight: {
'0%': { opacity: '0', transform: 'translateX(-30px)' },
'100%': { opacity: '1', transform: 'translateX(0)' }
},
bounceIn: {
'0%': { opacity: '0', transform: 'scale(0.3)' },
'50%': { opacity: '1', transform: 'scale(1.05)' },
'70%': { transform: 'scale(0.9)' },
'100%': { opacity: '1', transform: 'scale(1)' }
},
float: {
'0%, 100%': { transform: 'translateY(0px)' },
'50%': { transform: 'translateY(-10px)' }
}
},
// Custom Breakpoints
screens: {
'xs': '475px',
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
'3xl': '1920px'
}
}
},
darkMode: 'class',
plugins: []
}
การใช้งาน Custom Theme
<!-- ใช้สีที่เราสร้างขึ้น -->
<div class="bg-primary-500 hover:bg-primary-600 text-white p-6 rounded-2xl shadow-soft">
<h3 class="font-display text-xl font-semibold mb-2">Custom Primary Color</h3>
<p class="text-primary-100">ใช้สีและฟอนต์ที่กำหนดเอง</p>
</div>
<!-- ใช้ animation ที่สร้างขึ้น -->
<div class="animate-fade-in">
<h1 class="text-4xl font-display font-bold animate-slide-up">
Welcome Animation
</h1>
<p class="text-lg animate-slide-up animation-delay-200">
Text ที่เลื่อนขึ้นมา
</p>
</div>
<!-- ใช้ spacing และ shadow ที่กำหนดเอง -->
<div class="p-18 m-88 shadow-glow rounded-3xl">
<div class="w-128 h-32 bg-gradient-to-r from-primary-500 to-secondary-500">
Custom spacing และ shadow
</div>
</div>
การสร้าง Design Tokens ระบบ
Color System แบบมืออาชีพ
// tailwind.config.js - Advanced Color System
module.exports = {
theme: {
extend: {
colors: {
// Brand Colors
brand: {
primary: '#3b82f6',
secondary: '#8b5cf6',
accent: '#f59e0b'
},
// Semantic Colors
success: {
50: '#f0fdf4',
100: '#dcfce7',
500: '#22c55e',
600: '#16a34a',
700: '#15803d'
},
warning: {
50: '#fffbeb',
100: '#fef3c7',
500: '#f59e0b',
600: '#d97706',
700: '#b45309'
},
error: {
50: '#fef2f2',
100: '#fee2e2',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c'
},
info: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8'
},
// Neutral Colors
neutral: {
0: '#ffffff',
50: '#fafafa',
100: '#f4f4f5',
200: '#e4e4e7',
300: '#d4d4d8',
400: '#a1a1aa',
500: '#71717a',
600: '#52525b',
700: '#3f3f46',
800: '#27272a',
900: '#18181b',
1000: '#000000'
}
}
}
}
}
Typography System
// Typography Configuration
module.exports = {
theme: {
extend: {
fontFamily: {
// Primary Font Stack
sans: [
'Inter Variable',
'Inter',
'-apple-system',
'BlinkMacSystemFont',
'Segoe UI',
'Roboto',
'Helvetica Neue',
'Arial',
'sans-serif'
],
// Display Font
display: [
'Cal Sans',
'Inter Variable',
'Inter',
'ui-sans-serif',
'system-ui',
'sans-serif'
],
// Serif Font
serif: [
'Playfair Display Variable',
'Playfair Display',
'ui-serif',
'Georgia',
'Cambria',
'serif'
],
// Monospace Font
mono: [
'JetBrains Mono Variable',
'JetBrains Mono',
'SF Mono',
'Monaco',
'Inconsolata',
'Roboto Mono',
'monospace'
]
},
fontSize: {
// Custom Font Sizes
'xs': ['0.75rem', { lineHeight: '1rem' }],
'sm': ['0.875rem', { lineHeight: '1.25rem' }],
'base': ['1rem', { lineHeight: '1.5rem' }],
'lg': ['1.125rem', { lineHeight: '1.75rem' }],
'xl': ['1.25rem', { lineHeight: '1.75rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
'5xl': ['3rem', { lineHeight: '1' }],
'6xl': ['3.75rem', { lineHeight: '1' }],
'7xl': ['4.5rem', { lineHeight: '1' }],
'8xl': ['6rem', { lineHeight: '1' }],
'9xl': ['8rem', { lineHeight: '1' }]
},
letterSpacing: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0em',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em'
}
}
}
}
การสร้าง Custom Utilities
ใช้ @layer utilities
/* input.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
/* Text Utilities */
.text-shadow {
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.text-shadow-lg {
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
/* Scroll Utilities */
.scroll-smooth {
scroll-behavior: smooth;
}
.scroll-snap-x {
scroll-snap-type: x mandatory;
}
.scroll-snap-start {
scroll-snap-align: start;
}
/* Glass Morphism */
.glass {
backdrop-filter: blur(16px) saturate(180%);
background-color: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.125);
}
.glass-dark {
backdrop-filter: blur(16px) saturate(180%);
background-color: rgba(17, 24, 39, 0.75);
border: 1px solid rgba(255, 255, 255, 0.125);
}
/* Custom Aspect Ratios */
.aspect-golden {
aspect-ratio: 1.618 / 1;
}
.aspect-4-3 {
aspect-ratio: 4 / 3;
}
/* Background Patterns */
.bg-grid {
background-image:
linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px);
background-size: 20px 20px;
}
.bg-dots {
background-image: radial-gradient(circle, rgba(0, 0, 0, 0.1) 1px, transparent 1px);
background-size: 20px 20px;
}
/* Animation Delays */
.animation-delay-75 {
animation-delay: 75ms;
}
.animation-delay-100 {
animation-delay: 100ms;
}
.animation-delay-150 {
animation-delay: 150ms;
}
.animation-delay-200 {
animation-delay: 200ms;
}
.animation-delay-300 {
animation-delay: 300ms;
}
.animation-delay-500 {
animation-delay: 500ms;
}
/* Focus Utilities */
.focus-ring {
@apply focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2;
}
.focus-ring-error {
@apply focus:outline-none focus:ring-2 focus:ring-error-500 focus:ring-offset-2;
}
/* Gradient Text */
.text-gradient {
background: linear-gradient(90deg, #3b82f6, #8b5cf6);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
การใช้งาน Custom Utilities
<!-- Glass Morphism Effects -->
<div class="glass p-8 rounded-2xl backdrop-blur-sm">
<h3 class="text-xl font-semibold text-gray-800">Glass Card</h3>
<p class="text-gray-600 mt-2">Modern glassmorphism effect</p>
</div>
<!-- Text Effects -->
<h1 class="text-6xl font-bold text-gradient text-shadow-lg">
Gradient Text with Shadow
</h1>
<!-- Custom Animations with Delays -->
<div class="space-y-4">
<div class="animate-slide-up animation-delay-100">First Item</div>
<div class="animate-slide-up animation-delay-200">Second Item</div>
<div class="animate-slide-up animation-delay-300">Third Item</div>
</div>
<!-- Background Patterns -->
<div class="bg-grid bg-gray-50 p-8 rounded-lg">
<div class="bg-white p-6 rounded-lg shadow-soft">
Content on Grid Background
</div>
</div>
<!-- Custom Aspect Ratios -->
<div class="aspect-golden bg-gradient-to-br from-primary-500 to-secondary-500 rounded-lg">
Golden Ratio Container
</div>
การจัดการ Dark Mode ขั้นสูง
Dark Mode Configuration
// tailwind.config.js
module.exports = {
darkMode: 'class', // หรือ 'media' สำหรับ system preference
theme: {
extend: {
colors: {
// Colors ที่เปลี่ยนตาม mode
surface: {
DEFAULT: '#ffffff',
dark: '#1a1a1a'
},
'surface-elevated': {
DEFAULT: '#fafafa',
dark: '#2a2a2a'
},
'text-primary': {
DEFAULT: '#1a1a1a',
dark: '#fafafa'
},
'text-secondary': {
DEFAULT: '#6a6a6a',
dark: '#a0a0a0'
},
'border-default': {
DEFAULT: '#e0e0e0',
dark: '#404040'
}
}
}
}
}
Custom Dark Mode Utilities
@layer utilities {
.bg-surface {
@apply bg-white dark:bg-gray-900;
}
.bg-surface-elevated {
@apply bg-gray-50 dark:bg-gray-800;
}
.text-primary {
@apply text-gray-900 dark:text-gray-100;
}
.text-secondary {
@apply text-gray-600 dark:text-gray-400;
}
.border-default {
@apply border-gray-200 dark:border-gray-700;
}
.card-surface {
@apply bg-surface border-default shadow-sm;
}
}
Dark Mode Toggle Implementation
<!-- Dark Mode Toggle -->
<button id="theme-toggle" class="p-2 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 focus-ring">
<!-- Sun Icon (Light Mode) -->
<svg class="w-5 h-5 dark:hidden" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z" />
</svg>
<!-- Moon Icon (Dark Mode) -->
<svg class="w-5 h-5 hidden dark:block" fill="currentColor" viewBox="0 0 24 24">
<path d="M9.528 1.718a.75.75 0 01.162.819A8.97 8.97 0 009 6a9 9 0 009 9 8.97 8.97 0 003.463-.69.75.75 0 01.981.98 10.503 10.503 0 01-9.694 6.46c-5.799 0-10.5-4.701-10.5-10.5 0-4.368 2.667-8.112 6.46-9.694a.75.75 0 01.818.162z" />
</svg>
</button>
<script>
// Dark Mode Toggle Script
const themeToggle = document.getElementById('theme-toggle');
const html = document.documentElement;
// Check for saved theme or default to system preference
const savedTheme = localStorage.getItem('theme');
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (savedTheme === 'dark' || (!savedTheme && systemPrefersDark)) {
html.classList.add('dark');
}
themeToggle.addEventListener('click', () => {
html.classList.toggle('dark');
// Save preference to localStorage
if (html.classList.contains('dark')) {
localStorage.setItem('theme', 'dark');
} else {
localStorage.setItem('theme', 'light');
}
});
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
if (e.matches) {
html.classList.add('dark');
} else {
html.classList.remove('dark');
}
}
});
</script>
การใช้งาน Plugin Ecosystem
Official Plugins ที่แนะนำ
// tailwind.config.js
module.exports = {
plugins: [
// Typography Plugin - สำหรับ Rich Text Content
require('@tailwindcss/typography'),
// Forms Plugin - ปรับปรุง Form Elements
require('@tailwindcss/forms'),
// Aspect Ratio Plugin - จัดการอัตราส่วน
require('@tailwindcss/aspect-ratio'),
// Line Clamp Plugin - จำกัดบรรทัด
require('@tailwindcss/line-clamp'),
// Container Queries Plugin
require('@tailwindcss/container-queries')
]
}
การใช้งาน Typography Plugin
<!-- Rich Text Content -->
<article class="prose prose-lg prose-blue max-w-none dark:prose-invert">
<h1>หัวข้อบทความ</h1>
<p class="lead">ข้อความนำที่โดดเด่น</p>
<h2>หัวข้อย่อย</h2>
<p>เนื้อหาบทความที่มีการจัดรูปแบบอัตโนมัติ รองรับ <strong>ตัวหนา</strong>, <em>ตัวเอียง</em>, และ <code>โค้ด</code></p>
<blockquote>
<p>Quote ที่สวยงามและโดดเด่น</p>
</blockquote>
<ul>
<li>รายการที่มีการจัดรูปแบบอัตโนมัติ</li>
<li>ง่ายต่อการใช้งาน</li>
<li>รองรับ Dark Mode</li>
</ul>
<pre><code class="language-javascript">
const message = "Hello, World!";
console.log(message);
</code></pre>
</article>
<!-- Custom Prose Variants -->
<div class="prose prose-primary max-w-4xl mx-auto">
<!-- Content จะใช้ primary color scheme -->
</div>
<!-- Small Prose -->
<div class="prose prose-sm max-w-2xl">
<!-- Smaller text for compact content -->
</div>
การใช้งาน Container Queries
<!-- Container ที่ปรับตามขนาดของตัวเอง -->
<div class="@container max-w-4xl mx-auto">
<div class="grid grid-cols-1 @sm:grid-cols-2 @lg:grid-cols-3 @xl:grid-cols-4 gap-4">
<div class="bg-white p-4 rounded-lg shadow-sm">
<img class="w-full aspect-square @sm:aspect-video object-cover rounded mb-3"
src="https://images.unsplash.com/photo-1518837695005-2083093ee35b?w=400"
alt="Product Image"
loading="lazy">
<h3 class="font-semibold text-sm @sm:text-base @lg:text-lg">Product Name</h3>
<p class="text-gray-600 text-xs @sm:text-sm mt-1">Product description</p>
<div class="mt-3">
<button class="w-full @sm:w-auto px-4 py-2 bg-primary-500 text-white rounded text-sm hover:bg-primary-600 transition-colors">
Add to Cart
</button>
</div>
</div>
</div>
</div>
<!-- Named Container Queries -->
<div class="@container/sidebar w-64">
<nav class="space-y-1">
<a href="#" class="block p-2 @lg/sidebar:p-3 rounded hover:bg-gray-100 transition-colors">
<div class="flex items-center space-x-2 @lg/sidebar:space-x-3">
<svg class="w-4 h-4 @lg/sidebar:w-5 @lg/sidebar:h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"/>
</svg>
<span class="text-sm @lg/sidebar:text-base">Navigation Item</span>
</div>
</a>
</nav>
</div>
Third-Party Plugins ที่น่าสนใจ
// tailwind.config.js
module.exports = {
plugins: [
// Tailwind CSS Animate - Animation utilities
require('tailwindcss-animate'),
// Tailwind CSS Radix - Radix UI integration
require('tailwindcss-radix'),
// Headless UI - Integration with Headless UI
require('@headlessui/tailwindcss'),
// Custom Plugin สำหรับ Scrollbar
require('tailwind-scrollbar'),
// Custom Plugin Example
function({ addUtilities, theme }) {
const newUtilities = {
'.text-stroke': {
'-webkit-text-stroke': '1px currentColor',
},
'.text-stroke-2': {
'-webkit-text-stroke': '2px currentColor',
},
'.backdrop-blur-xs': {
'backdrop-filter': 'blur(2px)',
},
}
addUtilities(newUtilities)
}
]
}
การสร้าง Component System ขั้นสูง
Advanced Component Architecture
/* components.css */
@layer components {
/* Button System */
.btn {
@apply inline-flex items-center justify-center font-medium rounded-lg transition-all duration-200;
@apply focus:outline-none focus:ring-2 focus:ring-offset-2;
@apply disabled:opacity-50 disabled:cursor-not-allowed;
}
.btn-primary {
@apply bg-primary-500 text-white shadow-sm;
@apply hover:bg-primary-600 hover:shadow-md hover:-translate-y-0.5;
@apply focus:ring-primary-500;
@apply active:bg-primary-700 active:translate-y-0;
}
.btn-secondary {
@apply bg-secondary-500 text-white shadow-sm;
@apply hover:bg-secondary-600 hover:shadow-md hover:-translate-y-0.5;
@apply focus:ring-secondary-500;
}
.btn-outline {
@apply border-2 bg-transparent shadow-sm;
@apply hover:shadow-md hover:-translate-y-0.5;
}
.btn-outline-primary {
@apply border-primary-500 text-primary-500;
@apply hover:bg-primary-500 hover:text-white;
@apply focus:ring-primary-500;
}
.btn-ghost {
@apply bg-transparent shadow-none;
@apply hover:bg-gray-100 dark:hover:bg-gray-800;
@apply focus:ring-gray-500;
}
/* Button Sizes */
.btn-xs {
@apply px-2 py-1 text-xs;
}
.btn-sm {
@apply px-3 py-1.5 text-sm;
}
.btn-md {
@apply px-4 py-2 text-base;
}
.btn-lg {
@apply px-6 py-3 text-lg;
}
.btn-xl {
@apply px-8 py-4 text-xl;
}
/* Card System */
.card {
@apply bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700;
@apply overflow-hidden transition-all duration-300;
}
.card-hover {
@apply hover:shadow-lg hover:-translate-y-1;
}
.card-interactive {
@apply cursor-pointer;
@apply hover:shadow-xl hover:-translate-y-2;
@apply active:translate-y-0 active:shadow-md;
}
.card-glass {
@apply glass border-white/20 dark:border-gray-700/30;
}
.card-header {
@apply px-6 py-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50;
}
.card-body {
@apply px-6 py-4;
}
.card-footer {
@apply px-6 py-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/50;
}
/* Input System */
.input {
@apply w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg;
@apply bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100;
@apply focus:ring-2 focus:ring-primary-500 focus:border-primary-500;
@apply transition-colors duration-200;
@apply placeholder:text-gray-400 dark:placeholder:text-gray-500;
}
.input-error {
@apply border-error-500 focus:ring-error-500 focus:border-error-500;
}
.input-success {
@apply border-success-500 focus:ring-success-500 focus:border-success-500;
}
/* Alert System */
.alert {
@apply p-4 rounded-lg border flex items-start space-x-3;
}
.alert-success {
@apply bg-success-50 dark:bg-success-900/20 border-success-200 dark:border-success-800 text-success-800 dark:text-success-200;
}
.alert-warning {
@apply bg-warning-50 dark:bg-warning-900/20 border-warning-200 dark:border-warning-800 text-warning-800 dark:text-warning-200;
}
.alert-error {
@apply bg-error-50 dark:bg-error-900/20 border-error-200 dark:border-error-800 text-error-800 dark:text-error-200;
}
.alert-info {
@apply bg-info-50 dark:bg-info-900/20 border-info-200 dark:border-info-800 text-info-800 dark:text-info-200;
}
/* Badge System */
.badge {
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
}
.badge-primary {
@apply bg-primary-100 dark:bg-primary-900/30 text-primary-800 dark:text-primary-200;
}
.badge-success {
@apply bg-success-100 dark:bg-success-900/30 text-success-800 dark:text-success-200;
}
.badge-warning {
@apply bg-warning-100 dark:bg-warning-900/30 text-warning-800 dark:text-warning-200;
}
.badge-error {
@apply bg-error-100 dark:bg-error-900/30 text-error-800 dark:text-error-200;
}
/* Navigation */
.nav-link {
@apply px-3 py-2 rounded-md text-sm font-medium;
@apply text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100;
@apply hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors;
}
.nav-link-active {
@apply text-primary-600 dark:text-primary-400 bg-primary-50 dark:bg-primary-900/30;
@apply hover:text-primary-700 dark:hover:text-primary-300 hover:bg-primary-100 dark:hover:bg-primary-900/50;
}
}
การสร้าง Design System สมบูรณ์
Project Structure
project/
├── src/
│ ├── styles/
│ │ ├── tailwind.css # Main Tailwind file
│ │ ├── components/
│ │ │ ├── buttons.css # Button components
│ │ │ ├── cards.css # Card components
│ │ │ ├── forms.css # Form components
│ │ │ └── navigation.css # Navigation components
│ │ ├── utilities/
│ │ │ ├── animations.css # Custom animations
│ │ │ ├── effects.css # Visual effects
│ │ │ └── helpers.css # Helper utilities
│ │ └── tokens/
│ │ ├── colors.css # Color tokens
│ │ ├── typography.css # Typography tokens
│ │ └── spacing.css # Spacing tokens
│ ├── components/ # Framework components
│ └── pages/ # Application pages
├── docs/
│ ├── design-system.html # Design system documentation
│ └── style-guide.html # Style guide
├── tailwind.config.js # Tailwind configuration
└── package.json
Main Tailwind File
/* src/styles/tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Import Design Tokens */
@import './tokens/colors.css';
@import './tokens/typography.css';
@import './tokens/spacing.css';
/* Import Components */
@import './components/buttons.css';
@import './components/cards.css';
@import './components/forms.css';
@import './components/navigation.css';
/* Import Custom Utilities */
@import './utilities/animations.css';
@import './utilities/effects.css';
@import './utilities/helpers.css';
@layer base {
/* Global Base Styles */
html {
@apply scroll-smooth antialiased;
}
body {
@apply font-sans text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-900;
@apply transition-colors duration-300;
}
/* Focus Styles */
*:focus {
@apply outline-none;
}
/* Selection Styles */
::selection {
@apply bg-primary-500 text-white;
}
/* Scrollbar Styles */
::-webkit-scrollbar {
@apply w-2;
}
::-webkit-scrollbar-track {
@apply bg-gray-100 dark:bg-gray-800;
}
::-webkit-scrollbar-thumb {
@apply bg-gray-300 dark:bg-gray-600 rounded-full;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-gray-400 dark:bg-gray-500;
}
}
Performance Optimization
Tailwind Configuration สำหรับ Production
// tailwind.config.js - Production Optimized
module.exports = {
content: [
'./src/**/*.{html,js,jsx,ts,tsx,vue}',
'./pages/**/*.{html,js,jsx,ts,tsx,vue}',
'./components/**/*.{html,js,jsx,ts,tsx,vue}',
// Add any other paths where you use Tailwind classes
],
// Safelist for dynamic classes
safelist: [
'bg-primary-500',
'bg-secondary-500',
'text-error-500',
// Add classes that are generated dynamically
],
// Disable unused features for smaller build
corePlugins: {
// Disable if not used
preflight: true,
container: true,
accessibility: true,
appearance: true,
backgroundAttachment: false, // Disable if not used
backgroundClip: true,
backgroundImage: true,
backgroundOpacity: true,
backgroundPosition: true,
backgroundRepeat: true,
backgroundSize: true,
// ... configure based on usage
},
theme: {
// Use extend to add, not replace
extend: {
// Your custom theme here
}
},
plugins: [
// Only include plugins you actually use
require('@tailwindcss/typography'),
require('@tailwindcss/forms'),
]
}
Build Script Optimization
{
"scripts": {
"dev": "tailwindcss -i ./src/styles/tailwind.css -o ./dist/styles.css --watch",
"build": "NODE_ENV=production tailwindcss -i ./src/styles/tailwind.css -o ./dist/styles.css --minify",
"build:analyze": "tailwindcss -i ./src/styles/tailwind.css -o ./dist/styles.css --minify --verbose"
}
}
Component Documentation System
Style Guide Template
<!DOCTYPE html>
<html lang="th" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Design System Style Guide</title>
<link href="./dist/styles.css" rel="stylesheet">
</head>
<body class="bg-gray-50 dark:bg-gray-900">
<!-- Header -->
<header class="sticky top-0 z-50 bg-white/95 dark:bg-gray-900/95 backdrop-blur-sm border-b border-gray-200 dark:border-gray-700">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">Design System</h1>
<div class="flex items-center space-x-4">
<!-- Theme Toggle -->
<button id="theme-toggle" class="p-2 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors focus-ring">
<svg class="w-5 h-5 dark:hidden" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z" />
</svg>
<svg class="w-5 h-5 hidden dark:block" fill="currentColor" viewBox="0 0 24 24">
<path d="M9.528 1.718a.75.75 0 01.162.819A8.97 8.97 0 009 6a9 9 0 009 9 8.97 8.97 0 003.463-.69.75.75 0 01.981.98 10.503 10.503 0 01-9.694 6.46c-5.799 0-10.5-4.701-10.5-10.5 0-4.368 2.667-8.112 6.46-9.694a.75.75 0 01.818.162z" />
</svg>
</button>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
<!-- Sidebar Navigation -->
<nav class="lg:col-span-1">
<div class="sticky top-24 space-y-2">
<a href="#colors" class="nav-link block">Colors</a>
<a href="#typography" class="nav-link block">Typography</a>
<a href="#buttons" class="nav-link block">Buttons</a>
<a href="#cards" class="nav-link block">Cards</a>
<a href="#forms" class="nav-link block">Forms</a>
<a href="#alerts" class="nav-link block">Alerts</a>
<a href="#badges" class="nav-link block">Badges</a>
</div>
</nav>
<!-- Content -->
<main class="lg:col-span-3 space-y-16">
<!-- Colors Section -->
<section id="colors" class="animate-fade-in">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-8">Colors</h2>
<!-- Primary Colors -->
<div class="mb-8">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">Primary Colors</h3>
<div class="grid grid-cols-5 md:grid-cols-10 gap-3">
<div class="text-center">
<div class="w-full h-16 bg-primary-50 rounded-lg border border-gray-200 dark:border-gray-700 mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400">50</div>
</div>
<div class="text-center">
<div class="w-full h-16 bg-primary-100 rounded-lg mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400">100</div>
</div>
<div class="text-center">
<div class="w-full h-16 bg-primary-200 rounded-lg mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400">200</div>
</div>
<div class="text-center">
<div class="w-full h-16 bg-primary-300 rounded-lg mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400">300</div>
</div>
<div class="text-center">
<div class="w-full h-16 bg-primary-400 rounded-lg mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400">400</div>
</div>
<div class="text-center">
<div class="w-full h-16 bg-primary-500 rounded-lg mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400 font-semibold">500</div>
</div>
<div class="text-center">
<div class="w-full h-16 bg-primary-600 rounded-lg mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400">600</div>
</div>
<div class="text-center">
<div class="w-full h-16 bg-primary-700 rounded-lg mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400">700</div>
</div>
<div class="text-center">
<div class="w-full h-16 bg-primary-800 rounded-lg mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400">800</div>
</div>
<div class="text-center">
<div class="w-full h-16 bg-primary-900 rounded-lg mb-2"></div>
<div class="text-xs text-gray-600 dark:text-gray-400">900</div>
</div>
</div>
</div>
</section>
<!-- Typography Section -->
<section id="typography" class="animate-fade-in">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-8">Typography</h2>
<div class="space-y-6">
<div class="flex items-center space-x-6">
<div class="w-16 text-sm text-gray-500 dark:text-gray-400">9xl</div>
<div class="text-9xl font-bold text-gray-900 dark:text-gray-100">Aa</div>
</div>
<div class="flex items-center space-x-6">
<div class="w-16 text-sm text-gray-500 dark:text-gray-400">6xl</div>
<div class="text-6xl font-bold text-gray-900 dark:text-gray-100">Aa</div>
</div>
<div class="flex items-center space-x-6">
<div class="w-16 text-sm text-gray-500 dark:text-gray-400">4xl</div>
<div class="text-4xl font-bold text-gray-900 dark:text-gray-100">Aa</div>
</div>
<div class="flex items-center space-x-6">
<div class="w-16 text-sm text-gray-500 dark:text-gray-400">2xl</div>
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100">Aa</div>
</div>
<div class="flex items-center space-x-6">
<div class="w-16 text-sm text-gray-500 dark:text-gray-400">xl</div>
<div class="text-xl font-bold text-gray-900 dark:text-gray-100">Aa</div>
</div>
<div class="flex items-center space-x-6">
<div class="w-16 text-sm text-gray-500 dark:text-gray-400">base</div>
<div class="text-base font-medium text-gray-900 dark:text-gray-100">Aa</div>
</div>
<div class="flex items-center space-x-6">
<div class="w-16 text-sm text-gray-500 dark:text-gray-400">sm</div>
<div class="text-sm text-gray-900 dark:text-gray-100">Aa</div>
</div>
<div class="flex items-center space-x-6">
<div class="w-16 text-sm text-gray-500 dark:text-gray-400">xs</div>
<div class="text-xs text-gray-900 dark:text-gray-100">Aa</div>
</div>
</div>
</section>
<!-- Buttons Section -->
<section id="buttons" class="animate-fade-in">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-8">Buttons</h2>
<div class="space-y-8">
<!-- Primary Buttons -->
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Primary Buttons</h3>
<div class="flex flex-wrap gap-4">
<button class="btn btn-primary btn-xs">Extra Small</button>
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary btn-md">Medium</button>
<button class="btn btn-primary btn-lg">Large</button>
<button class="btn btn-primary btn-xl">Extra Large</button>
</div>
</div>
<!-- Secondary Buttons -->
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Secondary Buttons</h3>
<div class="flex flex-wrap gap-4">
<button class="btn btn-secondary btn-sm">Small</button>
<button class="btn btn-secondary btn-md">Medium</button>
<button class="btn btn-secondary btn-lg">Large</button>
</div>
</div>
<!-- Outline Buttons -->
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Outline Buttons</h3>
<div class="flex flex-wrap gap-4">
<button class="btn btn-outline btn-outline-primary btn-sm">Primary Outline</button>
<button class="btn btn-outline btn-outline-primary btn-md">Medium Outline</button>
<button class="btn btn-outline btn-outline-primary btn-lg">Large Outline</button>
</div>
</div>
<!-- Ghost Buttons -->
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Ghost Buttons</h3>
<div class="flex flex-wrap gap-4">
<button class="btn btn-ghost btn-sm">Small Ghost</button>
<button class="btn btn-ghost btn-md">Medium Ghost</button>
<button class="btn btn-ghost btn-lg">Large Ghost</button>
</div>
</div>
<!-- Button States -->
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Button States</h3>
<div class="flex flex-wrap gap-4">
<button class="btn btn-primary btn-md">Normal</button>
<button class="btn btn-primary btn-md hover:bg-primary-600" style="background-color: rgb(37 99 235);">Hover</button>
<button class="btn btn-primary btn-md" disabled>Disabled</button>
</div>
</div>
</div>
</section>
<!-- Cards Section -->
<section id="cards" class="animate-fade-in">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-8">Cards</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Basic Card -->
<div class="card">
<div class="card-body">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Basic Card</h3>
<p class="text-gray-600 dark:text-gray-400 mb-4">การ์ดพื้นฐานที่ใช้งานง่าย</p>
<button class="btn btn-primary btn-sm">Action</button>
</div>
</div>
<!-- Hover Card -->
<div class="card card-hover">
<div class="card-body">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Hover Card</h3>
<p class="text-gray-600 dark:text-gray-400 mb-4">การ์ดที่มี hover effect</p>
<button class="btn btn-primary btn-sm">Action</button>
</div>
</div>
<!-- Interactive Card -->
<div class="card card-interactive">
<div class="card-body">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Interactive Card</h3>
<p class="text-gray-600 dark:text-gray-400 mb-4">การ์ดที่คลิกได้</p>
<button class="btn btn-primary btn-sm">Action</button>
</div>
</div>
<!-- Card with Header/Footer -->
<div class="card md:col-span-2 lg:col-span-3">
<div class="card-header">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Card with Header & Footer</h3>
</div>
<div class="card-body">
<p class="text-gray-600 dark:text-gray-400">การ์ดที่มี header, body และ footer แยกกัน</p>
</div>
<div class="card-footer">
<div class="flex space-x-2">
<button class="btn btn-primary btn-sm">Primary</button>
<button class="btn btn-ghost btn-sm">Secondary</button>
</div>
</div>
</div>
</div>
</section>
<!-- Forms Section -->
<section id="forms" class="animate-fade-in">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-8">Forms</h2>
<div class="max-w-lg">
<form class="space-y-6">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" for="name">ชื่อ</label>
<input class="input" type="text" id="name" placeholder="กรอกชื่อของคุณ">
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">กรุณากรอกชื่อจริงของคุณ</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" for="email">อีเมล</label>
<input class="input" type="email" id="email" placeholder="your@email.com">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" for="email-error">อีเมล (Error State)</label>
<input class="input input-error" type="email" id="email-error" value="invalid-email">
<p class="text-sm text-error-600 dark:text-error-400 mt-1">รูปแบบอีเมลไม่ถูกต้อง</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" for="email-success">อีเมล (Success State)</label>
<input class="input input-success" type="email" id="email-success" value="valid@email.com">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" for="message">ข้อความ</label>
<textarea class="input min-h-[100px] resize-vertical" id="message" rows="4" placeholder="เขียนข้อความของคุณ..."></textarea>
</div>
<button type="submit" class="btn btn-primary btn-lg w-full">ส่งข้อมูล</button>
</form>
</div>
</section>
<!-- Alerts Section -->
<section id="alerts" class="animate-fade-in">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-8">Alerts</h2>
<div class="space-y-4 max-w-2xl">
<div class="alert alert-success">
<svg class="w-5 h-5 text-success-500 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div>
<div class="font-medium">ดำเนินการสำเร็จ</div>
<div class="text-sm opacity-75">ข้อมูลของคุณได้รับการบันทึกแล้ว</div>
</div>
</div>
<div class="alert alert-warning">
<svg class="w-5 h-5 text-warning-500 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
<div>
<div class="font-medium">คำเตือน</div>
<div class="text-sm opacity-75">กรุณาตรวจสอบข้อมูลให้ถูกต้อง</div>
</div>
</div>
<div class="alert alert-error">
<svg class="w-5 h-5 text-error-500 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
<path d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div>
<div class="font-medium">เกิดข้อผิดพลาด</div>
<div class="text-sm opacity-75">ไม่สามารถบันทึกข้อมูลได้ กรุณาลองใหม่อีกครั้ง</div>
</div>
</div>
<div class="alert alert-info">
<svg class="w-5 h-5 text-info-500 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
<path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div>
<div class="font-medium">ข้อมูล</div>
<div class="text-sm opacity-75">คุณสมบัตินี้จะมีผลในอีก 30 วัน</div>
</div>
</div>
</div>
</section>
<!-- Badges Section -->
<section id="badges" class="animate-fade-in">
<h2 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-8">Badges</h2>
<div class="space-y-4">
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Default Badges</h3>
<div class="flex flex-wrap gap-2">
<span class="badge badge-primary">Primary</span>
<span class="badge badge-success">Success</span>
<span class="badge badge-warning">Warning</span>
<span class="badge badge-error">Error</span>
</div>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">With Icons</h3>
<div class="flex flex-wrap gap-2">
<span class="badge badge-success inline-flex items-center">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Verified
</span>
<span class="badge badge-warning inline-flex items-center">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
Pending
</span>
<span class="badge badge-error inline-flex items-center">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 24 24">
<path d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Error
</span>
</div>
</div>
</div>
</section>
</main>
</div>
</div>
<!-- Footer -->
<footer class="mt-16 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="text-center text-gray-600 dark:text-gray-400">
<p>© 2025 Design System. สร้างด้วย Tailwind CSS</p>
</div>
</div>
</footer>
<script>
// Theme toggle functionality
const themeToggle = document.getElementById('theme-toggle');
const html = document.documentElement;
// Check for saved theme or default to system preference
const savedTheme = localStorage.getItem('theme');
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (savedTheme === 'dark' || (!savedTheme && systemPrefersDark)) {
html.classList.add('dark');
}
themeToggle.addEventListener('click', () => {
html.classList.toggle('dark');
// Save preference to localStorage
if (html.classList.contains('dark')) {
localStorage.setItem('theme', 'dark');
} else {
localStorage.setItem('theme', 'light');
}
});
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
if (e.matches) {
html.classList.add('dark');
} else {
html.classList.remove('dark');
}
}
});
// Smooth scroll for navigation links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Add active state to navigation
const navLinks = document.querySelectorAll('nav a[href^="#"]');
const sections = document.querySelectorAll('section[id]');
function updateActiveNav() {
let current = '';
sections.forEach(section => {
const sectionTop = section.offsetTop - 100;
if (window.pageYOffset >= sectionTop) {
current = section.getAttribute('id');
}
});
navLinks.forEach(link => {
link.classList.remove('nav-link-active');
if (link.getAttribute('href') === `#${current}`) {
link.classList.add('nav-link-active');
}
});
}
window.addEventListener('scroll', updateActiveNav);
updateActiveNav(); // Initial call
</script>
</body>
</html>
การจัดการ Multi-Brand Design System
Brand Configuration
// tailwind.config.js - Multi-Brand Support
const brandConfig = {
default: {
primary: '#3b82f6',
secondary: '#8b5cf6',
accent: '#f59e0b'
},
brand1: {
primary: '#dc2626',
secondary: '#7c3aed',
accent: '#059669'
},
brand2: {
primary: '#059669',
secondary: '#dc2626',
accent: '#d97706'
}
};
module.exports = {
theme: {
extend: {
colors: {
// Default brand colors
primary: brandConfig.default.primary,
secondary: brandConfig.default.secondary,
accent: brandConfig.default.accent,
// Brand specific colors
'brand-1': {
primary: brandConfig.brand1.primary,
secondary: brandConfig.brand1.secondary,
accent: brandConfig.brand1.accent
},
'brand-2': {
primary: brandConfig.brand2.primary,
secondary: brandConfig.brand2.secondary,
accent: brandConfig.brand2.accent
}
}
}
}
}
Brand Switching with CSS Variables
/* Brand theming with CSS Variables */
:root {
--color-brand-primary: 59 130 246; /* RGB values for alpha support */
--color-brand-secondary: 139 92 246;
--color-brand-accent: 245 158 11;
}
[data-brand="brand1"] {
--color-brand-primary: 220 38 38;
--color-brand-secondary: 124 58 237;
--color-brand-accent: 5 150 105;
}
[data-brand="brand2"] {
--color-brand-primary: 5 150 105;
--color-brand-secondary: 220 38 38;
--color-brand-accent: 217 119 6;
}
/* Use CSS variables in components */
.brand-button {
background-color: rgb(var(--color-brand-primary));
color: white;
transition: all 0.2s;
}
.brand-button:hover {
background-color: rgb(var(--color-brand-primary) / 0.9);
}
Best Practices และ Performance Tips
1. Optimization Strategies
// tailwind.config.js - Production Optimization
module.exports = {
content: [
'./src/**/*.{html,js,jsx,ts,tsx,vue}',
// Be specific with content paths
],
// Use safelist sparingly - only for dynamic classes
safelist: [
{
pattern: /bg-(red|green|blue)-(100|500|900)/,
variants: ['hover', 'focus']
}
],
// Disable unused core plugins
corePlugins: {
float: false,
clear: false,
skew: false,
// Disable features you don't use
},
theme: {
// Remove unused default colors
colors: {
transparent: 'transparent',
current: 'currentColor',
white: '#ffffff',
black: '#000000',
// Only include colors you actually use
primary: {
// Your primary color scale
}
}
}
}
2. Development Workflow
{
"scripts": {
"dev": "tailwindcss -i ./src/styles/tailwind.css -o ./dist/styles.css --watch",
"build": "NODE_ENV=production tailwindcss -i ./src/styles/tailwind.css -o ./dist/styles.css --minify",
"build:analyze": "tailwindcss -i ./src/styles/tailwind.css -o ./dist/styles.css --minify --verbose",
"purge": "purgecss --css ./dist/styles.css --content ./src/**/*.html --output ./dist/",
"lint:css": "stylelint ./src/**/*.css"
},
"devDependencies": {
"tailwindcss": "^3.4.0",
"@tailwindcss/typography": "^0.5.10",
"@tailwindcss/forms": "^0.5.7",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32"
}
}
3. Maintenance Tips
/* Organize your CSS files logically */
/* 1. Import Tailwind */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 2. Base customizations */
@layer base {
/* Global styles */
}
/* 3. Component styles */
@layer components {
/* Reusable components */
}
/* 4. Utility overrides */
@layer utilities {
/* Custom utilities */
}
/* 5. Third-party overrides (if needed) */
/* Keep these minimal and well-documented */
สรุป
การปรับแต่ง Tailwind CSS ขั้นสูงช่วยให้เราสร้าง Design System ที่สมบูรณ์และตอบโจทย์โปรเจ็กต์ได้อย่างแม่นยำ โดยมีจุดสำคัญดังนี้:
ประโยชน์หลัก:
- Consistency: Design System ที่สอดคล้องกันทั่วทั้งโปรเจ็กต์
- Maintainability: ง่ายต่อการบำรุงรักษาและอัปเดต
- Scalability: สามารถขยายและปรับใช้กับโปรเจ็กต์ขนาดใหญ่ได้
- Performance: Optimized สำหรับ production
- Developer Experience: เครื่องมือและ workflow ที่ดี
หลักการสำคัญ:
- Start with Design Tokens: กำหนด colors, fonts, spacing เป็นระบบ
- Component-Driven: สร้าง component ที่ใช้ซ้ำได้
- Performance-First: คำนึงถึง bundle size และ loading speed
- Documentation: จัดทำเอกสารและ style guide ที่ครบถ้วน
- Team Collaboration: สร้างมาตรฐานที่ทีมทั้งหมดเข้าใจและใช้งานได้
เครื่องมือที่แนะนำ:
- Official Plugins: Typography, Forms, Aspect Ratio
- Build Tools: PostCSS, AutoPrefixer, PurgeCSS
- Development: Live reload, CSS analysis tools
- Documentation: Style guide, component library
Next Steps:
- วิเคราะห์โปรเจ็กต์: ดูว่าต้องการ customization แบบไหน
- สร้าง Design Tokens: เริ่มจาก colors และ typography
- พัฒนา Component System: สร้างส่วนประกอบที่ใช้ซ้ำได้
- จัดทำ Documentation: เขียน style guide และ usage examples
- Optimize Performance: ปรับแต่งเพื่อความเร็วและประสิทธิภาพ
ในตอนถัดไป EP 9 เราจะมาเรียนรู้เคล็ดลับและเทคนิคเร็วต่างๆ ที่จะทำให้การใช้งาน Tailwind CSS เป็นเรื่องง่ายและรวดเร็วขึ้น รวมถึงการใช้เครื่องมือช่วยต่างๆ ที่จะเพิ่มประสิทธิภาพในการพัฒนา
พร้อมสร้าง Design System ระดับมืออาชีพแล้วหรือยัง? ติดตาม Superdev School เพื่อเรียนรู้เทคนิคขั้นสูงเพิ่มเติม และอย่าลืมลองปรับแต่ง Tailwind CSS ให้เป็นของตัวเองดูนะครับ!
อ่านบทความ Series อื่นๆ
🔵 Facebook: Superdev School (Superdev)
📸 Instagram: superdevschool
🎬 TikTok: superdevschool
🌐 Website: www.superdev.school