ข้ามไปเนื้อหาหลัก

Category: reference

CSS Architecture — BEM, CUBE CSS, Utility Classes

แนวทางจัดการ CSS ใน project ขนาดใหญ่: BEM naming, CUBE CSS methodology, เปรียบเทียบ Tailwind vs custom CSS

· อ่านประมาณ 3 นาที

สารบัญ

ปัญหาของ CSS ที่ไม่มีโครงสร้าง

/* ❌ ไม่มี convention — เดาไม่ออกว่า element ไหนใช้ class อะไร */
.header { ... }
.header-inner { ... }
.main-nav { ... }
.nav-link { ... }
.nav-link-active { ... }
.nav_item { ... }       /* ใช้ underscore บ้าง */
.NavItem { ... }        /* PascalCase บ้าง */
.is_active { ... }      /* ผสมกัน */

BEM (Block Element Modifier)

Convention: block__element--modifier

/* Block — component อิสระ */
.card { ... }
.button { ... }
.nav { ... }

/* Element — ส่วนประกอบของ Block */
.card__title { ... }
.card__body { ... }
.card__footer { ... }

/* Modifier — variant หรือ state */
.card--featured { ... }
.card--compact { ... }
.button--primary { ... }
.button--large { ... }
.button--disabled { ... }

BEM ในทางปฏิบัติ

<!-- ✓ BEM ชัดเจน -->
<div class="card card--featured">
  <h2 class="card__title">เรื่องน่าสนใจ</h2>
  <p class="card__body">รายละเอียด...</p>
  <a class="card__link button button--primary" href="/read">อ่านต่อ</a>
</div>
.card { ... }
.card--featured { border: 2px solid gold; }
.card__title { font-size: 1.25rem; }
.card__body { color: #334155; }

/* ✗ ไม่ nest class ใน BEM — ทำให้ specificity ซับซ้อน */
.card .card__title { ... }  /* ผิด */

/* ✓ flat selectors */
.card__title { ... }  /* ถูก */

CUBE CSS

CUBE = Composition, Utility, Block, Exception

/* 1. Composition — layout patterns ที่ reuse ได้ */
.flow > * + * { margin-top: var(--flow-space, 1rem); }
.cluster { display: flex; flex-wrap: wrap; gap: 1rem; }
.sidebar { display: flex; gap: 1rem; }
.sidebar > :last-child { flex-basis: 0; flex-grow: 999; min-width: 60%; }

/* 2. Utility — single-purpose classes */
.mt-4 { margin-top: 1rem; }
.text-center { text-align: center; }
.sr-only { /* visually hidden for screen readers */ }

/* 3. Block — component-specific styles (น้อยที่สุด) */
.card { padding: 1.5rem; border-radius: 8px; }
.button { padding: 0.5rem 1rem; border-radius: 4px; }

/* 4. Exception — state modifiers ด้วย data-* หรือ ARIA */
.button[data-variant="ghost"] { background: transparent; }
.card[data-size="compact"] { padding: 0.75rem; }

CUBE เน้น Cascade ไม่ใช่ต่อสู้มัน

/* ✓ ใช้ cascade — set defaults → override */
.button { background: var(--color-brand); color: white; }
.button[data-variant="outline"] { background: transparent; border: 2px solid currentColor; }

/* ✗ BEM แบบ strict ต่อสู้กับ cascade ด้วย flat selectors ทั้งหมด */

Utility-First (Tailwind Style)

<!-- สร้าง UI ด้วย atomic classes โดยตรงใน HTML -->
<div class="bg-white rounded-xl shadow p-6 flex flex-col gap-4">
  <h2 class="text-xl font-bold text-gray-900">Card Title</h2>
  <p class="text-gray-500 text-sm leading-relaxed">Description...</p>
  <a class="self-start px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-semibold">
    Read More
  </a>
</div>

ข้อดี: ไม่ต้องตั้งชื่อ class, ไม่มี CSS bloat, เขียนเร็ว
ข้อเสีย: HTML verbose, ยาก refactor ถ้าไม่ใช้ component framework


Custom Properties Architecture

/* Design tokens — layer ต่ำสุด */
:root {
  --color-blue-500: #3b82f6;
  --color-blue-600: #2563eb;
  --space-4: 1rem;
  --radius-md: 8px;
}

/* Semantic tokens — map จาก primitives */
:root {
  --color-brand: var(--color-blue-600);
  --color-text: #0f172a;
  --color-surface: #ffffff;
  --spacing-component: var(--space-4);
}

/* Dark mode tokens */
[data-theme='dark'] {
  --color-text: #f1f5f9;
  --color-surface: #0f172a;
}

/* Component tokens */
.button {
  --button-bg: var(--color-brand);
  --button-color: white;
  --button-radius: var(--radius-md);

  background: var(--button-bg);
  color: var(--button-color);
  border-radius: var(--button-radius);
}

/* Override ง่ายมาก */
.button-ghost {
  --button-bg: transparent;
  --button-color: var(--color-brand);
}

เมื่อไหรใช้อะไร

Approachเหมาะกับ
BEMTeam ใหญ่, CSS module, ไม่มี CSS-in-JS
CUBE CSSDesign system ที่ใช้ Cascade ให้เต็มที่
Utility-FirstRapid prototyping, Tailwind project
CSS Custom PropertiesTheme system, dark mode, component variants
CSS ModulesReact/Vue project, component-scoped styles

Selector Specificity — ไม่ต้องต่อสู้

/* Specificity: 0-0-1 */
.button { color: blue; }

/* Specificity: 0-1-1 — ชนะ .button */
.card .button { color: red; }

/* ✓ CUBE แก้ปัญหานี้ด้วย data attributes */
/* Specificity: 0-1-0 (attribute) */
[data-variant='ghost'] { color: currentColor; }

การออกแบบ specificity ให้ต่ำที่สุดเท่าที่ทำได้ ทำให้ override ง่ายเสมอ:

/* Layer ทำให้ควบคุม specificity ได้โดยไม่ต้อง !important */
@layer base, components, utilities;

@layer base {
  button { padding: 0.5rem; }
}

@layer components {
  .button { background: blue; color: white; }
}

@layer utilities {
  .mt-4 { margin-top: 1rem !important; } /* utilities จะชนะเสมอ */
}