25/04/2026 02:47น.

EP 7: หยุดเขียนซ้ำ! สร้าง Component ด้วย @apply และ Custom CSS
#Tailwind CSS
#tailwind css @apply
#component system tailwind
#tailwind css components
หลายคนที่เริ่มใช้ Tailwind CSS มักจะเจอปัญหาเดียวกัน คือ HTML ที่ดูยาวเยิ่นฟ่าเฟื่อง และการต้องเขียนคลาสเดิมๆ ซ้ำไปซ้ำมา เช่น เขียนปุ่มสีน้ำเงินแล้วต้องใส่ px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors ทุกครั้ง
แต่คุณรู้ไหมว่า Tailwind CSS มีเครื่องมือที่ทรงพลังในการแก้ปัญหานี้? วันนี้เราจะมาเรียนรู้การใช้ @apply และเทคนิคการสร้าง Component ที่จะทำให้คุณเขียนโค้ดได้เร็วขึ้น บำรุงรักษาง่ายขึ้น และยังคงความยืดหยุ่นของ Tailwind CSS ไว้ได้
ทำความเข้าใจ @apply Directive
@apply เป็นเครื่องมือพิเศษของ Tailwind CSS ที่ให้เราเอาคลาส utility หลายๆ ตัวมารวมเป็นคลาสเดียว ทำให้เราสามารถสร้าง Component ที่ใช้ซ้ำได้
หลักการพื้นฐานของ @apply
/* สร้างปุ่มพื้นฐานด้วย @apply */
.btn {
@apply px-6 py-3 font-semibold rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
}
.btn-primary {
@apply bg-blue-500 text-white hover:bg-blue-600 focus:ring-blue-500;
}
.btn-secondary {
@apply bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500;
}
.btn-danger {
@apply bg-red-500 text-white hover:bg-red-600 focus:ring-red-500;
}
<!-- ใช้งานในHTML -->
<button class="btn btn-primary">ปุ่มหลัก</button>
<button class="btn btn-secondary">ปุ่มรอง</button>
<button class="btn btn-danger">ปุ่มลบ</button>
ข้อดีของการใช้ @apply
- HTML สะอาดขึ้น: ลดความยาวของ class attributes
- บำรุงรักษาง่าย: แก้ไขที่เดียวได้ผลทุกที่
- สร้าง Design System: มีมาตรฐานที่ชัดเจน
- ยังคงความยืดหยุ่น: สามารถใส่ utility classes เพิ่มได้
เมื่อไหร่ควรใช้ @apply
/* ✅ ดี - Pattern ที่ใช้ซ้ำบ่อย */
.card {
@apply bg-white rounded-lg shadow-md p-6 border border-gray-200;
}
.input-field {
@apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500;
}
/* ❌ ไม่ดี - ใช้แค่ที่เดียว */
.unique-element {
@apply text-red-500 font-bold;
}
การสร้าง Component System แบบมืออาชีพ
Button Components
/* Base Button */
.btn {
@apply inline-flex items-center justify-center px-4 py-2 font-medium rounded-md transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed;
}
/* Button Sizes */
.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;
}
/* Button Variants */
.btn-solid {
@apply shadow-sm;
}
.btn-outline {
@apply border-2 bg-transparent;
}
/* Button Colors */
.btn-blue {
@apply bg-blue-500 text-white hover:bg-blue-600 focus:ring-blue-500;
}
.btn-blue.btn-outline {
@apply border-blue-500 text-blue-500 hover:bg-blue-500 hover:text-white;
}
.btn-green {
@apply bg-green-500 text-white hover:bg-green-600 focus:ring-green-500;
}
.btn-green.btn-outline {
@apply border-green-500 text-green-500 hover:bg-green-500 hover:text-white;
}
<!-- การใช้งาน Button System -->
<div class="space-y-4">
<!-- ขนาดต่างๆ -->
<div class="space-x-2">
<button class="btn btn-sm btn-solid btn-blue">เล็ก</button>
<button class="btn btn-md btn-solid btn-blue">ปกติ</button>
<button class="btn btn-lg btn-solid btn-blue">ใหญ่</button>
</div>
<!-- รูปแบบต่างๆ -->
<div class="space-x-2">
<button class="btn btn-md btn-solid btn-blue">Solid</button>
<button class="btn btn-md btn-outline btn-blue">Outline</button>
</div>
<!-- สีต่างๆ -->
<div class="space-x-2">
<button class="btn btn-md btn-solid btn-blue">น้ำเงิน</button>
<button class="btn btn-md btn-solid btn-green">เขียว</button>
</div>
</div>
Card Components
/* Base Card */
.card {
@apply bg-white rounded-lg shadow-md overflow-hidden;
}
.card-hover {
@apply transition-all duration-300 hover:shadow-lg hover:-translate-y-1;
}
.card-bordered {
@apply border border-gray-200;
}
/* Card Parts */
.card-header {
@apply px-6 py-4 border-b border-gray-200 bg-gray-50;
}
.card-body {
@apply px-6 py-4;
}
.card-footer {
@apply px-6 py-4 border-t border-gray-200 bg-gray-50;
}
.card-image {
@apply w-full h-48 object-cover;
}
.card-title {
@apply text-xl font-semibold text-gray-800 mb-2;
}
.card-text {
@apply text-gray-600 leading-relaxed;
}
<!-- การใช้งาน Card System -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Card แบบมี Header -->
<div class="card card-hover card-bordered">
<div class="card-header">
<h3 class="card-title">หัวข้อบทความ</h3>
</div>
<div class="card-body">
<p class="card-text">
เนื้อหาของบทความที่น่าสนใจและมีประโยชน์สำหรับผู้อ่าน
</p>
</div>
<div class="card-footer">
<button class="btn btn-sm btn-solid btn-blue">อ่านต่อ</button>
</div>
</div>
<!-- Card แบบมีรูปภาพ -->
<div class="card card-hover">
<img class="card-image" src="https://images.unsplash.com/photo-1518837695005-2083093ee35b?w=400" alt="Card image" loading="lazy">
<div class="card-body">
<h3 class="card-title">ชื่อสินค้า</h3>
<p class="card-text">รายละเอียดสินค้าที่น่าสนใจ</p>
<div class="mt-4 flex items-center justify-between">
<span class="text-2xl font-bold text-blue-600">฿1,999</span>
<button class="btn btn-sm btn-solid btn-green">ซื้อเลย</button>
</div>
</div>
</div>
</div>
Form Components
/* Form Elements */
.form-group {
@apply mb-4;
}
.form-label {
@apply block text-sm font-medium text-gray-700 mb-2;
}
.form-input {
@apply w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors;
}
.form-input.error {
@apply border-red-300 focus:ring-red-500 focus:border-red-500;
}
.form-textarea {
@apply form-input resize-vertical min-h-[100px];
}
.form-select {
@apply form-input appearance-none bg-no-repeat bg-right pr-10;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
}
.form-error {
@apply text-sm text-red-600 mt-1;
}
.form-help {
@apply text-sm text-gray-500 mt-1;
}
<!-- การใช้งาน Form System -->
<form class="max-w-lg mx-auto space-y-6">
<div class="form-group">
<label class="form-label" for="name">ชื่อ-นามสกุล</label>
<input class="form-input" type="text" id="name" placeholder="กรอกชื่อของคุณ">
<p class="form-help">กรุณากรอกชื่อจริงของคุณ</p>
</div>
<div class="form-group">
<label class="form-label" for="email">อีเมล</label>
<input class="form-input error" type="email" id="email" placeholder="your@email.com">
<p class="form-error">กรุณากรอกอีเมลที่ถูกต้อง</p>
</div>
<div class="form-group">
<label class="form-label" for="country">ประเทศ</label>
<select class="form-select" id="country">
<option>เลือกประเทศ</option>
<option>ไทย</option>
<option>สิงคโปร์</option>
<option>มาเลเซีย</option>
</select>
</div>
<div class="form-group">
<label class="form-label" for="message">ข้อความ</label>
<textarea class="form-textarea" id="message" placeholder="เขียนข้อความของคุณ..."></textarea>
</div>
<button class="btn btn-lg btn-solid btn-blue w-full">ส่งข้อมูล</button>
</form>
การจัดการ Custom Utilities และ Components
การใช้ @layer อย่างถูกต้อง
@layer base {
/* Reset และ Base Styles */
html {
@apply scroll-smooth;
}
body {
@apply font-sans text-gray-900 leading-relaxed;
}
}
@layer components {
/* Component Classes */
.btn {
@apply px-4 py-2 font-medium rounded-lg transition-all duration-200;
}
.card {
@apply bg-white rounded-lg shadow-md overflow-hidden;
}
.glass-effect {
@apply backdrop-blur-sm bg-white/80 border border-white/20;
}
}
@layer utilities {
/* Custom Utilities */
.scroll-snap-x {
scroll-snap-type: x mandatory;
}
.scroll-snap-start {
scroll-snap-align: start;
}
.text-shadow {
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
}
การสร้าง Responsive Components
/* Responsive Card */
.card-responsive {
@apply bg-white rounded-lg shadow-md overflow-hidden;
@apply flex flex-col md:flex-row;
}
.card-responsive .card-image {
@apply w-full md:w-1/3 h-48 md:h-auto object-cover;
}
.card-responsive .card-content {
@apply p-6 flex-1;
}
.card-responsive .card-title {
@apply text-xl font-semibold mb-2 md:text-2xl;
}
.card-responsive .card-text {
@apply text-gray-600 text-sm md:text-base;
}
Best Practices สำหรับ Component Development
1. การตั้งชื่อ Component
/* ✅ ดี - ชื่อที่สื่อความหมาย */
.btn-primary { }
.card-product { }
.form-input-error { }
.navbar-mobile { }
/* ❌ ไม่ดี - ชื่อที่คลุมเครือ */
.blue-button { }
.box1 { }
.input2 { }
.nav { }
2. การจัดระเบียบโครงสร้าง
/* จัดเรียงตามลำดับความสำคัญ */
/* 1. Base Components */
.btn { }
.card { }
.form-input { }
/* 2. Component Variants */
.btn-primary { }
.btn-secondary { }
.card-hover { }
.card-bordered { }
/* 3. Component Modifiers */
.btn-sm { }
.btn-lg { }
/* 4. State Classes */
.btn:disabled { }
.form-input.error { }
3. Performance Tips
/* ✅ ดี - ใช้ @apply เฉพาะที่จำเป็น */
.frequently-used-button {
@apply px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors;
}
/* ❌ ไม่ดี - ใช้ @apply กับทุกอย่าง */
.one-time-use {
@apply text-red-500 font-bold; /* ควรใช้ utility classes ตรงๆ */
}
/* ✅ ดี - Combine กับ utility classes */
.special-card {
@apply card;
/* เพิ่ม utility classes ตามต้องการ */
}
Common Pitfalls และวิธีแก้ไข
1. การใช้ @apply กับ Pseudo-classes ผิด
/* ❌ ไม่ทำงาน */
.btn {
@apply px-4 py-2 bg-blue-500:hover;
}
/* ✅ ถูกต้อง */
.btn {
@apply px-4 py-2 bg-blue-500 hover:bg-blue-600;
}
/* ✅ หรือแยกออกมา */
.btn {
@apply px-4 py-2 bg-blue-500;
}
.btn:hover {
@apply bg-blue-600;
}
2. การใช้ !important ผิด
/* ❌ ไม่ดี */
.btn {
@apply px-4 py-2 !bg-blue-500;
}
/* ✅ ดี - ใช้ specificity */
.btn.btn-important {
@apply bg-blue-500;
}
3. Bundle Size Considerations
/* ⚠️ ระวัง - อาจทำให้ bundle ใหญ่ */
.every-single-element {
@apply p-4 m-2 bg-white rounded shadow;
}
/* ✅ ดี - ใช้เฉพาะที่จำเป็น */
.common-card {
@apply p-4 m-2 bg-white rounded shadow;
}
การทำงานร่วมกับ JavaScript Frameworks
React Integration
// components/Button.jsx
import clsx from 'clsx';
const Button = ({
variant = 'primary',
size = 'md',
children,
className,
...props
}) => {
return (
<button
className={clsx(
'btn',
`btn-${variant}`,
`btn-${size}`,
className
)}
{...props}
>
{children}
</button>
);
};
// การใช้งาน
<Button variant="primary" size="lg" className="shadow-lg">
คลิกที่นี่
</Button>
Vue.js Integration
<!-- components/Card.vue -->
<template>
<div :class="cardClasses">
<img v-if="image" class="card-image" :src="image" :alt="title" loading="lazy">
<div class="card-body">
<h3 v-if="title" class="card-title">{{ title }}</h3>
<p v-if="description" class="card-text">{{ description }}</p>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
variant: {
type: String,
default: 'default'
},
hover: {
type: Boolean,
default: false
},
image: String,
title: String,
description: String
},
computed: {
cardClasses() {
return [
'card',
this.hover && 'card-hover',
`card-${this.variant}`
].filter(Boolean);
}
}
}
</script>
Configuration และ Development Workflow
Tailwind Config สำหรับ Component System
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{html,js,jsx,ts,tsx,vue}',
'./components/**/*.{html,js,jsx,ts,tsx,vue}'
],
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
}
},
spacing: {
'18': '4.5rem',
}
}
},
plugins: [
require('@tailwindcss/forms'),
]
}
Build Script Setup
{
"scripts": {
"dev": "tailwindcss -i ./src/input.css -o ./dist/output.css --watch",
"build": "tailwindcss -i ./src/input.css -o ./dist/output.css --minify"
}
}
การ Document Components
/**
* Button Component
*
* Usage:
* <button class="btn btn-primary btn-lg">Click me</button>
*
* Variants: primary, secondary, green
* Sizes: sm, md, lg
* States: disabled
*/
.btn {
@apply inline-flex items-center justify-center px-4 py-2 font-medium rounded-lg;
@apply transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
@apply disabled:opacity-50 disabled:cursor-not-allowed;
}
/**
* Card Component
*
* Usage:
* <div class="card card-hover">
* <div class="card-body">
* <h3 class="card-title">Title</h3>
* <p class="card-text">Content</p>
* </div>
* </div>
*
* Modifiers: hover, bordered
*/
.card {
@apply bg-white rounded-lg shadow-md overflow-hidden;
}
สรุป
การสร้าง Component System ด้วย @apply และ Custom CSS ใน Tailwind CSS เป็นเทคนิคที่ทรงพลังที่ช่วยให้เราสามารถ:
ประโยชน์หลัก:
- เขียนโค้ดเร็วขึ้น: ใช้ Component ที่พร้อมใช้งาน
- HTML สะอาด: ลดความยาวของ class attributes
- บำรุงรักษาง่าย: แก้ไขที่เดียวได้ผลทุกที่
- สร้าง Design System: มีมาตรฐานที่ชัดเจน
- ยืดหยุ่น: ยังคงใช้ utility classes เพิ่มได้
หลักการสำคัญ:
- ใช้
@applyเฉพาะ Pattern ที่ใช้ซ้ำบ่อย - ตั้งชื่อ Component ให้สื่อความหมาย
- จัดระเบียบด้วย
@layerที่ถูกต้อง - คำนึงถึง Performance และ Bundle Size
- Document Component ให้ครบถ้วน
ข้อควรระวัง:
- อย่าใช้
@applyกับทุกอย่าง - ระวัง Bundle Size เมื่อสร้าง Component มากเกินไป
- ทดสอบ Browser Compatibility สำหรับ CSS properties ที่ใหม่
- จัดลำดับ CSS imports ให้ถูกต้อง
ในตอนถัดไป EP 8 เราจะมาเรียนรู้เรื่อง Customization ขั้นสูง การปรับแต่ง Tailwind ให้เป็นของตัวเอง สร้าง Custom Theme และใช้งาน Plugin ต่างๆ ที่จะทำให้ Tailwind CSS ของเราพิเศษและตอบโจทย์โปรเจ็กต์ได้อย่างสมบูรณ์แบบ!
พร้อมหยุดเขียนโค้ดซ้ำๆ แล้วหรือยัง? ติดตาม Superdev School เพื่อเรียนรู้เทคนิคการพัฒนาเว็บไซต์แบบมืออาชีพ และอย่าลืมลองสร้าง Component System ของตัวเองดูนะครับ!
อ่านบทความ Series อื่นๆ
🔵 Facebook: Superdev School (Superdev)
📸 Instagram: superdevschool
🎬 TikTok: superdevschool
🌐 Website: www.superdev.school