CSS
Advanced & Best Practices
Naming Conventions (BEM)
เรียนรู้การตั้งชื่อ CSS class ให้สื่อความหมาย สม่ำเสมอ และดูแลง่าย พร้อมเกริ่นแนวทาง BEM สำหรับการจัดโครงสร้างชื่อ class แบบ component
หัวข้อนี้คืออะไร
Naming convention คือแนวทางการตั้งชื่อ CSS class ให้มีรูปแบบที่สม่ำเสมอและสื่อความหมายชัดเจน แทนที่จะตั้งชื่อแบบสุ่มหรือตามหน้าตาชั่วคราว เช่น .box1, .red-text, .left-thing การใช้ naming convention ช่วยให้ทุกคนในทีมอ่านโค้ดแล้วเข้าใจได้ทันทีว่า element นั้น คืออะไรและทำหน้าที่อะไร แนวทางที่นิยมที่สุดในโลก CSS คือ BEM (Block–Element–Modifier) ซึ่งตั้งชื่อ class ตามโครงสร้างของ component ทำให้ชื่อ class บอก "ที่มา" ของ element ได้ทันที
/* ❌ ไม่ดี — ชื่อไม่บอกบทบาท */
.box1 { ... }
.red-text { ... }
.left-card { ... }
/* ✅ ดีกว่า — ชื่อบอกบทบาทชัดเจน */
.product-card { ... }
.error-message { ... }
.sidebar-nav { ... }
/* ✅ BEM — บอกทั้งโครงสร้างและบทบาท */
.card { ... } /* Block */
.card__title { ... } /* Element */
.card--featured { ... } /* Modifier */ทำไมหัวข้อนี้สำคัญ
ลองนึกภาพเปิดไฟล์ CSS ที่มีชื่อ class อย่าง .a, .box2, .style3, .blue — คุณจะรู้ได้อย่างไรว่าแต่ละ class ทำหน้าที่อะไร และจะแก้ไขอะไรได้อย่างปลอดภัย? ปัญหาจากการตั้งชื่อไม่ดี: - ทีมใหม่ต้องเดาความหมายของชื่อ class ทุกครั้ง - แก้ class หนึ่งแล้วพังอีกที่โดยไม่รู้ตัว เพราะชื่อซ้ำหรือกำกวม - ค้นหา class ใน codebase ไม่เจอเพราะชื่อไม่สื่อถึงเนื้อหา - ยากต่อการ refactor หรือขยายระบบในอนาคต การตั้งชื่อที่ดีไม่ต้องใช้เวลาเพิ่มมาก แต่ประหยัดเวลาในการอ่านโค้ดและแก้บั๊กได้มหาศาลในระยะยาว
ตัวอย่างจากชีวิตจริง
ลองเปรียบกับการตั้งชื่อในชีวิตจริง: ถ้าตู้เก็บเอกสารมีลิ้นชักชื่อว่า "ลิ้นชัก 1", "ลิ้นชัก 2", "สีน้ำเงิน" — เราต้องเปิดดูทุกลิ้นชักเพื่อหาสิ่งที่ต้องการ แต่ถ้าชื่อว่า "ใบเสร็จ", "สัญญา", "ทรัพยากรบุคคล" — เราเปิดถูกทันที GitHub, Airbnb, และโปรเจกต์ขนาดใหญ่ใช้ BEM หรือแนว BEM เพราะทำให้นักพัฒนาหลายคนทำงานบน codebase เดียวกันได้โดยไม่ต้องถามกันว่า "class นี้ใช้ที่ไหนบ้าง" ตัวอย่างจาก Bootstrap: .btn, .btn--primary, .btn--lg — แค่อ่านชื่อก็รู้ว่าเป็นปุ่ม (block), style หลัก (modifier), และขนาดใหญ่ (modifier)
แนวคิดหลักที่ต้องเข้าใจ
หลักการตั้งชื่อ class ที่ดีมี 3 ข้อ: 1. ตั้งชื่อตามบทบาท ไม่ใช่หน้าตา ชื่อควรบอกว่า element นั้นคืออะไร ไม่ใช่มันดูยังไง เพราะหน้าตาเปลี่ยนได้แต่บทบาทมักคงเดิม 2. ตั้งชื่อให้สม่ำเสมอ ถ้าเลือกใช้ kebab-case (เช่น .product-card) ก็ใช้แบบนั้นตลอดทั้งโปรเจกต์ อย่าผสม .productCard กับ .product_card ในที่เดียวกัน 3. ชื่อควรบอกความสัมพันธ์ได้ BEM แก้ปัญหานี้โดยให้ชื่อบอกทั้งโครงสร้าง (Block > Element) และสถานะ (Modifier)
| ส่วน | รูปแบบ | ความหมาย | ตัวอย่าง |
|---|---|---|---|
| Block | .block | component หลัก | .card, .nav, .btn |
| Element | .block__element | ส่วนย่อยของ block | .card__title, .nav__item |
| Modifier | .block--modifier | สถานะหรือ variant | .card--featured, .btn--primary |
การทำงานทีละขั้นตอน
- ระบุ Block — มองหา component หลักใน UI เช่น card, navbar, form, button ตั้งชื่อด้วยคำนามที่สื่อบทบาท เช่น .product-card, .site-header
- ระบุ Elements — มองหาส่วนย่อยที่อยู่ใน block เช่น title, description, image ตั้งชื่อด้วย block__element เช่น .product-card__title, .product-card__image
- ระบุ Modifiers — มองหา variant หรือสถานะที่ต่างกัน เช่น featured, disabled, large ตั้งชื่อด้วย block--modifier เช่น .product-card--featured, .btn--disabled
- ตรวจสอบความสม่ำเสมอ — ใช้รูปแบบ (kebab-case, BEM ฯลฯ) เดียวกันตลอดทั้งไฟล์และโปรเจกต์ ไม่ผสมกัน
ตัวอย่างที่ 1 — ชื่อที่ดี vs ชื่อที่ควรหลีกเลี่ยง
เปรียบเทียบชื่อ class 3 คู่ที่พบบ่อย เพื่อให้เห็นความแตกต่างระหว่างชื่อที่สื่อความหมายกับชื่อที่กำกวม
ตัวอย่างที่ 2 — BEM กับ Component จริง (Navigation)
ตัวอย่าง navigation bar ที่ใช้ BEM อย่างสมบูรณ์ เพื่อให้เห็นว่าชื่อ class บอกโครงสร้างของ component ได้ทั้งหมด
ตัวอย่างที่ 3 — Form Component กับ BEM
Form เป็นอีก component ที่พบบ่อยมากในเว็บ ตัวอย่างนี้แสดงการใช้ BEM กับ form เพื่อให้ทุก element ของ form มีชื่อที่อ่านเข้าใจได้ทันที
จุดที่ผู้เริ่มต้นมักสับสน
- ตั้งชื่อตามหน้าตา ไม่ใช่บทบาท — .red-button ดูสมเหตุสมผลในตอนแรก แต่ถ้าวันหนึ่งปุ่มนั้นเปลี่ยนเป็นสีน้ำเงิน ชื่อ .red-button จะทำให้สับสนมาก ควรใช้ .btn--danger หรือ .btn--primary แทน
- BEM ไม่ได้บังคับให้ชื่อยาว — หลายคนกลัว BEM เพราะชื่อดูยาว เช่น .login-form__submit--disabled แต่ความยาวนั้นซื้อความชัดเจน ในโปรเจกต์จริงการอ่านโค้ดเร็วขึ้นมากกว่าที่ต้องพิมพ์เพิ่ม
- ไม่ต้อง nest element ซ้อนกันใน BEM — .card__header__title ไม่ใช่แนว BEM ที่ถูกต้อง ควรใช้ .card__title แทน แม้ว่า title จะอยู่ใน header ก็ตาม เพราะ BEM ไม่สะท้อน HTML structure ลึกกว่า 2 ระดับ
- ชื่อ class ไม่ควรซ้ำกับ HTML tag — ไม่ต้องตั้งชื่อ .div-container หรือ .p-text เพราะ tag บอกอยู่แล้ว ตั้งชื่อตามบทบาทเช่น .hero-description แทน
เปรียบเทียบแนวทางการตั้งชื่อ
| แนวทาง | รูปแบบ | ข้อดี | ข้อจำกัด |
|---|---|---|---|
| ไม่มี convention | .box1, .red, .top | พิมพ์เร็ว | อ่านยาก, ทีมสับสน, scale ไม่ได้ |
| Semantic naming | .product-card, .error-msg | อ่านง่าย, บอกบทบาท | ไม่มีโครงสร้างชัดสำหรับ component ใหญ่ |
| BEM | .card__title, .btn--primary | บอกโครงสร้างและ state ชัดมาก | ชื่อยาวกว่า, ต้องเรียนรู้ convention |
| Utility-first | .mt-4, .text-center | ยืดหยุ่น, ไม่ต้องคิดชื่อใหม่ | HTML อาจยาว, ไม่บอก component structure |
สรุปท้ายบทแบบจำง่าย
ชื่อ class ที่ดีคือชื่อที่คนอ่านครั้งแรกก็เข้าใจได้ทันทีว่า element นั้นคืออะไร ไม่ต้องเปิด CSS ก็รู้บทบาท
- ตั้งชื่อตามบทบาท ไม่ใช่หน้าตา — .error-message ดีกว่า .red-text
- ใช้ kebab-case เป็นมาตรฐาน — .product-card ไม่ใช่ .productCard หรือ .product_card
- BEM = Block__Element--Modifier — บอกโครงสร้างและ variant ในชื่อเดียว
- สม่ำเสมอตลอดโปรเจกต์ — เลือกแนวทางแล้วยึดมั่น อย่าผสมหลาย convention
Lab 1 — ตั้งชื่อ Class ให้ Element พื้นฐาน
HTML มี element 3 ตัวที่ยังไม่มี class ให้เพิ่ม class ที่มีชื่อสื่อบทบาทลงไปในแต่ละ element ตาม comment ที่กำหนด แล้วเขียน CSS ให้ตรงกับชื่อที่ตั้ง
Lab 2 — เปลี่ยนชื่อ Class ที่กำกวมให้ชัดขึ้น
HTML และ CSS มีชื่อ class ที่กำกวมอยู่ 3 ตัว ได้แก่ .box1, .blue-text, และ .right — ให้เปลี่ยนชื่อให้สื่อบทบาทชัดขึ้น และอัปเดตทั้ง HTML และ CSS ให้ตรงกัน
Lab 3 — Refactor ชื่อ Class ของ Component ให้เป็น BEM
Component card ด้านล่างมีชื่อ class ที่ไม่เป็นระบบ ให้ refactor ให้เป็น BEM โดย Block = .article-card, Element = .article-card__xxx, Modifier = .article-card--xxx