Shadow DOM
Learn how to use Shadow DOM in Aurelia components for style encapsulation and native web component features.
Shadow DOM provides native browser encapsulation for your components, isolating styles and DOM structure. Aurelia makes it easy to enable Shadow DOM for any custom element.
Quick decision guide
Start with Light DOM unless you have a specific reason to enable Shadow DOM. Shadow DOM isolates styles by design, so global CSS frameworks and app-wide themes do not flow into components unless you explicitly share them. You can mix Light DOM and Shadow DOM in the same app—use Shadow DOM only on components that need strict isolation or native web component features.
Enabling Shadow DOM
Using the @useShadowDOM Decorator
The simplest way to enable Shadow DOM is with the @useShadowDOM decorator:
import { customElement, useShadowDOM } from 'aurelia';
@customElement('my-card')
@useShadowDOM()
export class MyCard {
message = 'Hello from Shadow DOM';
}By default, this creates a shadow root with mode: 'open'.
Configuring Shadow DOM Mode
Shadow DOM supports two modes: open and closed.
Open mode (default) allows external JavaScript to access the shadow root:
Closed mode prevents external access to the shadow root:
Mode only controls JavaScript access to the shadow root. It does not change CSS encapsulation—global styles still will not cross the shadow boundary in open mode.
Using the Configuration Object
You can also configure Shadow DOM using the @customElement decorator's configuration object:
Or using a static property:
Styling Shadow DOM Components
Shadow DOM provides complete CSS isolation. Styles defined outside the component won't affect elements inside, and styles inside won't leak out.
Troubleshooting checklist
If styles or slots are not behaving as expected, check these first:
Confirm the component actually uses Shadow DOM:
@useShadowDOM()orshadowOptionsmust be set.Global CSS won’t cross the boundary: Use Light DOM for framework styles, or register shared styles with
StyleConfiguration.shadowDOM({ sharedStyles: [...] }).Co-located CSS is not auto-injected: Import CSS as a string and pass it to
shadowCSS()for Shadow DOM components.Use Shadow DOM selectors:
:host,:host-context(), and::slotted()apply inside the shadow root. Use CSS variables or::partfor safe theming.Slots require Shadow DOM: Native
<slot>only works with Shadow DOM; use<au-slot>if you stay in Light DOM.Containerless is incompatible: You cannot use Shadow DOM and
@containerlesstogether.Debugging needs open mode:
mode: 'open'makes it easier to inspect and tweak styles in DevTools.
Component-Local Styles
Use the shadowCSS helper to register styles for your component:
Using Constructable Stylesheets
For better performance and reusability, you can pass CSSStyleSheet instances:
Global Shared Styles
Configure styles that apply to all Shadow DOM components in your application:
Global styles are applied first, followed by component-local styles.
Shared styles only apply to components that actually use Shadow DOM. They do not affect Light DOM components, and selectors like html or body still cannot reach into a shadow root. If you want a global CSS framework to style Shadow DOM components, import that stylesheet and include it in sharedStyles, or keep those components in Light DOM.
Shadow DOM CSS Selectors
Shadow DOM provides special CSS selectors for enhanced styling control:
The :host Selector
:host SelectorStyle the component's host element from within the shadow root:
The :host-context() Selector
:host-context() SelectorStyle the host based on an ancestor's context:
The ::slotted() Selector
::slotted() SelectorStyle content that has been projected into a slot:
Important: The ::slotted() selector only works on direct children of the slot. It cannot select nested descendants within slotted content.
The ::part() Selector
::part() SelectorExpose specific elements for external styling using part attributes:
Styling from Outside: CSS Custom Properties
The most flexible way to style Shadow DOM components from outside is using CSS custom properties (CSS variables):
Using CSS Modules with Shadow DOM
CSS Modules provide class name transformation for avoiding naming conflicts. You can combine cssModules() with Shadow DOM for both style encapsulation and class name scoping:
Note: CSS Modules mappings do not inherit to child components. Each component must register its own cssModules() dependency.
Shadow DOM and Slots
Native <slot> elements require Shadow DOM. Attempting to use <slot> without Shadow DOM will throw a compilation error.
Basic Slot Usage
Usage:
Named Slots
Usage:
Fallback Content
Slots can have default content when nothing is projected:
Listening to Slot Changes
React to changes in slotted content:
For more advanced slot usage, including the @children decorator and component view model retrieval, see the Slotted Content documentation.
Constraints and Limitations
Cannot Combine with @containerless
Shadow DOM requires a host element to attach to. You cannot use both @useShadowDOM and @containerless on the same component:
Error: Invalid combination: cannot combine the containerless custom element option with Shadow DOM.
Native Slots Require Shadow DOM
Using <slot> elements without enabling Shadow DOM will cause a compilation error:
Error: Template compilation error: detected a usage of "<slot>" element without specifying shadow DOM options in element: broken-component
Solution: Either enable Shadow DOM or use <au-slot> instead:
Choosing Between Shadow DOM and Light DOM
Use Shadow DOM When:
Style isolation is critical: You need to prevent external styles from affecting your component
Building reusable components: Your component will be used in different contexts and needs predictable styling
Using native web component features: You need features like
<slot>, CSS:hostselector, or::partCreating a design system: Components should maintain consistent appearance regardless of environment
Use Light DOM (no Shadow DOM) When:
Easy styling is important: Parent components or application styles should easily affect the component
Working with global styles: You rely on application-wide styles or CSS frameworks (Bulma/Bootstrap/Tailwind) to flow into components
SEO is a concern: Search engines can more easily index light DOM content
Using
<au-slot>: You need Aurelia's slot features like$hostscope access
Practical Examples
Themed Button Component
Card with Multiple Slots
Component with Dynamic Styles
Best Practices
1. Use CSS Custom Properties for Theming
Allow users to customize your components through CSS variables with sensible defaults:
2. Provide Fallback Content for Slots
Give users a good default experience even when they don't provide slot content:
3. Namespace Your CSS Variables
Prevent naming conflicts by prefixing your component's CSS variables:
4. Consider Performance with Constructable Stylesheets
For optimal performance, Aurelia uses Constructable Stylesheets when supported by the browser, falling back to <style> elements otherwise.
Automatic caching: When you pass CSS strings to shadowCSS(), Aurelia automatically caches the compiled CSSStyleSheet instances. This means multiple instances of the same component share the same stylesheet object in memory.
For maximum control, you can create CSSStyleSheet objects directly:
5. Use Open Mode Unless You Have a Reason Not To
Closed mode prevents useful debugging and testing. Use open mode by default:
6. Document Your CSS Custom Properties
If your component supports theming, document the available CSS variables:
7. Convention-Based CSS Does Not Auto-Inject into Shadow DOM
Aurelia's convention-based CSS loading (where my-component.css is auto-imported alongside my-component.ts) does not automatically inject styles into Shadow DOM. For Shadow DOM components, you must explicitly use shadowCSS():
Additional Resources
Slotted Content Documentation - Deep dive into slots,
@children, and@slotteddecoratorsWeb Components Documentation - Using Aurelia components as web components
CustomElement API Reference - Complete API documentation including Shadow DOM options
Last updated
Was this helpful?