การดู : 218

25/04/2026 02:47น.

EP 7: หยุดเขียนซ้ำ! สร้าง Component ด้วย @apply และ Custom CSS

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