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

Host Element และ Shadow Boundary

Host element คือ element ที่เป็นเจ้าของ shadow root — เช่น <my-badge> หรือ <host-badge> จากภายใน shadow tree คุณสามารถกำหนดเป้าหมายและสไตล์ host element ได้ด้วย pseudo-class selector :host

เรื่องนี้สำคัญเพราะ host element เองอยู่ใน light DOM ตามปกติ shadow CSS จะเข้าไม่ถึง light DOM เลย — แต่ :host คือข้อยกเว้นพิเศษที่ให้ shadow tree สไตล์ container ของตัวเองได้

:host กำหนดเป้าหมายไปที่ host element จากภายใน shadow root:

/* inside shadow root <style> */
:host {
display: inline-flex;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
border: 2px solid #6366f1;
background: #eef2ff;
}

ถ้าไม่มี :host { display: ... } custom element จะใช้ค่าเริ่มต้น display: inline ซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ

:host(selector) ให้คุณสไตล์ host แบบมีเงื่อนไข ตาม attribute, class หรือ state ของมันเอง:

/* style the host when it has a [disabled] attribute */
:host([disabled]) {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
/* style the host when it has a specific class */
:host(.primary) {
background: #4f46e5;
color: white;
}

pattern นี้พบได้บ่อยมาก — สถานะของ component (active, disabled, highlighted) มักถูกสื่อสารผ่าน attribute และ :host([attr]) คือวิธีที่ถูกต้องในการตอบสนองต่อมันจากภายใน shadow CSS

Shadow boundary เข้มงวดกับ CSS selector ทั้งสองทิศทาง:

/* page CSS — CANNOT reach inside <my-badge> shadow root */
my-badge p { color: red; } /* no effect on shadow tree */
/* shadow CSS — CANNOT reach outside into the light DOM */
p { color: blue; } /* only affects <p> inside this shadow tree */

ข้อยกเว้นมีเพียง CSS custom properties (ตัวแปร) และ pseudo-element ::part() ซึ่งถูกออกแบบมาให้ข้ามขอบเขตได้โดยเฉพาะ

Event ส่วนใหญ่ bubble ผ่าน shadow boundary ได้ แต่เมื่อทำเช่นนั้น browser จะทำ retarget — event.target ที่ listener ใน light DOM เห็นจะเป็น host element ไม่ใช่ element จริงภายใน shadow tree ที่ยิง event นั้นออกมา

document.querySelector('my-badge').addEventListener('click', function(e) {
console.log(e.target); // <my-badge> — not the <button> inside the shadow
});

สิ่งนี้รักษา encapsulation ไว้: โค้ดใน light DOM มองไม่เห็นโครงสร้างภายในของ component ผ่าน event target

Light DOMShadow DOM
นิยามchildren ที่คุณวางไว้ใน element จากภายนอกshadow tree ส่วนตัวที่ element แนบไว้เอง
การมองเห็นอยู่ใน document tree ปกติถูก encapsulate เข้าถึงผ่าน shadow root
การสไตล์สไตล์ด้วย page CSSสไตล์ด้วย shadow CSS
Slotsถูก project เข้า shadow ผ่าน <slot>เป็นที่อยู่ของ <slot> element เอง
ตัวเลือกBenefitCost
Event retargeting ที่ shadow boundaryโค้ดภายนอกไม่เห็น internal structure ของ component ผ่าน event.targetdebug event ยากขึ้น เพราะ event.target ที่ host ไม่ใช่ element จริงที่ถูกคลิกภายใน shadow tree
สไตล์ host ผ่าน :host / :host() จากภายใน shadow CSScomponent ควบคุม box model ของตัวเองได้ (display, border) โดยไม่ต้องพึ่งสไตล์ภายนอกโค้ดภายนอกยังคง override บาง property จาก host element ได้ (เช่น inline style) ทำให้ผลลัพธ์ขึ้นกับใครเขียนทีหลัง
  • คาดหวังว่า event.target ในหน้าเว็บจะชี้ไปที่ element จริงภายใน shadow tree — เพราะ event retargeting จะเปลี่ยน event.target ให้เป็น host element เสมอเมื่อ event ฟองออกมาถึง light DOM
  • ใช้ selector ธรรมดาแทน :host เพื่อสไตล์ตัว host element เอง — เขียน host-badge { border: ... } ใน shadow CSS แล้วสงสัยว่าทำไมไม่ทำงาน ต้องใช้ :host เท่านั้นเพราะ host element อยู่นอก shadow tree
  • สับสนระหว่าง light DOM กับ shadow DOM — คิดว่าเนื้อหาที่ project ผ่าน <slot> อยู่ใน shadow tree ทั้งที่จริงๆ light DOM content ยังคงเป็นของ caller และแก้ไขสไตล์จาก shadow root ไม่ได้โดยตรง

💡 ตัวอย่างจากของจริง

Salesforce Lightning (LWC) — ใช้ :host styling อย่างเป็นระบบเพื่อให้ทุก component ควบคุม layout ของตัวเองได้โดยไม่ต้องพึ่ง global CSS ของหน้า (salesforce/lwc)

selector :host กำหนดเป้าหมายอะไร?
:host([disabled]) ใช้งานเมื่อใด?
CSS selector จากภายนอก shadow root สามารถกำหนดเป้าหมาย element ภายในได้หรือไม่?
light DOM คืออะไร?