ข้ามไปยังเนื้อหา

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 ช่วยให้คุณสร้างอ็อบเจกต์ 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 ได้ทุกที่

เดโมด้านล่างนิยาม element <themed-badge> ที่สีพื้นหลังและสีข้อความถูกควบคุมทั้งหมดด้วย CSS custom properties ที่ตั้งไว้บน host จาก page stylesheet ลองเปลี่ยนค่า --badge-bg ในแผง CSS เพื่อเปลี่ยนธีมได้โดยไม่ต้องแตะโค้ดของ component เลย

ตัวเลือกBenefitCost
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 ภายใน

Litcss tagged template ของ Lit สร้าง CSSStyleSheet ที่แชร์กันในระดับ class แล้ว adopt เข้า shadow root ของทุก instance โดยอัตโนมัติ ตรงกับแนวคิด Constructable Stylesheets ที่อธิบายในบทนี้

CSS custom properties (--vars) ข้ามขอบเขตของ shadow DOM ได้หรือไม่?
ไวยากรณ์ในการกำหนดค่า fallback ให้กับ custom property ภายใน shadow root คืออะไร?
adoptedStyleSheets บน shadow root ทำให้ทำอะไรได้?