CSS
Tailwind Essentials
Tailwind functions & directives
เข้าใจ directive และ function ที่ใช้จริงใน Tailwind v4 เช่น @utility, @apply, @reference, --alpha() และ --spacing() เพื่อเขียน CSS-first workflow แบบอ่านง่ายและยังเกาะ token เดิมของระบบ
Functions กับ directives ต่างกันตรงไหน
ใน Tailwind v4 คำว่า directive คือคำสั่งพิเศษที่เราเขียนใน CSS เพื่อบอก Tailwind ว่าควรสร้างอะไรเพิ่มหรือควรตีความ CSS ก้อนนั้นแบบไหน ส่วน function คือคำสั่งย่อยที่คืนค่าออกมาให้เราเอาไปใส่ใน property CSS จำง่าย ๆ ได้ว่า directive = สั่ง Tailwind ทำงาน และ function = ขอค่าที่คำนวณแล้วมาใช้ต่อ
| ชนิด | หน้าตา | ใช้เมื่อ |
|---|---|---|
| Directive | @theme, @utility, @apply | อยากประกาศ token, utility, variant หรือกติกาใหม่ให้ Tailwind |
| Function | --alpha(...), --spacing(...) | อยากได้ค่าที่อิงระบบเดิมของ Tailwind ไปใส่ใน CSS |
| Reference import | @reference | อยากให้ CSS เฉพาะ component มองเห็น theme และ utilities จากไฟล์หลัก |
- ไฟล์ CSS หลักหรือ app.css มักเป็นที่อยู่ของ @import, @theme, @utility และ @custom-variant
- ในบทเรียนนี้เราใช้ <style type="text/tailwindcss"> เพราะตรงกับ workflow ของ browser CDN
- ถ้าอยู่ใน CSS module หรือ <style> ของ component แล้วอยากใช้ @apply หรือ @variant ให้ใช้ @reference ก่อน
@theme ใช้สร้าง token ใหม่ให้ทั้งระบบ
@theme คือจุดที่เราประกาศ design tokens ของโปรเจกต์ เช่น สี ฟอนต์ breakpoint หรือ radius ใหม่ เมื่อประกาศแล้ว Tailwind จะสร้าง utility classes ให้ตามชื่อ token นั้นทันที ใช้ @theme เมื่อค่าหนึ่งเริ่มถูกใช้ซ้ำหลายจุด เพราะมันทำให้โค้ดอ่านง่ายขึ้นและช่วยรวม source of truth ไว้จุดเดียว
พอประกาศ token แล้ว class อย่าง bg-brand-500 หรือ rounded-card จะใช้งานได้ทันที
<style type="text/tailwindcss">
@theme {
--color-brand-500: #2563eb;
--radius-card: 1rem;
}
</style>
<section class="rounded-card bg-brand-500 text-white">...</section>ข้อควรระวังคืออย่ารีบย้ายค่าทุกอย่างเข้า @theme ตั้งแต่ครั้งแรก ถ้าค่านั้นใช้ครั้งเดียวหรือแค่ทดลองชั่วคราว การใช้ utility ปกติหรือ arbitrary value อาจตรงกว่า แต่ถ้าค่าเดิมเริ่มโผล่ซ้ำหลาย component นั่นคือสัญญาณที่ดีว่าควรย้ายเข้า @theme
@utility ใช้สร้าง utility class ที่เรียกซ้ำได้
@utility ใช้สำหรับสร้าง utility class ใหม่ในสไตล์ของ Tailwind เอง จุดเด่นคือ utility ที่สร้างแบบนี้ยังทำงานร่วมกับ variants อย่าง hover:, md: หรือ dark: ได้เหมือน utility ปกติ ใช้ @utility เมื่อคุณอยากได้ class เล็ก ๆ ที่มีหน้าที่ชัดเจนและน่าจะถูกนำไปใช้ซ้ำ มากกว่าการเขียน custom selector ยาว ๆ
utility ที่ดีควรสื่อหน้าที่ชัด เช่น stack-gap, surface-card หรือ text-balance-soft
@utility stack-gap {
display: grid;
gap: --spacing(4);
}ข้อควรระวังคืออย่าใช้ @utility เพื่อสร้าง class ที่ใหญ่เกินไปจนกลายเป็น component แบบแฝง ถ้า class นั้นรวมหน้าที่หลายอย่าง เช่น layout, color, border, action state พร้อมกัน มักจะเหมาะกับ @layer components มากกว่า
@variant ใช้สอด variant เข้าไปใน CSS ที่กำลังเขียน
@variant ใช้เมื่อคุณกำลังเขียน custom CSS แล้วอยากบอกว่า style บางส่วนควรทำงานเฉพาะตอนเป็น dark mode หรืออยู่ใน breakpoint/interaction บางแบบ มันช่วยให้เราไม่ต้องแตก selector เดิมออกไปเขียนซ้ำหลายรอบ
แนวคิดคือเขียน style ฐานก่อน แล้วค่อยซ้อน variant ที่ต้องการเฉพาะกรณีเข้าไป
.banner {
background: white;
@variant dark {
background: #0f172a;
}
}ข้อควรระวังคือ @variant เหมาะกับการเพิ่มเงื่อนไขให้ CSS เดิม ไม่ใช่การย้าย utility usage ทั้งหมดออกจาก HTML ถ้า component ยังอ่านง่ายด้วย class ปกติ ก็ยังควรใช้ utility classes ตามเดิมก่อน
@custom-variant ใช้ตั้งชื่อ variant ของโปรเจกต์เอง
@custom-variant ใช้สร้าง variant ใหม่จาก selector ที่เราออกแบบเอง เช่น data-theme, data-state หรือโครงสร้าง wrapper บางอย่าง พอประกาศแล้วเราจะเรียกใช้มันใน class name ได้เหมือน variant มาตรฐานของ Tailwind ใช้เมื่อโปรเจกต์มีเงื่อนไขที่ใช้ซ้ำจริง และอยากตั้งชื่อให้ทุกคนในทีมเรียกตรงกัน
ประกาศครั้งเดียว แล้วค่อยใช้ theme-midnight:* ซ้ำได้หลาย component
@custom-variant theme-midnight (&:where([data-theme="midnight"] *));
/* ใช้งานใน HTML */
<section class="theme-midnight:bg-slate-950">...</section>ข้อควรระวังคืออย่าตั้ง variant ที่คลุมเครือหรือผูกกับสถานะชั่วคราวเกินไป ชื่อ variant ควรสื่อเงื่อนไขจริง เช่น theme-midnight หรือ group-data-open ไม่ใช่ชื่อที่อ่านแล้วเดาไม่ออกว่าทำงานตอนไหน
@apply ใช้ดึง utility เดิมมา inline ใน custom CSS
@apply ช่วยให้เราเอา utility classes ที่มีอยู่แล้วของ Tailwind มารวมไว้ใน custom CSS ได้ เหมาะกับกรณีที่เราแตะ third-party markup ไม่ได้ หรือมี UI pattern ที่อยากรวมให้อ่านเป็นชื่อเดียวก่อน มันไม่ได้แทน utility classes ในทุกสถานการณ์ แต่ช่วยในงานที่ต้อง bridge ระหว่าง HTML ที่ควบคุมไม่ได้กับ design system ของเรา
ตัวอย่างคลาสจาก third-party library ที่เราไม่อยากไล่ใส่ utility ที่ HTML โดยตรง
.select2-dropdown {
@apply rounded-b-lg shadow-md ring-1 ring-slate-200;
}ข้อควรระวังคืออย่าใช้ @apply เพื่อซ่อน utility ทุกอย่างจนมองไม่ออกว่า component ได้ style อะไรบ้าง ถ้าเป็น markup ที่เราเขียนเองและ class ยังอ่านง่ายอยู่ การใช้ utility classes ตรง ๆ มักตรงไปตรงมากว่า
@reference ใช้เปิดทางให้ CSS เฉพาะ component มองเห็น Tailwind
เวลาอยู่ใน CSS module หรือ <style> ของ component บางระบบ Tailwind จะไม่รู้จัก theme variables, custom utilities หรือ custom variants จากไฟล์หลักโดยอัตโนมัติ @reference มีหน้าที่อ้างไฟล์หลักเข้ามาแบบ reference-only เพื่อให้ context เหล่านั้นถูกมองเห็น โดยไม่ duplicate CSS ลง output
ตัวอย่างนี้สื่อว่าถ้าไม่มี @reference ส่วน @apply และ @variant อาจไม่เห็นของจาก stylesheet หลักใน context นี้
@reference "../../app.css";
.lesson-title {
@apply text-2xl font-bold text-slate-950;
@variant dark {
@apply text-white;
}
}ข้อควรระวังคือ @reference ไม่ใช่เครื่องมือสำหรับ import CSS มาแสดงผลซ้ำ แต่ใช้เพื่ออ้าง context เท่านั้น เพราะฉะนั้นให้ชี้ไปที่ stylesheet หลักที่รวม theme/utilities ของโปรเจกต์จริง
--alpha() ใช้ปรับความโปร่งใสจากสีเดิม
--alpha() ใช้เมื่อคุณมีสีหลักอยู่แล้วและอยากลดความเข้มของมันโดยยังยึดกับ token เดิม เช่นอยากทำ background โปร่ง ๆ หรือเส้นขอบที่เบากว่าสีหลักเดิม วิธีนี้ปลอดภัยกว่าการ hard-code สีใหม่อีกเฉด เพราะถ้า token หลักเปลี่ยน UI ที่อิงมันก็ยังอัปเดตตาม
ใช้สีเดิมหนึ่งตัว แต่แตกเป็นหลายระดับความโปร่งใสได้โดยไม่ต้องสร้าง token สีเพิ่มทุกเฉด
.surface {
background: --alpha(var(--color-brand-500) / 12%);
box-shadow: 0 0 0 1px --alpha(var(--color-brand-500) / 24%);
}ข้อควรระวังคือ --alpha() ควรใช้กับกรณีที่อยากลด opacity ของสีเดิมจริง ๆ ถ้าคุณต้องการสีที่มีความหมายใหม่ เช่น success surface หรือ danger border ที่ใช้ซ้ำหลายจุด ก็มักเหมาะกว่าที่จะประกาศ token เพิ่มใน @theme
--spacing() ใช้ดึง spacing scale ของ Tailwind มาใช้ใน CSS
--spacing() คืนค่าระยะที่อิงกับ spacing scale กลางของ Tailwind ทำให้ custom CSS ของเรายังเดินจังหวะเดียวกับ utility classes เช่น p-4, gap-6 หรือ mt-8 ได้ มันมีประโยชน์มากในจุดที่ utility ปกติยังไม่พอ เช่น ต้องคำนวณใน calc() หรือสร้าง utility/custom class ของเราเอง
เหมาะกับ custom CSS ที่ยังอยากรักษา spacing rhythm เดียวกับทั้งระบบ
.panel {
padding: --spacing(6);
}
.hero {
padding-block: calc(--spacing(8) + 2px);
}ข้อควรระวังคืออย่าใช้ --spacing() แทน utility ปกติทุกจุด ถ้าความต้องการตรงกับ class ที่มีอยู่แล้ว เช่น gap-6 หรือ p-6 ให้ใช้ utility class ตรง ๆ ก่อน แล้วค่อยใช้ --spacing() ตอนต้องคำนวณหรือห่อพฤติกรรมไว้ใน custom CSS จริง ๆ
ใช้ @apply ร่วมกับ @variant เมื่อมี CSS-first pattern ชัดเจน
บางครั้ง @apply กับ @variant จะมาคู่กันได้ดี โดยเฉพาะตอนเรากำลังเขียน class กลางที่มี style ฐานชัดเจน แต่ต้องมีเงื่อนไขเพิ่มตอน dark mode หรือ responsive state บางแบบ วิธีนี้ช่วยให้โค้ดอ่านเป็นก้อนเดียว แทนที่จะกระจาย style หลายจุด
สรุป compatibility: @config, @plugin และ theme()
สามอย่างนี้ยังมีประโยชน์ แต่บทบาทหลักคือช่วยเรื่อง migration จาก Tailwind v3 หรือ workflow แบบเก่า มากกว่าจะเป็นเครื่องมือหลักสำหรับโปรเจกต์ใหม่บน Tailwind v4 ถ้าเริ่มใหม่ ให้คิดแบบ CSS-first ก่อน คือใช้ @theme, @utility, @custom-variant และ functions ใหม่ ๆ เป็นแกนหลัก แล้วค่อยหยิบ @config หรือ @plugin มาใช้เมื่อมีข้อจำเป็นด้าน compatibility จริง
| ตัวเลือก | ควรมองว่าเป็นอะไร | ข้อสังเกต |
|---|---|---|
| @config | เครื่องมือ migration | ใช้เมื่อต้องโหลด tailwind.config.js แบบเดิมระหว่างค่อย ๆ ย้ายมา v4 |
| @plugin | เครื่องมือ migration / legacy integration | ใช้เมื่อยังพึ่ง plugin แบบเก่าที่ไม่ได้ย้ายมาฝั่ง CSS-first |
| theme() | deprecated helper | docs แนะนำให้ย้ายไปใช้ CSS theme variables แทน |
Lab: ใช้ directive และ function ใน snippet เดียว
โจทย์นี้ให้คุณใช้ความรู้จากบทนี้ร่วมกันในไฟล์เดียว โดยสร้าง utility ใหม่ด้วย @utility แล้วใช้ --spacing() เพื่อกำหนด gap ของ utility นั้น เป้าหมายคือแยกให้ออกว่า directive ใช้ประกาศพฤติกรรมใหม่ให้ Tailwind ส่วน function ใช้คืนค่าที่คำนวณแล้วไปใส่ใน CSS