CINXE.COM
Shadow DOM v1 - Self-Contained Web Components | Articles | web.dev
<!doctype html> <html lang="en" dir="ltr"> <head> <meta name="google-signin-client-id" content="157101835696-ooapojlodmuabs2do2vuhhnf90bccmoi.apps.googleusercontent.com"> <meta name="google-signin-scope" content="profile email https://www.googleapis.com/auth/developerprofiles https://www.googleapis.com/auth/developerprofiles.award"> <meta property="og:site_name" content="web.dev"> <meta property="og:type" content="website"><meta name="theme-color" content="#3740ff"><meta charset="utf-8"> <meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="manifest" href="/_pwa/web/manifest.json" crossorigin="use-credentials"> <link rel="preconnect" href="//www.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.googleapis.com" crossorigin> <link rel="preconnect" href="//apis.google.com" crossorigin> <link rel="preconnect" href="//www.google-analytics.com" crossorigin><link rel="stylesheet" href="//fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"> <link rel="stylesheet" href="//fonts.googleapis.com/css2?family=Material+Icons&family=Material+Symbols+Outlined&display=block"><link rel="stylesheet" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/css/app.css"> <link rel="stylesheet" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/css/dark-theme.css" disabled> <link rel="shortcut icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/favicon.png"> <link rel="apple-touch-icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/touchicon-180.png"><link rel="canonical" href="https://web.dev/articles/shadowdom-v1"><link rel="search" type="application/opensearchdescription+xml" title="web.dev" href="https://web.dev/s/opensearch.xml"> <link rel="alternate" hreflang="en" href="https://web.dev/articles/shadowdom-v1" /><link rel="alternate" hreflang="x-default" href="https://web.dev/articles/shadowdom-v1" /><link rel="alternate" hreflang="ar" href="https://web.dev/articles/shadowdom-v1?hl=ar" /><link rel="alternate" hreflang="bn" href="https://web.dev/articles/shadowdom-v1?hl=bn" /><link rel="alternate" hreflang="zh-Hans" href="https://web.dev/articles/shadowdom-v1?hl=zh-cn" /><link rel="alternate" hreflang="zh-Hant" href="https://web.dev/articles/shadowdom-v1?hl=zh-tw" /><link rel="alternate" hreflang="fa" href="https://web.dev/articles/shadowdom-v1?hl=fa" /><link rel="alternate" hreflang="fr" href="https://web.dev/articles/shadowdom-v1?hl=fr" /><link rel="alternate" hreflang="de" href="https://web.dev/articles/shadowdom-v1?hl=de" /><link rel="alternate" hreflang="he" href="https://web.dev/articles/shadowdom-v1?hl=he" /><link rel="alternate" hreflang="hi" href="https://web.dev/articles/shadowdom-v1?hl=hi" /><link rel="alternate" hreflang="id" href="https://web.dev/articles/shadowdom-v1?hl=id" /><link rel="alternate" hreflang="it" href="https://web.dev/articles/shadowdom-v1?hl=it" /><link rel="alternate" hreflang="ja" href="https://web.dev/articles/shadowdom-v1?hl=ja" /><link rel="alternate" hreflang="ko" href="https://web.dev/articles/shadowdom-v1?hl=ko" /><link rel="alternate" hreflang="pl" href="https://web.dev/articles/shadowdom-v1?hl=pl" /><link rel="alternate" hreflang="pt-BR" href="https://web.dev/articles/shadowdom-v1?hl=pt-br" /><link rel="alternate" hreflang="ru" href="https://web.dev/articles/shadowdom-v1?hl=ru" /><link rel="alternate" hreflang="es-419" href="https://web.dev/articles/shadowdom-v1?hl=es-419" /><link rel="alternate" hreflang="th" href="https://web.dev/articles/shadowdom-v1?hl=th" /><link rel="alternate" hreflang="tr" href="https://web.dev/articles/shadowdom-v1?hl=tr" /><link rel="alternate" hreflang="vi" href="https://web.dev/articles/shadowdom-v1?hl=vi" /><link rel="alternate" hreflang="en-cn" href="https://web.developers.google.cn/articles/shadowdom-v1" /><link rel="alternate" hreflang="x-default" href="https://web.developers.google.cn/articles/shadowdom-v1" /><link rel="alternate" hreflang="ar-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=ar" /><link rel="alternate" hreflang="bn-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=bn" /><link rel="alternate" hreflang="zh-Hans-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=zh-cn" /><link rel="alternate" hreflang="zh-Hant-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=zh-tw" /><link rel="alternate" hreflang="fa-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=fa" /><link rel="alternate" hreflang="fr-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=fr" /><link rel="alternate" hreflang="de-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=de" /><link rel="alternate" hreflang="he-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=he" /><link rel="alternate" hreflang="hi-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=hi" /><link rel="alternate" hreflang="id-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=id" /><link rel="alternate" hreflang="it-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=it" /><link rel="alternate" hreflang="ja-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=ja" /><link rel="alternate" hreflang="ko-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=ko" /><link rel="alternate" hreflang="pl-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=pl" /><link rel="alternate" hreflang="pt-BR-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=pt-br" /><link rel="alternate" hreflang="ru-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=ru" /><link rel="alternate" hreflang="es-419-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=es-419" /><link rel="alternate" hreflang="th-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=th" /><link rel="alternate" hreflang="tr-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=tr" /><link rel="alternate" hreflang="vi-cn" href="https://web.developers.google.cn/articles/shadowdom-v1?hl=vi" /><title>Shadow DOM v1 - Self-Contained Web Components | Articles | web.dev</title> <meta property="og:title" content="Shadow DOM v1 - Self-Contained Web Components | Articles | web.dev"><meta name="description" content="Shadow DOM allows web developers to create compartmentalized DOM and CSS for web components"> <meta property="og:description" content="Shadow DOM allows web developers to create compartmentalized DOM and CSS for web components"><meta property="og:url" content="https://web.dev/articles/shadowdom-v1"><meta property="og:locale" content="en"><script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Article", "dateModified": "2016-08-01", "headline": "Shadow DOM v1 - Self-Contained Web Components" } </script><script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{ "@type": "ListItem", "position": 1, "name": "Articles", "item": "https://web.dev/articles" },{ "@type": "ListItem", "position": 2, "name": "Shadow DOM v1 - Self-Contained Web Components", "item": "https://web.dev/articles/shadowdom-v1" }] } </script> <link rel="stylesheet" href="/extras.css"></head> <body class="" template="page" theme="web-theme" type="article" appearance layout="docs" display-toc pending> <devsite-progress type="indeterminate" id="app-progress"></devsite-progress> <section class="devsite-wrapper"> <devsite-cookie-notification-bar></devsite-cookie-notification-bar><devsite-header role="banner"> <div class="devsite-header--inner nocontent"> <div class="devsite-top-logo-row-wrapper-wrapper"> <div class="devsite-top-logo-row-wrapper"> <div class="devsite-top-logo-row"> <button type="button" id="devsite-hamburger-menu" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Navigation menu button" visually-hidden aria-label="Open menu"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="webDev" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <source srcset="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup-dark-theme.svg" media="(prefers-color-scheme: dark)" class="devsite-dark-theme" alt="web.dev"> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup.svg" class="devsite-site-logo" alt="web.dev"> </picture> </a> <span class="devsite-product-name"> <ul class="devsite-breadcrumb-list" > <li class="devsite-breadcrumb-item "> </li> </ul> </span> </div> <div class="devsite-top-logo-row-middle"> <div class="devsite-header-upper-tabs"> <devsite-tabs class="upper-tabs"> <nav class="devsite-tabs-wrapper" aria-label="Upper tabs"> <tab > <a href="https://web.dev/about" track-metadata-eventdetail="https://web.dev/about" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - about" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: About" track-name="about" > About </a> </tab> <tab > <a href="https://web.dev/html" track-metadata-eventdetail="https://web.dev/html" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - html" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: HTML" track-name="html" > HTML </a> </tab> <tab > <a href="https://web.dev/css" track-metadata-eventdetail="https://web.dev/css" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - css" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: CSS" track-name="css" > CSS </a> </tab> <tab > <a href="https://web.dev/javascript" track-metadata-eventdetail="https://web.dev/javascript" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - javascript" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: JavaScript" track-name="javascript" > JavaScript </a> </tab> <tab > <a href="https://web.dev/blog" track-metadata-eventdetail="https://web.dev/blog" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - blog" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Blog" track-name="blog" > Blog </a> </tab> <tab > <a href="https://web.dev/learn" track-metadata-eventdetail="https://web.dev/learn" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - learn" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Learn" track-name="learn" > Learn </a> </tab> <tab > <a href="https://web.dev/explore" track-metadata-eventdetail="https://web.dev/explore" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - explore" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Explore" track-name="explore" > Explore </a> </tab> <tab > <a href="https://web.dev/patterns" track-metadata-eventdetail="https://web.dev/patterns" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - patterns" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Patterns" track-name="patterns" > Patterns </a> </tab> <tab > <a href="https://web.dev/case-studies" track-metadata-eventdetail="https://web.dev/case-studies" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - case studies" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Case studies" track-name="case studies" > Case studies </a> </tab> </nav> </devsite-tabs> </div> <devsite-search enable-signin enable-search enable-suggestions enable-query-completion project-name="Articles" tenant-name="web.dev" > <form class="devsite-search-form" action="https://web.dev/s/results" method="GET"> <div class="devsite-search-container"> <button type="button" search-open class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Open search"></button> <div class="devsite-searchbox"> <input aria-activedescendant="" aria-autocomplete="list" aria-label="Search" aria-expanded="false" aria-haspopup="listbox" autocomplete="off" class="devsite-search-field devsite-search-query" name="q" placeholder="Search" role="combobox" type="text" value="" > <div class="devsite-search-image material-icons" aria-hidden="true"> </div> <div class="devsite-search-shortcut-icon-container" aria-hidden="true"> <kbd class="devsite-search-shortcut-icon">/</kbd> </div> </div> </div> </form> <button type="button" search-close class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Close search"></button> </devsite-search> </div> <devsite-appearance-selector></devsite-appearance-selector> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> <devsite-user enable-profiles id="devsite-user"> <span class="button devsite-top-button" aria-hidden="true" visually-hidden>Sign in</span> </devsite-user> </div> </div> </div> <div class="devsite-collapsible-section devsite-header-no-lower-tabs "> <div class="devsite-header-background"> </div> </div> </div> </devsite-header> <devsite-book-nav scrollbars hidden> <div class="devsite-book-nav-filter" hidden> <span class="filter-list-icon material-icons" aria-hidden="true"></span> <input type="text" placeholder="Filter" aria-label="Type to filter" role="searchbox"> <span class="filter-clear-button hidden" data-title="Clear filter" aria-label="Clear filter" role="button" tabindex="0"></span> </div> <nav class="devsite-book-nav devsite-nav nocontent" aria-label="Side menu"> <div class="devsite-mobile-header"> <button type="button" id="devsite-close-nav" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Close navigation" aria-label="Close navigation"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="webDev" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <source srcset="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup-dark-theme.svg" media="(prefers-color-scheme: dark)" class="devsite-dark-theme" alt="web.dev"> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup.svg" class="devsite-site-logo" alt="web.dev"> </picture> </a> <span class="devsite-product-name"> <ul class="devsite-breadcrumb-list" > <li class="devsite-breadcrumb-item "> </li> </ul> </span> </div> </div> <div class="devsite-book-nav-wrapper"> <div class="devsite-mobile-nav-top"> <ul class="devsite-nav-list"> <li class="devsite-nav-item"> <a href="/about" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: About" track-name="about" data-category="Site-Wide Custom Events" data-label="Responsive Tab: About" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > About </span> </a> </li> <li class="devsite-nav-item"> <a href="/html" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: HTML" track-name="html" data-category="Site-Wide Custom Events" data-label="Responsive Tab: HTML" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > HTML </span> </a> </li> <li class="devsite-nav-item"> <a href="/css" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: CSS" track-name="css" data-category="Site-Wide Custom Events" data-label="Responsive Tab: CSS" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > CSS </span> </a> </li> <li class="devsite-nav-item"> <a href="/javascript" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: JavaScript" track-name="javascript" data-category="Site-Wide Custom Events" data-label="Responsive Tab: JavaScript" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > JavaScript </span> </a> </li> <li class="devsite-nav-item"> <a href="/blog" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Blog" track-name="blog" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Blog" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Blog </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Learn" track-name="learn" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Learn" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Learn </span> </a> </li> <li class="devsite-nav-item"> <a href="/explore" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Explore" track-name="explore" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Explore" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Explore </span> </a> </li> <li class="devsite-nav-item"> <a href="/patterns" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Patterns" track-name="patterns" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Patterns" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Patterns </span> </a> </li> <li class="devsite-nav-item"> <a href="/case-studies" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Case studies" track-name="case studies" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Case studies" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Case studies </span> </a> </li> </ul> </div> </div> </nav> </devsite-book-nav> <section id="gc-wrapper"> <main role="main" class="devsite-main-content" has-sidebar > <div class="devsite-sidebar"> <div class="devsite-sidebar-content"> <devsite-toc class="devsite-nav" role="navigation" aria-label="On this page" depth="2" scrollbars ></devsite-toc> <devsite-recommendations-sidebar class="nocontent devsite-nav"> </devsite-recommendations-sidebar> </div> </div> <devsite-content> <article class="devsite-article"> <div class="devsite-article-meta nocontent" role="navigation"> <ul class="devsite-breadcrumb-list" aria-label="Breadcrumb"> <li class="devsite-breadcrumb-item "> <a href="https://web.dev/" class="devsite-breadcrumb-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Breadcrumbs" data-value="1" track-type="globalNav" track-name="breadcrumb" track-metadata-position="1" track-metadata-eventdetail="" > Home </a> </li> <li class="devsite-breadcrumb-item "> <div class="devsite-breadcrumb-guillemet material-icons" aria-hidden="true"></div> <a href="https://web.dev/articles" class="devsite-breadcrumb-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Breadcrumbs" data-value="2" track-type="globalNav" track-name="breadcrumb" track-metadata-position="2" track-metadata-eventdetail="Articles" > Articles </a> </li> </ul> <devsite-thumb-rating position="header"> </devsite-thumb-rating> </div> <h1 class="devsite-page-title" tabindex="-1"> Shadow DOM v1 - Self-Contained Web Components </h1> <devsite-feature-tooltip ack-key="AckCollectionsBookmarkTooltipDismiss" analytics-category="Site-Wide Custom Events" analytics-action-show="Callout Profile displayed" analytics-action-close="Callout Profile dismissed" analytics-label="Create Collection Callout" class="devsite-page-bookmark-tooltip nocontent" dismiss-button="true" id="devsite-collections-dropdown" dismiss-button-text="Dismiss" close-button-text="Got it"> <devsite-bookmark></devsite-bookmark> <span slot="popout-heading"> Stay organized with collections </span> <span slot="popout-contents"> Save and categorize content based on your preferences. </span> </devsite-feature-tooltip> <div class="devsite-page-title-meta"><devsite-view-release-notes></devsite-view-release-notes></div> <devsite-toc class="devsite-nav" depth="2" devsite-toc-embedded > </devsite-toc> <div class="devsite-article-body clearfix "> <p>Shadow DOM allows web developers to create compartmentalized DOM and CSS for web components</p> <p> <div class="wd-authors" translate="no"> <div class="wd-author"> <div> <span> Eric Bidelman </span> <div class="wd-author__links"> <a href="https://twitter.com/ebidel" aria-label="Eric Bidelman on X" rel="me"> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 300 271"> <title>X</title> <path fill="currentColor" d="m236 0h46l-101 115 118 156h-92.6l-72.5-94.8-83 94.8h-46l107-123-113-148h94.9l65.5 86.6zm-16.1 244h25.5l-165-218h-27.4z"></path> </svg></a> <a href="https://github.com/ebidel" aria-label="Eric Bidelman on GitHub" rel="me"> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 32.6 31.8"> <title>GitHub</title> <path d="M16.3 0C7.3 0 0 7.3 0 16.3c0 7.2 4.7 13.3 11.1 15.5.8.1 1.1-.4 1.1-.8v-2.8c-4.5 1-5.5-2.2-5.5-2.2-.7-1.9-1.8-2.4-1.8-2.4-1.5-1 .1-1 .1-1 1.6.1 2.5 1.7 2.5 1.7 1.5 2.5 3.8 1.8 4.7 1.4.1-1.1.6-1.8 1-2.2-3.6-.4-7.4-1.8-7.4-8.1 0-1.8.6-3.2 1.7-4.4-.1-.3-.7-2 .2-4.2 0 0 1.4-.4 4.5 1.7 1.3-.4 2.7-.5 4.1-.5 1.4 0 2.8.2 4.1.5 3.1-2.1 4.5-1.7 4.5-1.7.9 2.2.3 3.9.2 4.3 1 1.1 1.7 2.6 1.7 4.4 0 6.3-3.8 7.6-7.4 8 .6.5 1.1 1.5 1.1 3V31c0 .4.3.9 1.1.8 6.5-2.2 11.1-8.3 11.1-15.5C32.6 7.3 25.3 0 16.3 0z" fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" /> </svg></a> <a href="http://ericbidelman.com/" aria-label="Eric Bidelman's homepage" rel="me"> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 30 30"> <title>Homepage</title> <circle cx="14.5" cy="14.5" r="13.5" stroke-width="2" stroke-miterlimit="10" fill="none" stroke="currentColor" /> <ellipse cx="14.5" cy="14.5" rx="6.1" ry="13.5" stroke-width="2" stroke-miterlimit="10" fill="none" stroke="currentColor" /> <path d="M1.6 9.6h25.8M1.6 19.4h25.8" stroke-width="2" stroke-miterlimit="10" fill="none" stroke="currentColor" /> </svg></a> </div> </div> </div> </div></p> <h2 id="summary" data-text="Summary" tabindex="-1">Summary</h2> <p>Shadow DOM removes the brittleness of building web apps. The brittleness comes from the global nature of HTML, CSS, and JS. Over the years we've invented an exorbitant <a href="http://getbem.com/introduction/">number</a> <a href="https://github.com/css-modules/css-modules">of</a> <a href="https://www.smashingmagazine.com/2011/12/an-introduction-to-object-oriented-css-oocss/">tools</a> to circumvent the issues. For example, when you use a new HTML id/class, there's no telling if it will conflict with an existing name used by the page. <a href="http://www.2ality.com/2012/08/ids-are-global.html">Subtle bugs</a> creep up, CSS specificity becomes a huge issue (<code translate="no" dir="ltr">!important</code> all the things!), style selectors grow out of control, and <a href="https://developer.chrome.com/blog/css-containment">performance can suffer</a>. The list goes on.</p> <p><strong>Shadow DOM fixes CSS and DOM</strong>. It introduces <strong>scoped styles</strong> to the web platform. Without tools or naming conventions, you can <strong>bundle CSS with markup</strong>, hide implementation details, and <strong>author self-contained components</strong> in vanilla JavaScript.</p> <h2 id="introduction" data-text="Introduction" tabindex="-1">Introduction</h2> <aside class="note"><strong>Note:</strong><span> <strong>Already familiar with Shadow DOM?</strong> This article describes the new <a href="http://w3c.github.io/webcomponents/spec/shadow/">Shadow DOM v1 spec</a>. If you've been using Shadow DOM, chances are you're familiar with the <a href="https://www.chromestatus.com/features/4507242028072960">v0 version that shipped in Chrome 35</a>, and the webcomponents.js polyfills. The concepts are the same, but the v1 spec has important API differences. It's also the version that all major browsers have agreed to implement, with implementations already in Safari, Chrome and Firefox. Keep reading to see what's new or check out the section on <a href="#history_browser_support">History and browser support</a> for more info.</span></aside> <p>Shadow DOM is one of the three Web Component standards: <a href="https://www.html5rocks.com/en/tutorials/webcomponents/template/">HTML Templates</a>, <a href="https://dom.spec.whatwg.org/#shadow-trees">Shadow DOM</a> and <a href="/articles/custom-elements-v1">Custom elements</a>. <a href="https://www.html5rocks.com/en/tutorials/webcomponents/imports/">HTML Imports</a> used to be part of the list but now are considered <a href="https://developer.chrome.com/blog/chrome-70-deps-rems#deprecate-html-imports">deprecated</a>.</p> <p>You don't have to author web components that use shadow DOM. But when you do, you take advantage of its benefits (CSS scoping, DOM encapsulation, composition) and build reusable <a href="/articles/custom-elements-v1">custom elements</a>, which are resilient, highly configurable, and extremely reusable. If custom elements are the way to create a new HTML (with a JS API), shadow DOM is the way you provide its HTML and CSS. The two APIs combine to make a component with self-contained HTML, CSS, and JavaScript.</p> <p>Shadow DOM is designed as a tool for building component-based apps. Therefore, it brings solutions for common problems in web development:</p> <ul> <li><strong>Isolated DOM</strong>: A component's DOM is self-contained (e.g. <code translate="no" dir="ltr">document.querySelector()</code> won't return nodes in the component's shadow DOM).</li> <li><strong>Scoped CSS</strong>: CSS defined inside shadow DOM is scoped to it. Style rules don't leak out and page styles don't bleed in.</li> <li><strong>Composition</strong>: Design a declarative, markup-based API for your component.</li> <li><strong>Simplifies CSS</strong> - Scoped DOM means you can use simple CSS selectors, more generic id/class names, and not worry about naming conflicts.</li> <li><strong>Productivity</strong> - Think of apps in chunks of DOM rather than one large (global) page.</li> </ul> <aside class="note"><b>Note: </b> Although you can use the shadow DOM API and its benefits outside of web components, I'm only going to focus on examples that build on custom elements. I'll be using the custom elements v1 API in all examples. </aside> <h3 id="fancy-tabs_demo" data-text="fancy-tabs demo" tabindex="-1"><code translate="no" dir="ltr">fancy-tabs</code> demo</h3> <p>Throughout this article, I'll be referring to a demo component (<code translate="no" dir="ltr"><fancy-tabs></code>) and referencing code snippets from it. If your browser supports the APIs, you should see a live demo of it just below. Otherwise, check out the <a href="https://gist.github.com/ebidel/2d2bb0cdec3f2a16cf519dbaa791ce1b">full source on Github</a>.</p> <figure class="demoarea"> <iframe style="height:360px;width:100%;border:none" src="https://rawgit.com/ebidel/2d2bb0cdec3f2a16cf519dbaa791ce1b/raw/fancy-tabs-demo.html"> </iframe> <figcaption class="wd-caption"> <a href="https://gist.github.com/ebidel/2d2bb0cdec3f2a16cf519dbaa791ce1b" target="_blank"> View source on Github </a> </figcaption> </figure> <h2 id="what_is_shadow_dom" data-text="What is shadow DOM?" tabindex="-1">What is shadow DOM?</h2> <h3 id="background_on_dom" data-text="Background on DOM" tabindex="-1">Background on DOM</h3> <p>HTML powers the web because it's easy to work with. By declaring a few tags, you can author a page in seconds that has both presentation and structure. However, by itself HTML isn't all that useful. It's easy for humans to understand a text- based language, but machines need something more. Enter the Document Object Model, or DOM.</p> <p>When the browser loads a web page it does a bunch of interesting stuff. One of the things it does is transform the author's HTML into a live document. Basically, to understand the page's structure, the browser parses HTML (static strings of text) into a data model (objects/nodes). The browser preserves the HTML's hierarchy by creating a tree of these nodes: the DOM. The cool thing about DOM is that it's a live representation of your page. Unlike the static HTML we author, the browser-produced nodes contain properties, methods, and best of all… can be manipulated by programs! That's why we're able to create DOM elements directly using JavaScript:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">header</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">createElement</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'header'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">h1</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">createElement</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'h1'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-nx">h1</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">textContent</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s1">'Hello DOM'</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-nx">header</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">appendChild</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">h1</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">body</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">appendChild</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">header</span><span class="devsite-syntax-p">);</span> </code></pre></devsite-code> <p>produces the following HTML markup:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><body> <header> <h1>Hello DOM</h1> </header> </body> </code></pre></devsite-code> <p>All that is well and good. Then <a href="https://glazkov.com/2011/01/14/what-the-heck-is-shadow-dom/">what the heck is <em>shadow DOM</em></a>?</p> <h4 id="dom…_in_the_shadows" data-text="DOM… in the shadows" tabindex="-1">DOM… in the shadows</h4> <p>Shadow DOM is just normal DOM with two differences: 1) how it's created/used and 2) how it behaves in relation to the rest of the page. Normally, you create DOM nodes and append them as children of another element. With shadow DOM, you create a scoped DOM tree that's attached to the element, but separate from its actual children. This scoped subtree is called a <strong>shadow tree</strong>. The element it's attached to is its <strong>shadow host</strong>. Anything you add in the shadows becomes local to the hosting element, including <code translate="no" dir="ltr"><style></code>. This is how shadow DOM achieves CSS style scoping.</p> <h2 id="creating_shadow_dom" data-text="Creating shadow DOM" tabindex="-1">Creating shadow DOM</h2> <p>A <strong>shadow root</strong> is a document fragment that gets attached to a “host” element. The act of attaching a shadow root is how the element gains its shadow DOM. To create shadow DOM for an element, call <code translate="no" dir="ltr">element.attachShadow()</code>:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">header</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">createElement</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'header'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">header</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">attachShadow</span><span class="devsite-syntax-p">({</span><span class="devsite-syntax-nx">mode</span><span class="devsite-syntax-o">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s1">'open'</span><span class="devsite-syntax-p">});</span> <span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">innerHTML</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s1">'<h1>Hello Shadow DOM</h1>'</span><span class="devsite-syntax-p">;</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// Could also use appendChild().</span> <span class="devsite-syntax-c1">// header.shadowRoot === shadowRoot</span> <span class="devsite-syntax-c1">// shadowRoot.host === header</span> </code></pre></devsite-code> <p>I'm using <code translate="no" dir="ltr">.innerHTML</code> to fill the shadow root, but you could also use other DOM APIs. This is the web. We have choice.</p> <p>The spec <a href="https://dom.spec.whatwg.org/#dom-element-attachshadow">defines a list of elements</a> that can't host a shadow tree. There are several reasons an element might be on the list:</p> <ul> <li>The browser already hosts its own internal shadow DOM for the element (<code translate="no" dir="ltr"><textarea></code>, <code translate="no" dir="ltr"><input></code>).</li> <li>It doesn't make sense for the element to host a shadow DOM (<code translate="no" dir="ltr"><img></code>).</li> </ul> <p>For example, this doesn't work:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">createElement</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'input'</span><span class="devsite-syntax-p">).</span><span class="devsite-syntax-nx">attachShadow</span><span class="devsite-syntax-p">({</span><span class="devsite-syntax-nx">mode</span><span class="devsite-syntax-o">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s1">'open'</span><span class="devsite-syntax-p">});</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// Error. `<input>` cannot host shadow dom.</span> </code></pre></devsite-code> <h3 id="creating_shadow_dom_for_a_custom_element" data-text="Creating shadow DOM for a custom element" tabindex="-1">Creating shadow DOM for a custom element</h3> <p>Shadow DOM is particularly useful when creating <a href="/articles/custom-elements-v1">custom elements</a>. Use shadow DOM to compartmentalize an element's HTML, CSS, and JS, thus producing a "web component".</p> <p><strong>Example</strong> - a custom element <strong>attaches shadow DOM to itself</strong>, encapsulating its DOM/CSS:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">// Use custom elements API v1 to register a new HTML tag and define its JS behavior</span> <span class="devsite-syntax-c1">// using an ES6 class. Every instance of <fancy-tab> will have this same prototype.</span> <span class="devsite-syntax-nx">customElements</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">define</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'fancy-tabs'</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-kd">class</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">extends</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">HTMLElement</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-kr">constructor</span><span class="devsite-syntax-p">()</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">super</span><span class="devsite-syntax-p">();</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// always call super() first in the constructor.</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// Attach a shadow root to <fancy-tabs>.</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">this</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">attachShadow</span><span class="devsite-syntax-p">({</span><span class="devsite-syntax-nx">mode</span><span class="devsite-syntax-o">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s1">'open'</span><span class="devsite-syntax-p">});</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">innerHTML</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-sb">`</span> <span class="devsite-syntax-sb"> <style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! --></span> <span class="devsite-syntax-sb"> <div id="tabs">...</div></span> <span class="devsite-syntax-sb"> <div id="panels">...</div></span> <span class="devsite-syntax-sb"> `</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">...</span> <span class="devsite-syntax-p">});</span> </code></pre></devsite-code> <p>There are a couple of interesting things going on here. The first is that the custom element <strong>creates its own shadow DOM</strong> when an instance of <code translate="no" dir="ltr"><fancy-tabs></code> is created. That's done in the <code translate="no" dir="ltr">constructor()</code>. Secondly, because we're creating a shadow root, the CSS rules inside the <code translate="no" dir="ltr"><style></code> will be scoped to <code translate="no" dir="ltr"><fancy-tabs></code>.</p> <aside class="note"><strong>Note:</strong><span> When you try to run this example, you'll probably notice that nothing renders. The user's markup seemingly disappears! That's because the <strong>element's shadow DOM is rendered in place of its children</strong>. If you want to display the children, you need to tell the browser where to render them by placing a <a href="#slots"><code translate="no" dir="ltr"><slot></code> element</a> in your shadow DOM. More on that <a href="#history_browser_support">later</a>.</span></aside> <h2 id="composition_and_slots" data-text="Composition and slots" tabindex="-1">Composition and slots</h2> <p>Composition is one of the least understood features of shadow DOM, but it's arguably the most important.</p> <p>In our world of web development, composition is how we construct apps, declaratively out of HTML. Different building blocks (<code translate="no" dir="ltr"><div></code>s, <code translate="no" dir="ltr"><header></code>s, <code translate="no" dir="ltr"><form></code>s, <code translate="no" dir="ltr"><input></code>s) come together to form apps. Some of these tags even work with each other. Composition is why native elements like <code translate="no" dir="ltr"><select></code>, <code translate="no" dir="ltr"><details></code>, <code translate="no" dir="ltr"><form></code>, and <code translate="no" dir="ltr"><video></code> are so flexible. Each of those tags accepts certain HTML as children and does something special with them. For example, <code translate="no" dir="ltr"><select></code> knows how to render <code translate="no" dir="ltr"><option></code> and <code translate="no" dir="ltr"><optgroup></code> into dropdown and multi-select widgets. The <code translate="no" dir="ltr"><details></code> element renders <code translate="no" dir="ltr"><summary></code> as a expandable arrow. Even <code translate="no" dir="ltr"><video></code> knows how to deal with certain children: <code translate="no" dir="ltr"><source></code> elements don't get rendered, but they do affect the video's behavior. What magic!</p> <h3 id="terminology_light_dom_vs_shadow_dom" data-text="Terminology: light DOM vs. shadow DOM" tabindex="-1">Terminology: light DOM vs. shadow DOM</h3> <p>Shadow DOM composition introduces a bunch of new fundamentals in web development. Before getting into the weeds, let's standardize on some terminology so we're speaking the same lingo.</p> <p><strong>Light DOM</strong></p> <p>The markup a user of your component writes. This DOM lives outside the component's shadow DOM. It is the element's actual children.</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><better-button> <!-- the image and span are better-button's light DOM --> <img src="gear.svg" slot="icon"> <span>Settings</span> </better-button> </code></pre></devsite-code> <p><strong>Shadow DOM</strong></p> <p>The DOM a component author writes. Shadow DOM is local to the component and defines its internal structure, scoped CSS, and encapsulates your implementation details. It can also define how to render markup that's authored by the consumer of your component.</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr">#shadow-root <style>...</style> <slot name="icon"></slot> <span id="wrapper"> <slot>Button</slot> </span> </code></pre></devsite-code> <p><strong>Flattened DOM tree</strong></p> <p>The result of the browser distributing the user's light DOM into your shadow DOM, rendering the final product. The flattened tree is what you ultimately see in the DevTools and what's rendered on the page.</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><better-button> #shadow-root <style>...</style> <slot name="icon"> <img src="gear.svg" slot="icon"> </slot> <span id="wrapper"> <slot> <span>Settings</span> </slot> </span> </better-button> </code></pre></devsite-code> <h3 id="slots" data-text="The <slot> element" tabindex="-1">The <slot> element</h3> <p>Shadow DOM composes different DOM trees together using the <code translate="no" dir="ltr"><slot></code> element. <strong>Slots are placeholders inside your component that users <em>can</em> fill with their own markup</strong>. By defining one or more slots, you invite outside markup to render in your component's shadow DOM. Essentially, you're saying <em>"Render the user's markup over here"</em>.</p> <aside class="note"><strong>Note:</strong><span> Slots are a way of creating a "declarative API" for a web component. They mix-in the user's DOM to help render the overall component, thus, <strong>composing different DOM trees together</strong>.</span></aside> <p>Elements are allowed to "cross" the shadow DOM boundary when a <code translate="no" dir="ltr"><slot></code> invites them in. These elements are called <strong>distributed nodes</strong>. Conceptually, distributed nodes can seem a bit bizarre. Slots don't physically move DOM; they render it at another location inside the shadow DOM.</p> <p>A component can define zero or more slots in its shadow DOM. Slots can be empty or provide fallback content. If the user doesn't provide <a href="#terminology_light_dom_vs_shadow_dom">light DOM</a> content, the slot renders its fallback content.</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><!-- Default slot. If there's more than one default slot, the first is used. --> <slot></slot> <slot>fallback content</slot> <!-- default slot with fallback content --> <slot> <!-- default slot entire DOM tree as fallback --> <h2>Title</h2> <summary>Description text</summary> </slot> </code></pre></devsite-code> <p>You can also create <strong>named slots</strong>. Named slots are specific holes in your shadow DOM that users reference by name.</p> <p><strong>Example</strong> - the slots in <code translate="no" dir="ltr"><fancy-tabs></code>'s shadow DOM:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr">#shadow-root <div id="tabs"> <slot id="tabsSlot" name="title"></slot> <!-- named slot --> </div> <div id="panels"> <slot id="panelsSlot"></slot> </div> </code></pre></devsite-code> <p>Component users declare <code translate="no" dir="ltr"><fancy-tabs></code> like so:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><fancy-tabs> <button slot="title">Title</button> <button slot="title" selected>Title 2</button> <button slot="title">Title 3</button> <section>content panel 1</section> <section>content panel 2</section> <section>content panel 3</section> </fancy-tabs> <!-- Using <h2>'s and changing the ordering would also work! --> <fancy-tabs> <h2 slot="title">Title</h2> <section>content panel 1</section> <h2 slot="title" selected>Title 2</h2> <section>content panel 2</section> <h2 slot="title">Title 3</h2> <section>content panel 3</section> </fancy-tabs> </code></pre></devsite-code> <p>And if you're wondering, the flattened tree looks something like this:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><fancy-tabs> #shadow-root <div id="tabs"> <slot id="tabsSlot" name="title"> <button slot="title">Title</button> <button slot="title" selected>Title 2</button> <button slot="title">Title 3</button> </slot> </div> <div id="panels"> <slot id="panelsSlot"> <section>content panel 1</section> <section>content panel 2</section> <section>content panel 3</section> </slot> </div> </fancy-tabs> </code></pre></devsite-code> <p>Notice our component is able to handle different configurations, but the flattened DOM tree remains the same. We can also switch from <code translate="no" dir="ltr"><button></code> to <code translate="no" dir="ltr"><h2></code>. This component was authored to handle different types of children… just like <code translate="no" dir="ltr"><select></code> does!</p> <h2 id="styling" data-text="Styling" tabindex="-1">Styling</h2> <p>There are many options for styling web components. A component that uses shadow DOM can be styled by the main page, define its own styles, or provide hooks (in the form of <a href="https://developer.mozilla.org/docs/Web/CSS/Using_CSS_variables">CSS custom properties</a>) for users to override defaults.</p> <h3 id="component-defined_styles" data-text="Component-defined styles" tabindex="-1">Component-defined styles</h3> <p>Hands down the most useful feature of shadow DOM is <strong>scoped CSS</strong>:</p> <ul> <li>CSS selectors from the outer page don't apply inside your component.</li> <li>Styles defined inside don't bleed out. They're scoped to the host element.</li> </ul> <p><strong>CSS selectors used inside shadow DOM apply locally to your component</strong>. In practice, this means we can use common id/class names again, without worrying about conflicts elsewhere on the page. Simpler CSS selectors are a best practice inside Shadow DOM. They're also good for performance.</p> <p><strong>Example</strong> - styles defined in a shadow root are local</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr">#shadow-root <style> #panels { box-shadow: 0 2px 2px rgba(0, 0, 0, .3); background: white; ... } #tabs { display: inline-flex; ... } </style> <div id="tabs"> ... </div> <div id="panels"> ... </div> </code></pre></devsite-code> <p>Stylesheets are also scoped to the shadow tree:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr">#shadow-root <link rel="stylesheet" href="styles.css"> <div id="tabs"> ... </div> <div id="panels"> ... </div> </code></pre></devsite-code> <p>Ever wonder how the <code translate="no" dir="ltr"><select></code> element renders a multi-select widget (instead of a dropdown) when you add the <code translate="no" dir="ltr">multiple</code> attribute:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><select multiple> <option>Do</option> <option selected>Re</option> <option>Mi</option> <option>Fa</option> <option>So</option> </select> </code></pre></devsite-code> <p><code translate="no" dir="ltr"><select></code> is able to style <em>itself</em> differently based on the attributes you declare on it. Web components can style themselves too, by using the <code translate="no" dir="ltr">:host</code> selector.</p> <p><strong>Example</strong> - a component styling itself</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><style> :host { display: block; /* by default, custom elements are display: inline */ contain: content; /* CSS containment FTW. */ } </style> </code></pre></devsite-code> <p>One gotcha with <code translate="no" dir="ltr">:host</code> is that rules in the parent page have higher specificity than <code translate="no" dir="ltr">:host</code> rules defined in the element. That is, outside styles win. This allows users to override your top-level styling from the outside. Also, <code translate="no" dir="ltr">:host</code> only works in the context of a shadow root, so you can't use it outside of shadow DOM.</p> <p>The functional form of <code translate="no" dir="ltr">:host(<selector>)</code> allows you to target the host if it matches a <code translate="no" dir="ltr"><selector></code>. This is a great way for your component to encapsulate behaviors that react to user interaction or state or style internal nodes based on the host.</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><style> :host { opacity: 0.4; will-change: opacity; transition: opacity 300ms ease-in-out; } :host(:hover) { opacity: 1; } :host([disabled]) { /* style when host has disabled attribute. */ background: grey; pointer-events: none; opacity: 0.4; } :host(.blue) { color: blue; /* color host when it has class="blue" */ } :host(.pink) > #tabs { color: pink; /* color internal #tabs node when host has class="pink". */ } </style> </code></pre></devsite-code> <h3 id="styling_based_on_context" data-text="Styling based on context" tabindex="-1">Styling based on context</h3> <p><code translate="no" dir="ltr">:host-context(<selector>)</code> matches the component if it or any of its ancestors matches <code translate="no" dir="ltr"><selector></code>. A common use for this is theming based on a component's surroundings. For example, many people do theming by applying a class to <code translate="no" dir="ltr"><html></code> or <code translate="no" dir="ltr"><body></code>:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><body class="darktheme"> <fancy-tabs> ... </fancy-tabs> </body> </code></pre></devsite-code> <p><code translate="no" dir="ltr">:host-context(.darktheme)</code> would style <code translate="no" dir="ltr"><fancy-tabs></code> when it's a descendant of <code translate="no" dir="ltr">.darktheme</code>:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="CSS"><code translate="no" dir="ltr"><span class="devsite-syntax-p">:</span><span class="devsite-syntax-nd">host-context</span><span class="devsite-syntax-o">(</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nc">darktheme</span><span class="devsite-syntax-o">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">color</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-kc">white</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">background</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-kc">black</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <p><code translate="no" dir="ltr">:host-context()</code> can be useful for theming, but an even better approach is to <a href="#styling">create style hooks using CSS custom properties</a>.</p> <h3 id="styling_distributed_nodes" data-text="Styling distributed nodes" tabindex="-1">Styling distributed nodes</h3> <p><code translate="no" dir="ltr">::slotted(<compound-selector>)</code> matches nodes that are distributed into a <code translate="no" dir="ltr"><slot></code>.</p> <p>Let's say we've created a name badge component:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><name-badge> <h2>Eric Bidelman</h2> <span class="title"> Digital Jedi, <span class="company">Google</span> </span> </name-badge> </code></pre></devsite-code> <p>The component's shadow DOM can style the user's <code translate="no" dir="ltr"><h2></code> and <code translate="no" dir="ltr">.title</code>:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><style> ::slotted(h2) { margin: 0; font-weight: 300; color: red; } ::slotted(.title) { color: orange; } /* DOESN'T WORK (can only select top-level nodes). ::slotted(.company), ::slotted(.title .company) { text-transform: uppercase; } */ </style> <slot></slot> </code></pre></devsite-code> <p>If you remember from before, <code translate="no" dir="ltr"><slot></code>s do not move the user's light DOM. When nodes are distributed into a <code translate="no" dir="ltr"><slot></code>, the <code translate="no" dir="ltr"><slot></code> renders their DOM but the nodes physically stay put. <strong>Styles that applied before distribution continue to apply after distribution</strong>. However, when the light DOM is distributed, it <em>can</em> take on additional styles (ones defined by the shadow DOM).</p> <p>Another, more in-depth example from <code translate="no" dir="ltr"><fancy-tabs></code>:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">this</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">attachShadow</span><span class="devsite-syntax-p">({</span><span class="devsite-syntax-nx">mode</span><span class="devsite-syntax-o">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s1">'open'</span><span class="devsite-syntax-p">});</span> <span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">innerHTML</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-sb">`</span> <span class="devsite-syntax-sb"> <style></span> <span class="devsite-syntax-sb"> #panels {</span> <span class="devsite-syntax-sb"> box-shadow: 0 2px 2px rgba(0, 0, 0, .3);</span> <span class="devsite-syntax-sb"> background: white;</span> <span class="devsite-syntax-sb"> border-radius: 3px;</span> <span class="devsite-syntax-sb"> padding: 16px;</span> <span class="devsite-syntax-sb"> height: 250px;</span> <span class="devsite-syntax-sb"> overflow: auto;</span> <span class="devsite-syntax-sb"> }</span> <span class="devsite-syntax-sb"> #tabs {</span> <span class="devsite-syntax-sb"> display: inline-flex;</span> <span class="devsite-syntax-sb"> -webkit-user-select: none;</span> <span class="devsite-syntax-sb"> user-select: none;</span> <span class="devsite-syntax-sb"> }</span> <span class="devsite-syntax-sb"> #tabsSlot::slotted(*) {</span> <span class="devsite-syntax-sb"> font: 400 16px/22px 'Roboto';</span> <span class="devsite-syntax-sb"> padding: 16px 8px;</span> <span class="devsite-syntax-sb"> ...</span> <span class="devsite-syntax-sb"> }</span> <span class="devsite-syntax-sb"> #tabsSlot::slotted([aria-selected="true"]) {</span> <span class="devsite-syntax-sb"> font-weight: 600;</span> <span class="devsite-syntax-sb"> background: white;</span> <span class="devsite-syntax-sb"> box-shadow: none;</span> <span class="devsite-syntax-sb"> }</span> <span class="devsite-syntax-sb"> #panelsSlot::slotted([aria-hidden="true"]) {</span> <span class="devsite-syntax-sb"> display: none;</span> <span class="devsite-syntax-sb"> }</span> <span class="devsite-syntax-sb"> </style></span> <span class="devsite-syntax-sb"> <div id="tabs"></span> <span class="devsite-syntax-sb"> <slot id="tabsSlot" name="title"></slot></span> <span class="devsite-syntax-sb"> </div></span> <span class="devsite-syntax-sb"> <div id="panels"></span> <span class="devsite-syntax-sb"> <slot id="panelsSlot"></slot></span> <span class="devsite-syntax-sb"> </div></span> <span class="devsite-syntax-sb">`</span><span class="devsite-syntax-p">;</span> </code></pre></devsite-code> <p>In this example, there are two slots: a named slot for the tab titles, and a slot for the tab panel content. When the user selects a tab, we bold their selection and reveal its panel. That's done by selecting distributed nodes that have the <code translate="no" dir="ltr">selected</code> attribute. The custom element's JS (not shown here) adds that attribute at the correct time.</p> <h3 id="styling_a_component_from_the_outside" data-text="Styling a component from the outside" tabindex="-1">Styling a component from the outside</h3> <p>There are a couple of ways to style a component from the outside. The easiest way is to use the tag name as a selector:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="CSS"><code translate="no" dir="ltr"><span class="devsite-syntax-nt">fancy-tabs</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">width</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mi">500</span><span class="devsite-syntax-kt">px</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">color</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-kc">red</span><span class="devsite-syntax-p">;</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-c">/* Note: inheritable CSS properties pierce the shadow DOM boundary. */</span> <span class="devsite-syntax-p">}</span> <span class="devsite-syntax-nt">fancy-tabs</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-nd">hover</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">box-shadow</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mi">0</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mi">3</span><span class="devsite-syntax-kt">px</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mi">3</span><span class="devsite-syntax-kt">px</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mh">#ccc</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <p><strong>Outside styles always win over styles defined in shadow DOM</strong>. For example, if the user writes the selector <code translate="no" dir="ltr">fancy-tabs { width: 500px; }</code>, it will trump the component's rule: <code translate="no" dir="ltr">:host { width: 650px;}</code>.</p> <p>Styling the component itself will only get you so far. But what happens if you want to style the internals of a component? For that, we need CSS custom properties.</p> <h4 id="creating_style_hooks_using_css_custom_properties" data-text="Creating style hooks using CSS custom properties" tabindex="-1">Creating style hooks using CSS custom properties</h4> <p>Users can tweak internal styles if the component's author provides styling hooks using <a href="https://developer.mozilla.org/docs/Web/CSS/Using_CSS_variables">CSS custom properties</a>. Conceptually, the idea is similar to <code translate="no" dir="ltr"><slot></code>. You create "style placeholders" for users to override.</p> <p><strong>Example</strong> - <code translate="no" dir="ltr"><fancy-tabs></code> allows users to override the background color:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><!-- main page --> <style> fancy-tabs { margin-bottom: 32px; --fancy-tabs-bg: black; } </style> <fancy-tabs background>...</fancy-tabs> </code></pre></devsite-code> <p>Inside its shadow DOM:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="CSS"><code translate="no" dir="ltr"><span class="devsite-syntax-p">:</span><span class="devsite-syntax-nd">host</span><span class="devsite-syntax-o">([</span><span class="devsite-syntax-nt">background</span><span class="devsite-syntax-o">])</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">background</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nf">var</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nv">--fancy-tabs-bg</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mh">#9E9E9E</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">border-radius</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mi">10</span><span class="devsite-syntax-kt">px</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">padding</span><span class="devsite-syntax-p">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mi">10</span><span class="devsite-syntax-kt">px</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <p>In this case, the component will use <code translate="no" dir="ltr">black</code> as the background value since the user provided it. Otherwise, it would default to <code translate="no" dir="ltr">#9E9E9E</code>.</p> <aside class="note"><strong>Note:</strong><span> As the component author, you're responsible for letting developers know about CSS custom properties they can use. Consider it part of your component's public interface. Make sure to document styling hooks!</span></aside> <h2 id="advanced_topics" data-text="Advanced topics" tabindex="-1">Advanced topics</h2> <h3 id="creating_closed_shadow_roots_should_avoid" data-text="Creating closed shadow roots (should avoid)" tabindex="-1">Creating closed shadow roots (should avoid)</h3> <p>There's another flavor of shadow DOM called "closed" mode. When you create a closed shadow tree, outside JavaScript won't be able to access the internal DOM of your component. This is similar to how native elements like <code translate="no" dir="ltr"><video></code> work. JavaScript cannot access the shadow DOM of <code translate="no" dir="ltr"><video></code> because the browser implements it using a closed-mode shadow root.</p> <p><strong>Example</strong> - creating a closed shadow tree:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">div</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">createElement</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'div'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">div</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">attachShadow</span><span class="devsite-syntax-p">({</span><span class="devsite-syntax-nx">mode</span><span class="devsite-syntax-o">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s1">'closed'</span><span class="devsite-syntax-p">});</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// close shadow tree</span> <span class="devsite-syntax-c1">// div.shadowRoot === null</span> <span class="devsite-syntax-c1">// shadowRoot.host === div</span> </code></pre></devsite-code> <p>Other APIs are also affected by closed-mode:</p> <ul> <li><code translate="no" dir="ltr">Element.assignedSlot</code> / <code translate="no" dir="ltr">TextNode.assignedSlot</code> returns <code translate="no" dir="ltr">null</code></li> <li><code translate="no" dir="ltr">Event.composedPath()</code> for events associated with elements inside the shadow DOM, returns []</li> </ul> <aside class="note"><b>Note: </b> Closed shadow roots are not very useful. Some developers will see closed mode as an artificial security feature. But let's be clear, it's <strong>not</strong> a security feature. Closed mode simply prevents outside JS from drilling into an element's internal DOM. </aside> <p>Here's my summary of why you should never create web components with <code translate="no" dir="ltr">{mode: 'closed'}</code>:</p> <ol> <li><p>Artificial sense of security. There's nothing stopping an attacker from hijacking <code translate="no" dir="ltr">Element.prototype.attachShadow</code>.</p></li> <li><p>Closed mode <strong>prevents your custom element code from accessing its own shadow DOM</strong>. That's complete fail. Instead, you'll have to stash a reference for later if you want to use things like <code translate="no" dir="ltr">querySelector()</code>. This completely defeats the original purpose of closed mode!</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">customElements</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">define</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'x-element'</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-kd">class</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">extends</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">HTMLElement</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-kr">constructor</span><span class="devsite-syntax-p">()</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">super</span><span class="devsite-syntax-p">();</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// always call super() first in the constructor.</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">this</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">_shadowRoot</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">this</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">attachShadow</span><span class="devsite-syntax-p">({</span><span class="devsite-syntax-nx">mode</span><span class="devsite-syntax-o">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s1">'closed'</span><span class="devsite-syntax-p">});</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">this</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">_shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">innerHTML</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-s1">'<div class="wrapper"></div>'</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">connectedCallback</span><span class="devsite-syntax-p">()</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// When creating closed shadow trees, you'll need to stash the shadow root</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// for later if you want to use it again. Kinda pointless.</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">wrapper</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">this</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">_shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">querySelector</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'.wrapper'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">...</span> <span class="devsite-syntax-p">});</span> </code></pre></devsite-code></li> <li><p><strong>Closed mode makes your component less flexible for end users</strong>. As you build web components, there will come a time when you forget to add a feature. A configuration option. A use case the user wants. A common example is forgetting to include adequate styling hooks for internal nodes. With closed mode, there's no way for users to override defaults and tweak styles. Being able to access the component's internals is super helpful. Ultimately, users will fork your component, find another, or create their own if it doesn't do what they want :(</p></li> </ol> <h3 id="working_with_slots_in_js" data-text="Working with slots in JS" tabindex="-1">Working with slots in JS</h3> <p>The shadow DOM API provides utilities for working with slots and distributed nodes. These come in handy when authoring a custom element.</p> <h4 id="slotchange_event" data-text="slotchange event" tabindex="-1">slotchange event</h4> <p>The <code translate="no" dir="ltr">slotchange</code> event fires when a slot's distributed nodes changes. For example, if the user adds/removes children from the light DOM.</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">slot</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">this</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">querySelector</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'#slot'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-nx">slot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">addEventListener</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'slotchange'</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">e</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span>><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">console</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">log</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'light dom children changed!'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-p">});</span> </code></pre></devsite-code> <aside class="note"><b>Note: </b> <code translate="no" dir="ltr">slotchange</code> does not fire when an instance of the component is first initialized. </aside> <p>To monitor other types of changes to light DOM, you can setup a <a href="https://developer.mozilla.org/docs/Web/API/MutationObserver"><code translate="no" dir="ltr">MutationObserver</code></a> in your element's constructor.</p> <h4 id="what_elements_are_being_rendering_in_a_slot" data-text="What elements are being rendering in a slot?" tabindex="-1">What elements are being rendering in a slot?</h4> <p>Sometimes it's useful to know what elements are associated with a slot. Call <code translate="no" dir="ltr">slot.assignedNodes()</code> to find which elements the slot is rendering. The <code translate="no" dir="ltr">{flatten: true}</code> option will also return a slot's fallback content (if no nodes are being distributed).</p> <p>As an example, let's say your shadow DOM looks like this:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><slot><b>fallback content</b></slot> </code></pre></devsite-code> <table> <thead><th>Usage</th><th>Call</th><th>Result</th></thead> <tr> <td><my-component>component text</my-component></td> <td><code translate="no" dir="ltr">slot.assignedNodes();</code></td> <td><code translate="no" dir="ltr">[component text]</code></td> </tr> <tr> <td><my-component></my-component></td> <td><code translate="no" dir="ltr">slot.assignedNodes();</code></td> <td><code translate="no" dir="ltr">[]</code></td> </tr> <tr> <td><my-component></my-component></td> <td><code translate="no" dir="ltr">slot.assignedNodes({flatten: true});</code></td> <td><code translate="no" dir="ltr">[<b>fallback content</b>]</code></td> </tr> </table> <h4 id="what_slot_is_an_element_assigned_to" data-text="What slot is an element assigned to?" tabindex="-1">What slot is an element assigned to?</h4> <p>Answering the reverse question is also possible. <code translate="no" dir="ltr">element.assignedSlot</code> tells you which of the component slots your element is assigned to.</p> <h3 id="the_shadow_dom_event_model" data-text="The Shadow DOM event model" tabindex="-1">The Shadow DOM event model</h3> <p>When an event bubbles up from shadow DOM it's target is adjusted to maintain the encapsulation that shadow DOM provides. That is, events are re-targeted to look like they've come from the component rather than internal elements within your shadow DOM. Some events do not even propagate out of shadow DOM.</p> <p>The events that <strong>do</strong> cross the shadow boundary are:</p> <ul> <li>Focus Events: <code translate="no" dir="ltr">blur</code>, <code translate="no" dir="ltr">focus</code>, <code translate="no" dir="ltr">focusin</code>, <code translate="no" dir="ltr">focusout</code></li> <li>Mouse Events: <code translate="no" dir="ltr">click</code>, <code translate="no" dir="ltr">dblclick</code>, <code translate="no" dir="ltr">mousedown</code>, <code translate="no" dir="ltr">mouseenter</code>, <code translate="no" dir="ltr">mousemove</code>, etc.</li> <li>Wheel Events: <code translate="no" dir="ltr">wheel</code></li> <li>Input Events: <code translate="no" dir="ltr">beforeinput</code>, <code translate="no" dir="ltr">input</code></li> <li>Keyboard Events: <code translate="no" dir="ltr">keydown</code>, <code translate="no" dir="ltr">keyup</code></li> <li>Composition Events: <code translate="no" dir="ltr">compositionstart</code>, <code translate="no" dir="ltr">compositionupdate</code>, <code translate="no" dir="ltr">compositionend</code></li> <li>DragEvent: <code translate="no" dir="ltr">dragstart</code>, <code translate="no" dir="ltr">drag</code>, <code translate="no" dir="ltr">dragend</code>, <code translate="no" dir="ltr">drop</code>, etc.</li> </ul> <p><strong>Tips</strong></p> <p>If the shadow tree is open, calling <code translate="no" dir="ltr">event.composedPath()</code> will return an array of nodes that the event traveled through.</p> <h4 id="using_custom_events" data-text="Using custom events" tabindex="-1">Using custom events</h4> <p>Custom DOM events which are fired on internal nodes in a shadow tree do not bubble out of the shadow boundary unless the event is created using the <code translate="no" dir="ltr">composed: true</code> flag:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-c1">// Inside <fancy-tab> custom element class definition:</span> <span class="devsite-syntax-nx">selectTab</span><span class="devsite-syntax-p">()</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">tabs</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">this</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">querySelector</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'#tabs'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">tabs</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">dispatchEvent</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-ow">new</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">Event</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'tab-select'</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span><span class="devsite-syntax-nx">bubbles</span><span class="devsite-syntax-o">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-kc">true</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">composed</span><span class="devsite-syntax-o">:</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-kc">true</span><span class="devsite-syntax-p">}));</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <p>If <code translate="no" dir="ltr">composed: false</code> (default), consumers won't be able to listen for the event outside of your shadow root.</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><fancy-tabs></fancy-tabs> <script> const tabs = document.querySelector('fancy-tabs'); tabs.addEventListener('tab-select', e => { // won't fire if `tab-select` wasn't created with `composed: true`. }); </script> </code></pre></devsite-code> <h3 id="handling_focus" data-text="Handling focus" tabindex="-1">Handling focus</h3> <p>If you recall from <a href="#the_shadow_dom_event_model">shadow DOM's event model</a>, events that are fired inside shadow DOM are adjusted to look like they come from the hosting element. For example, let's say you click an <code translate="no" dir="ltr"><input></code> inside a shadow root:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><x-focus> #shadow-root <input type="text" placeholder="Input inside shadow dom"> </code></pre></devsite-code> <p>The <code translate="no" dir="ltr">focus</code> event will look like it came from <code translate="no" dir="ltr"><x-focus></code>, not the <code translate="no" dir="ltr"><input></code>. Similarly, <code translate="no" dir="ltr">document.activeElement</code> will be <code translate="no" dir="ltr"><x-focus></code>. If the shadow root was created with <code translate="no" dir="ltr">mode:'open'</code> (see <a href="#creating_closed_shadow_roots_should_avoid">closed mode</a>), you'll also be able access the internal node that gained focus:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">activeElement</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">activeElement</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// only works with open mode.</span> </code></pre></devsite-code> <p>If there are multiple levels of shadow DOM at play (say a custom element within another custom element), you need to recursively drill into the shadow roots to find the <code translate="no" dir="ltr">activeElement</code>:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-kd">function</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">deepActiveElement</span><span class="devsite-syntax-p">()</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-kd">let</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">a</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">activeElement</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">while</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">a</span><span class="devsite-syntax-w"> && </span><span class="devsite-syntax-nx">a</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-w"> && </span><span class="devsite-syntax-nx">a</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">activeElement</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">a</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">a</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">activeElement</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">return</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">a</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <p>Another option for focus is the <code translate="no" dir="ltr">delegatesFocus: true</code> option, which expands the focus behavior of element's within a shadow tree:</p> <ul> <li>If you click a node inside shadow DOM and the node is not a focusable area, the first focusable area becomes focused.</li> <li>When a node inside shadow DOM gains focus, <code translate="no" dir="ltr">:focus</code> applies to the host in addition to the focused element.</li> </ul> <p><strong>Example</strong> - how <code translate="no" dir="ltr">delegatesFocus: true</code> changes focus behavior</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><style> :focus { outline: 2px solid red; } </style> <x-focus></x-focus> <script> customElements.define('x-focus', class extends HTMLElement { constructor() { super(); // always call super() first in the constructor. const root = this.attachShadow({mode: 'open', delegatesFocus: true}); root.innerHTML = ` <style> :host { display: flex; border: 1px dotted black; padding: 16px; } :focus { outline: 2px solid blue; } </style> <div>Clickable Shadow DOM text</div> <input type="text" placeholder="Input inside shadow dom">`; // Know the focused element inside shadow DOM: this.addEventListener('focus', function(e) { console.log('Active element (inside shadow dom):', this.shadowRoot.activeElement); }); } }); </script> </code></pre></devsite-code> <p><strong>Result</strong></p> <figure> <img src="/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab.png" alt="delegatesFocus: true behavior." width="800" height="102" srcset="https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_36.png 36w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_48.png 48w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_72.png 72w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_96.png 96w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_480.png 480w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_720.png 720w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_856.png 856w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_960.png 960w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_1440.png 1440w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_1920.png 1920w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-true-beha-2829e235044ab_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"> </figure> <p>Above is the result when <code translate="no" dir="ltr"><x-focus></code> is focused (user click, tabbed into, <code translate="no" dir="ltr">focus()</code>, etc.), "Clickable Shadow DOM text" is clicked, or the internal <code translate="no" dir="ltr"><input></code> is focused (including <code translate="no" dir="ltr">autofocus</code>).</p> <p>If you were to set <code translate="no" dir="ltr">delegatesFocus: false</code>, here's what you would see instead:</p> <figure> <img src="/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9.png" alt="delegatesFocus: false and the internal input is focused." width="800" height="97" srcset="https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_36.png 36w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_48.png 48w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_72.png 72w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_96.png 96w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_480.png 480w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_720.png 720w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_856.png 856w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_960.png 960w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_1440.png 1440w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_1920.png 1920w,https://web.dev/static/articles/shadowdom-v1/image/delegatesfocus-false-th-a0624bcb139e9_2880.png 2880w" sizes="(max-width: 840px) 100vw, 856px"> <figcaption class="wd-caption"> <code translate="no" dir="ltr">delegatesFocus: false</code> and the internal <code translate="no" dir="ltr"><input></code> is focused. </figcaption> </figure> <figure> <img src="/static/articles/shadowdom-v1/image/delegatesfocus-false-x-ad7729291b314.png" alt="delegatesFocus: false and x-focus gains focus (e.g. it has tabindex='0')." width="800" height="98"> <figcaption class="wd-caption"> <code translate="no" dir="ltr">delegatesFocus: false</code> and <code translate="no" dir="ltr"><x-focus></code> gains focus (e.g. it has <code translate="no" dir="ltr">tabindex="0"</code>). </figcaption> </figure> <figure> <img src="/static/articles/shadowdom-v1/image/delegatesfocus-false-c-29c711da9b14f.png" alt="delegatesFocus: false and 'Clickable Shadow DOM text' is clicked (or other empty area within the element's shadow DOM is clicked)." width="800" height="91"> <figcaption class="wd-caption"> <code translate="no" dir="ltr">delegatesFocus: false</code> and "Clickable Shadow DOM text" is clicked (or other empty area within the element's shadow DOM is clicked). </figcaption> </figure> <h2 id="tips_tricks" data-text="Tips & Tricks" tabindex="-1">Tips & Tricks</h2> <p>Over the years I've learned a thing or two about authoring web components. I think you'll find some of these tips useful for authoring components and debugging shadow DOM.</p> <h3 id="use_css_containment" data-text="Use CSS containment" tabindex="-1">Use CSS containment</h3> <p>Typically, a web component's layout/style/paint is fairly self-contained. Use <a href="https://developer.chrome.com/blog/css-containment">CSS containment</a> in <code translate="no" dir="ltr">:host</code> for a perf win:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><style> :host { display: block; contain: content; /* Boom. CSS containment FTW. */ } </style> </code></pre></devsite-code> <h3 id="resetting_inheritable_styles" data-text="Resetting inheritable styles" tabindex="-1">Resetting inheritable styles</h3> <p>Inheritable styles (<code translate="no" dir="ltr">background</code>, <code translate="no" dir="ltr">color</code>, <code translate="no" dir="ltr">font</code>, <code translate="no" dir="ltr">line-height</code>, etc.) continue to inherit in shadow DOM. That is, they pierce the shadow DOM boundary by default. If you want to start with a fresh slate, use <code translate="no" dir="ltr">all: initial;</code> to reset inheritable styles to their initial value when they cross the shadow boundary.</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="HTML"><code translate="no" dir="ltr"><style> div { padding: 10px; background: red; font-size: 25px; text-transform: uppercase; color: white; } </style> <div> <p>I'm outside the element (big/white)</p> <my-element>Light DOM content is also affected.</my-element> <p>I'm outside the element (big/white)</p> </div> <script> const el = document.querySelector('my-element'); el.attachShadow({mode: 'open'}).innerHTML = ` <style> :host { all: initial; /* 1st rule so subsequent properties are reset. */ display: block; background: white; } </style> <p>my-element: all CSS properties are reset to their initial value using <code>all: initial</code>.</p> <slot></slot> `; </script> </code></pre></devsite-code> <h3 id="finding_all_the_custom_elements_used_by_a_page" data-text="Finding all the custom elements used by a page" tabindex="-1">Finding all the custom elements used by a page</h3> <p>Sometimes it's useful to find custom elements used on the page. To do so, you need to recursively traverse the shadow DOM of all elements used on the page.</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">allCustomElements</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">[];</span> <span class="devsite-syntax-kd">function</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">isCustomElement</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">el</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">isAttr</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">el</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">getAttribute</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'is'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// Check for <super-button> and <button is="super-button">.</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">return</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">el</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">localName</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">includes</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'-'</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">||</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">isAttr</span><span class="devsite-syntax-w"> && </span><span class="devsite-syntax-nx">isAttr</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">includes</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'-'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-p">}</span> <span class="devsite-syntax-kd">function</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">findAllCustomElements</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">nodes</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">for</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-kd">let</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">i</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-mf">0</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">el</span><span class="devsite-syntax-p">;</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">el</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">nodes</span><span class="devsite-syntax-p">[</span><span class="devsite-syntax-nx">i</span><span class="devsite-syntax-p">];</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">++</span><span class="devsite-syntax-nx">i</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">if</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">isCustomElement</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">el</span><span class="devsite-syntax-p">))</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">allCustomElements</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">push</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">el</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// If the element has shadow DOM, dig deeper.</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">if</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">el</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">findAllCustomElements</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">el</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">shadowRoot</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">querySelectorAll</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'*'</span><span class="devsite-syntax-p">));</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">}</span> <span class="devsite-syntax-p">}</span> <span class="devsite-syntax-nx">findAllCustomElements</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">querySelectorAll</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'*'</span><span class="devsite-syntax-p">));</span> </code></pre></devsite-code> <h3 id="creating_elements_from_a_template" data-text="Creating elements from a <template>" tabindex="-1">Creating elements from a <template></h3> <p>Instead of populating a shadow root using <code translate="no" dir="ltr">.innerHTML</code>, we can use a declarative <code translate="no" dir="ltr"><template></code>. Templates are an ideal placeholder for declaring the structure of a web component.</p> <p>See the example in <a href="/articles/custom-elements-v1">"Custom elements: building reusable web components"</a>.</p> <h2 id="history_browser_support" data-text="History & browser support" tabindex="-1">History & browser support</h2> <p>If you've been following web components for the last couple of years, you'll know that Chrome 35+/Opera have been shipping an older version of shadow DOM for some time. Blink will continue to support both versions in parallel for some time. The v0 spec provided a different method to create a shadow root (<code translate="no" dir="ltr">element.createShadowRoot</code> instead of v1's <code translate="no" dir="ltr">element.attachShadow</code>). Calling the older method continues to create a shadow root with v0 semantics, so existing v0 code won't break.</p> <p>If you happen to be interested in the old v0 spec, check out the html5rocks articles: <a href="https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/">1</a>, <a href="https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/">2</a>, <a href="https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/">3</a>. There's also a great comparison of the <a href="https://hayatoito.github.io/2016/shadowdomv1/">differences between shadow DOM v0 and v1</a>.</p> <h3 id="browser_support" data-text="Browser support" tabindex="-1">Browser support</h3> <p>Shadow DOM v1 is shipped in Chrome 53 (<a href="https://www.chromestatus.com/features/4667415417847808">status</a>), Opera 40, Safari 10, and Firefox 63. Edge <a href="https://developer.microsoft.com/microsoft-edge/platform/status/shadowdom/">has started development</a>.</p> <p>To feature detect shadow DOM, check for the existence of <code translate="no" dir="ltr">attachShadow</code>:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">supportsShadowDOMV1</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">!!</span><span class="devsite-syntax-nx">HTMLElement</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">prototype</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">attachShadow</span><span class="devsite-syntax-p">;</span> </code></pre></devsite-code> <h4 id="polyfill" data-text="Polyfill" tabindex="-1">Polyfill</h4> <p>Until browser support is widely available, the <a href="https://github.com/webcomponents/shadydom">shadydom</a> and <a href="https://github.com/webcomponents/shadycss">shadycss</a> polyfills give you v1 feature. Shady DOM mimics the DOM scoping of Shadow DOM and shadycss polyfills CSS custom properties and the style scoping the native API provides.</p> <p>Install the polyfills:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="Bash"><code translate="no" dir="ltr">bower<span class="devsite-syntax-w"> </span>install<span class="devsite-syntax-w"> </span>--save<span class="devsite-syntax-w"> </span>webcomponents/shadydom bower<span class="devsite-syntax-w"> </span>install<span class="devsite-syntax-w"> </span>--save<span class="devsite-syntax-w"> </span>webcomponents/shadycss </code></pre></devsite-code> <p>Use the polyfills:</p> <div></div><devsite-code><pre class="devsite-click-to-copy" translate="no" dir="ltr" is-upgraded syntax="JavaScript"><code translate="no" dir="ltr"><span class="devsite-syntax-kd">function</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">loadScript</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">src</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">return</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-ow">new</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">Promise</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-kd">function</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">resolve</span><span class="devsite-syntax-p">,</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">reject</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-kd">const</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">script</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">createElement</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'script'</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">script</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-k">async</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-kc">true</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">script</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">src</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">src</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">script</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">onload</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">resolve</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">script</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">onerror</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">reject</span><span class="devsite-syntax-p">;</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nb">document</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">head</span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">appendChild</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">script</span><span class="devsite-syntax-p">);</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">});</span> <span class="devsite-syntax-p">}</span> <span class="devsite-syntax-c1">// Lazy load the polyfill if necessary.</span> <span class="devsite-syntax-k">if</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-o">!</span><span class="devsite-syntax-nx">supportsShadowDOMV1</span><span class="devsite-syntax-p">)</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">loadScript</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'/bower_components/shadydom/shadydom.min.js'</span><span class="devsite-syntax-p">)</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">then</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">e</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span>><span class="devsite-syntax-w"> </span><span class="devsite-syntax-nx">loadScript</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-s1">'/bower_components/shadycss/shadycss.min.js'</span><span class="devsite-syntax-p">))</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">.</span><span class="devsite-syntax-nx">then</span><span class="devsite-syntax-p">(</span><span class="devsite-syntax-nx">e</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-o">=</span>><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// Polyfills loaded.</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">});</span> <span class="devsite-syntax-p">}</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-k">else</span><span class="devsite-syntax-w"> </span><span class="devsite-syntax-p">{</span> <span class="devsite-syntax-w"> </span><span class="devsite-syntax-c1">// Native shadow dom v1 support. Go to go!</span> <span class="devsite-syntax-p">}</span> </code></pre></devsite-code> <p>See the <a href="https://github.com/webcomponents/shadycss">https://github.com/webcomponents/shadycss#usage</a> for instructions on how to shim/scope your styles.</p> <h2 id="conclusion" data-text="Conclusion" tabindex="-1">Conclusion</h2> <p>For the first time ever, we have an API primitive that does proper CSS scoping, DOM scoping, and has true composition. Combined with other web component APIs like custom elements, shadow DOM provides a way to author truly encapsulated components without hacks or using older baggage like <code translate="no" dir="ltr"><iframe></code>s.</p> <p>Don't get me wrong. Shadow DOM is certainly a complex beast! But it's a beast worth learning. Spend some time with it. Learn it and ask questions!</p> <h3 id="further_reading" data-text="Further reading" tabindex="-1">Further reading</h3> <ul> <li><a href="https://hayatoito.github.io/2016/shadowdomv1/">Differences between Shadow DOM v1 and v0</a></li> <li><a href="https://webkit.org/blog/4096/introducing-shadow-dom-api/">"Introducing Slot-Based Shadow DOM API"</a> from the WebKit Blog.</li> <li><a href="https://philipwalton.github.io/talks/2015-10-26/">Web Components and the future of Modular CSS</a> by <a href="https://twitter.com/@philwalton">Philip Walton</a></li> <li><a href="/articles/custom-elements-v1">"Custom elements: building reusable web components"</a> from Google's WebFundamentals.</li> <li><a href="https://dom.spec.whatwg.org/#shadow-trees">Shadow DOM v1 spec</a></li> <li><a href="https://html.spec.whatwg.org/multipage/scripting.html#custom-elements">Custom elements v1 spec</a></li> </ul> <h2 id="faq" data-text="FAQ" tabindex="-1">FAQ</h2> <p><strong>Can I use Shadow DOM v1 today?</strong></p> <p>With a polyfill, yes. See <a href="#history_browser_support">Browser support</a>.</p> <p><strong>What security features does shadow DOM provide?</strong></p> <p>Shadow DOM is not a security feature. It's a lightweight tool for scoping CSS and hiding away DOM trees in component. If you want a true security boundary, use an <code translate="no" dir="ltr"><iframe></code>.</p> <p><strong>Does a web component have to use shadow DOM?</strong></p> <p>Nope! You don't have to create web components that use shadow DOM. However, authoring <a href="#creating_shadow_dom_for_a_custom_element">custom elements that use Shadow DOM</a> means you can take advantage of features like CSS scoping, DOM encapsulation, and composition.</p> <p><strong>What's the difference between open and closed shadow roots?</strong></p> <p>See <a href="#creating_closed_shadow_roots_should_avoid">Closed shadow roots</a>.</p> </div> <devsite-thumb-rating position="footer"> </devsite-thumb-rating> <div class="devsite-floating-action-buttons"> </div> </article> <devsite-content-footer class="nocontent"> <p>Except as otherwise noted, the content of this page is licensed under the <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 License</a>, and code samples are licensed under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 License</a>. For details, see the <a href="https://developers.google.com/site-policies">Google Developers Site Policies</a>. Java is a registered trademark of Oracle and/or its affiliates.</p> <p>Last updated 2016-08-01 UTC.</p> </devsite-content-footer> <devsite-notification > </devsite-notification> <div class="devsite-content-data"> <template class="devsite-content-data-template"> [[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2016-08-01 UTC."],[],[]] </template> </div> </devsite-content> </main> <devsite-footer-promos class="devsite-footer"> </devsite-footer-promos> <devsite-footer-linkboxes class="devsite-footer"> <nav class="devsite-footer-linkboxes nocontent" aria-label="Footer links"> <ul class="devsite-footer-linkboxes-list"> <li class="devsite-footer-linkbox wd-footer-promo"> <h3 class="devsite-footer-linkbox-heading no-link">web.dev</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <h3 class="devsite-footer-linkbox-heading no-link"> web.dev </h3> <div class="devsite-footer-linkbox-description">We want to help you build beautiful, accessible, fast, and secure websites that work cross-browser, and for all of your users. This site is our home for content to help you on that journey, written by members of the Chrome team, and external experts.</div> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Contribute</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="https://issuetracker.google.com/issues/new?component=1400680&template=1857359" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > File a bug </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://issuetracker.google.com/issues?q=status:open%20componentid:1400680&s=created_time:desc" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > See open issues </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Related Content</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="https://developer.chrome.com/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > Chrome for Developers </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://blog.chromium.org/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > Chromium updates </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/case-studies" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Case studies </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/shows" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > Podcasts & shows </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Follow</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="https://twitter.com/ChromiumDev" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > @ChromiumDev on X </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://www.youtube.com/user/ChromeDevelopers" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > YouTube </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://www.linkedin.com/showcase/chrome-for-developers" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Chrome for Developers on LinkedIn </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/static/blog/feed.xml" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > RSS </a> </li> </ul> </li> </ul> </nav> </devsite-footer-linkboxes> <devsite-footer-utility class="devsite-footer"> <div class="devsite-footer-utility nocontent"> <nav class="devsite-footer-utility-links" aria-label="Utility links"> <ul class="devsite-footer-utility-list"> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="//policies.google.com/terms" data-category="Site-Wide Custom Events" data-label="Footer Terms link" > Terms </a> </li> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="//policies.google.com/privacy" data-category="Site-Wide Custom Events" data-label="Footer Privacy link" > Privacy </a> </li> <li class="devsite-footer-utility-item glue-cookie-notification-bar-control"> <a class="devsite-footer-utility-link gc-analytics-event" href="#" data-category="Site-Wide Custom Events" data-label="Footer Manage cookies link" aria-hidden="true" > Manage cookies </a> </li> </ul> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> </nav> </div> </devsite-footer-utility> <devsite-panel></devsite-panel> </section></section> <devsite-sitemask></devsite-sitemask> <devsite-snackbar></devsite-snackbar> <devsite-tooltip ></devsite-tooltip> <devsite-heading-link></devsite-heading-link> <devsite-analytics> <script type="application/json" analytics>[]</script> <script type="application/json" tag-management>{"at": "True", "ga4": [], "ga4p": [], "gtm": [{"id": "GTM-MZWCJPP", "purpose": 0}], "parameters": {"internalUser": "False", "language": {"machineTranslated": "False", "requested": "en", "served": "en"}, "pageType": "article", "projectName": "Articles", "signedIn": "False", "tenant": "web", "recommendations": {"sourcePage": "", "sourceType": 0, "sourceRank": 0, "sourceIdenticalDescriptions": 0, "sourceTitleWords": 0, "sourceDescriptionWords": 0, "experiment": ""}, "experiment": {"ids": ""}}}</script> </devsite-analytics> <devsite-badger></devsite-badger> <script nonce="FrjPkBbCUrGeFq6V11Lt2CEo76stgM"> (function(d,e,v,s,i,t,E){d['GoogleDevelopersObject']=i; t=e.createElement(v);t.async=1;t.src=s;E=e.getElementsByTagName(v)[0]; E.parentNode.insertBefore(t,E);})(window, document, 'script', 'https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/js/app_loader.js', '[27,"en",null,"/js/devsite_app_module.js","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web","https://web-dot-devsite-v2-prod-3p.appspot.com",1,null,["/_pwa/web/manifest.json","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/images/video-placeholder.svg","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/favicon.png","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup.svg","https://fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"],1,null,[1,6,8,12,14,17,21,25,50,52,63,70,75,76,80,87,91,92,93,97,98,100,101,102,103,104,105,107,108,109,110,112,113,117,118,120,122,124,125,126,127,129,130,131,132,133,134,135,136,138,140,141,147,148,149,151,152,156,157,158,159,161,163,164,168,169,170,179,180,182,183,186,191,193,196],"AIzaSyCNm9YxQumEXwGJgTDjxoxXK6m1F-9720Q","AIzaSyCc76DZePGtoyUjqKrLdsMGk_ry7sljLbY","web.dev","AIzaSyB9bqgQ2t11WJsOX8qNsCQ6U-w91mmqF-I","AIzaSyAdYnStPdzjcJJtQ0mvIaeaMKj7_t6J_Fg",null,null,null,["TpcFeatures__enable_mirror_tenant_redirects","Cloud__enable_llm_concierge_chat","TpcFeatures__enable_required_headers","Search__enable_ai_eligibility_checks","CloudShell__cloud_shell_button","Cloud__enable_cloud_shell_fte_user_flow","MiscFeatureFlags__enable_view_transitions","Profiles__enable_awarding_url","Search__enable_dynamic_content_confidential_banner","Profiles__enable_recognition_badges","Concierge__enable_pushui","DevPro__enable_developer_subscriptions","Experiments__reqs_query_experiments","MiscFeatureFlags__enable_firebase_utm","Search__enable_suggestions_from_borg","Profiles__enable_release_notes_notifications","Cloud__enable_cloud_dlp_service","Profiles__enable_complete_playlist_endpoint","Cloud__enable_cloudx_ping","MiscFeatureFlags__enable_project_variables","DevPro__enable_cloud_innovators_plus","OnSwitch__enable","MiscFeatureFlags__enable_variable_operator","BookNav__enable_tenant_cache_key","Cloud__enable_legacy_calculator_redirect","Profiles__enable_page_saving","Cloud__enable_cloud_facet_chat","MiscFeatureFlags__developers_footer_image","MiscFeatureFlags__developers_footer_dark_image","Cloud__enable_cloudx_experiment_ids","Cloud__enable_cloud_shell","EngEduTelemetry__enable_engedu_telemetry","Profiles__require_profile_eligibility_for_signin","Search__enable_page_map","Profiles__enable_public_developer_profiles","Profiles__enable_dashboard_curated_recommendations","CloudShell__cloud_code_overflow_menu","MiscFeatureFlags__emergency_css","Cloud__enable_free_trial_server_call","Profiles__enable_developer_profiles_callout","MiscFeatureFlags__enable_explain_this_code","Profiles__enable_completecodelab_endpoint","Analytics__enable_clearcut_logging","Profiles__enable_profile_collections"],null,null,"AIzaSyA58TaKli1DculwmAmbpzLVGuWc8eCQgQc","https://developerscontentserving-pa.googleapis.com","AIzaSyDWBU60w0P9hEkr29kkksYs8Z7gvZ8u_wc","https://developerscontentsearch-pa.googleapis.com",2,4,null,"https://developerprofiles-pa.googleapis.com",[27,"web","web.dev","web.dev",null,"web-dot-devsite-v2-prod-3p.appspot.com",null,null,[null,null,null,null,null,null,null,null,null,null,null,[1],null,null,null,null,null,null,[1],null,null,null,null,[1,null,1],[1,1,null,1,1]],null,[38,null,null,null,null,null,"/images/lockup.svg","/images/touchicon-180.png",null,null,null,1,1,null,null,null,null,null,null,null,null,2,null,null,null,"/images/lockup-dark-theme.svg",[]],[],null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,[[],[1,1]],[[null,null,null,null,null,["GTM-MZWCJPP"],null,null,null,null,null,[["GTM-MZWCJPP",1]],1]],null,4]]') </script> <devsite-a11y-announce></devsite-a11y-announce> </body> </html>