{
    "slug": "shadow_dom",
    "term": "Shadow DOM",
    "category": "frontend",
    "difficulty": "intermediate",
    "short": "A browser feature that attaches a scoped, encapsulated DOM subtree to an element — styles and IDs inside the shadow tree do not leak in or out, enabling true component isolation on the web.",
    "long": "Shadow DOM is one of the three foundational pillars of Web Components (alongside Custom Elements and HTML Templates). It lets you attach a 'shadow root' to a host element with Element.attachShadow({ mode: 'open' | 'closed' }), creating a separate DOM subtree whose CSS rules, IDs, and JavaScript queries are scoped to that root. Selectors like document.querySelector('#foo') do not match elements inside a shadow tree, and page-level CSS cannot accidentally restyle shadow content. This is how native elements such as <video> and <input type=\"range\"> keep their internal structure hidden from the page. The boundary is crossed explicitly — via slots for projected content, CSS custom properties and ::part() for theming, and the host's exposed API for JS. 'Open' mode exposes the shadowRoot property for debugging; 'closed' hides it entirely. Shadow DOM is the mechanism that makes encapsulated, drop-in UI components possible in plain browsers without a framework.",
    "aliases": [
        "Shadow Root",
        "DOM Encapsulation"
    ],
    "tags": [
        "web-components",
        "dom",
        "encapsulation",
        "css-isolation"
    ],
    "misconception": "Shadow DOM isolates styles but not events — events still bubble up through the shadow boundary, though they are retargeted so the event.target from outside the shadow root points at the host element, not the inner node.",
    "why_it_matters": "Without style encapsulation, any third-party widget can be broken by a global CSS rule, and any page CSS can be broken by a widget. Shadow DOM gives you a browser-native way to ship reusable UI without namespace-prefixing classnames or shipping an entire framework runtime.",
    "common_mistakes": [
        "Trying to style shadow-DOM content with page CSS — use CSS custom properties or the ::part() selector with a matching part=\"…\" attribute on the inner element.",
        "Using document.querySelector() to find shadow-DOM elements — you must traverse through the host's shadowRoot property.",
        "Attaching shadow root twice on the same element — attachShadow() throws on a second call; check element.shadowRoot first.",
        "Forgetting the <slot> element for projected content — without a slot, children placed inside the host in markup are not rendered.",
        "Assuming focus and form participation work automatically — form-associated custom elements need the ElementInternals API; plain shadow trees do not submit values with forms."
    ],
    "when_to_use": [
        "Building reusable UI widgets that must survive being dropped into unknown CSS environments.",
        "Creating design-system components where internal structure should be an implementation detail.",
        "Wrapping third-party content that you do not want page styles to leak into."
    ],
    "avoid_when": [
        "You control both the page and the component — regular CSS-in-JS or scoped stylesheets are simpler.",
        "SEO-critical content needs to be inside — shadow trees render fine but some older crawlers and tools have gaps; verify your target audience."
    ],
    "related": [
        "web_components",
        "js_web_components",
        "css_custom_properties",
        "js_custom_events"
    ],
    "prerequisites": [
        "html_forms",
        "css_cascade",
        "js_dom_manipulation"
    ],
    "refs": [
        "https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM",
        "https://web.dev/articles/shadowdom-v1"
    ],
    "bad_code": "<!-- Global CSS breaks widget internals -->\n<style>.label { color: red; }</style>\n<my-widget><span class=\"label\">hello</span></my-widget>",
    "good_code": "class MyWidget extends HTMLElement {\n  constructor() {\n    super();\n    const root = this.attachShadow({ mode: 'open' });\n    root.innerHTML = `\n      <style>\n        .label { color: var(--label-color, black); }\n      </style>\n      <span class=\"label\" part=\"label\"><slot></slot></span>\n    `;\n  }\n}\ncustomElements.define('my-widget', MyWidget);\n// Page CSS can still theme it:\n// my-widget::part(label) { color: blue; }",
    "example_note": "Minimal Shadow DOM with a slot, scoped styles, and a themable part.",
    "quick_fix": "Encapsulate a widget: const root = element.attachShadow({ mode: 'open' }); root.innerHTML = '<style>…</style><slot></slot>';",
    "severity": "info",
    "effort": "medium",
    "created": "2026-04-18",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/shadow_dom",
        "html_url": "https://codeclaritylab.com/glossary/shadow_dom",
        "json_url": "https://codeclaritylab.com/glossary/shadow_dom.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Shadow DOM](https://codeclaritylab.com/glossary/shadow_dom) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/shadow_dom"
            }
        }
    }
}