CSS Custom Properties และ Constructable Stylesheets
CSS custom properties (หรือที่เรียกว่า CSS variables) เป็น API หลักสำหรับการทำธีมให้กับ web components ต่างจาก CSS rules ทั่วไป custom properties จะ สืบทอดผ่านขอบเขตของ shadow ได้ host element หรือ ancestor ใด ๆ สามารถประกาศ --color: blue และ shadow root สามารถอ่านค่าได้ด้วย color: var(--color, defaultValue) โดยอาร์กิวเมนต์ที่สองคือค่า fallback ที่จะถูกใช้เมื่อ property นั้นไม่ได้ถูกตั้งค่าไว้
/* Page or host sets the token */themed-badge { --badge-bg: #7c3aed; --badge-color: white;}
/* Shadow root reads it */span { background: var(--badge-bg, #6b7280); color: var(--badge-color, white);}การสืบทอดนี้เป็นไปโดยตั้งใจ เพราะ custom properties ถูกออกแบบมาให้ทะลุผ่าน encapsulation ทุกอย่างที่เหลือ (selectors, cascade) จะหยุดอยู่ที่ขอบเขตของ shadow แต่ตัวแปร -- จะไหลผ่านได้อย่างอิสระ
Constructable Stylesheets
หัวข้อที่มีชื่อว่า “Constructable Stylesheets”Constructable Stylesheets ช่วยให้คุณสร้างอ็อบเจกต์ CSSStyleSheet ใน JavaScript ใส่ค่าเพียงครั้งเดียวด้วย .replaceSync() แล้วแชร์อ็อบเจกต์เดียวกันนั้นไปยัง shadow roots หลายตัวได้ด้วยการกำหนดให้กับ shadowRoot.adoptedStyleSheets shadow root แต่ละตัวที่ adopt sheet นี้จะได้สไตล์มาโดยไม่ต้องทำซ้ำ CSS ที่ parse แล้วในหน่วยความจำ
const sheet = new CSSStyleSheet();sheet.replaceSync('span { font-weight: bold; color: steelblue; }');
class MyEl extends HTMLElement { connectedCallback() { var shadow = this.attachShadow({ mode: 'open' }); shadow.adoptedStyleSheets = [sheet]; }}สิ่งนี้มีประโยชน์อย่างยิ่งใน design systems ที่มี components หลายสิบตัวแชร์ stylesheet พื้นฐานร่วมกัน parse ครั้งเดียว แล้ว adopt ได้ทุกที่
Live Demo
หัวข้อที่มีชื่อว่า “Live Demo”เดโมด้านล่างนิยาม element <themed-badge> ที่สีพื้นหลังและสีข้อความถูกควบคุมทั้งหมดด้วย CSS custom properties ที่ตั้งไว้บน host จาก page stylesheet ลองเปลี่ยนค่า --badge-bg ในแผง CSS เพื่อเปลี่ยนธีมได้โดยไม่ต้องแตะโค้ดของ component เลย
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| CSS Custom Properties | เป็นช่องทาง theming ที่เรียบง่ายและยืดหยุ่น สืบทอดผ่าน shadow boundary ได้โดยไม่ต้องเขียน JavaScript | หน้าเว็บภายนอกไม่มีสิทธิ์ override ทุก style decision ภายใน — ธีมได้เฉพาะ token ที่ผู้เขียน component เปิดให้เท่านั้น |
| Constructable Stylesheets | แชร์ CSSStyleSheet object เดียวข้ามหลาย shadow roots ได้ ลด memory และไม่ต้อง parse ซ้ำ | ต้องเขียนโค้ด JavaScript เพิ่มเพื่อสร้างและ adopt sheet ซับซ้อนกว่าการใส่ <style> ตรง ๆ ในแต่ละ instance |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ทำซ้ำ
<style>ในทุก instance — ใส่บล็อก<style>เดิมซ้ำในทุก shadow root แทนที่จะสร้างCSSStyleSheetเดียวแล้วแชร์ผ่านadoptedStyleSheetsทำให้เปลืองหน่วยความจำและแก้ยากเมื่อต้อง update สไตล์ - ลืมใส่ fallback value — เขียน
var(--badge-bg)โดยไม่มีค่า fallback เมื่อ property ไม่ถูกตั้งค่าไว้ component จะพังแบบเงียบ ๆ ควรเขียนเป็นvar(--badge-bg, #6b7280)เสมอ - คาดหวังว่า custom properties จะ override ได้ทุกอย่าง — custom properties เป็นเพียง token ที่ผู้เขียน component เลือกเปิดให้ผ่าน
var()เท่านั้น ไม่ได้ให้สิทธิ์หน้าเว็บ override โครงสร้างหรือ layout ภายใน shadow root ทั้งหมด
💡 ตัวอย่างจากของจริง
Shoelace — เปิด CSS custom-property API ที่สม่ำเสมอทั่วทั้งชุด component ทำให้ผู้ใช้ปรับสีและ spacing ของทุก component ได้จากจุดเดียวโดยไม่ต้องแตะ shadow DOM ภายใน
Lit —
csstagged template ของ Lit สร้างCSSStyleSheetที่แชร์กันในระดับ class แล้ว adopt เข้า shadow root ของทุก instance โดยอัตโนมัติ ตรงกับแนวคิด Constructable Stylesheets ที่อธิบายในบทนี้