Shadow DOM Encapsulation and Its Limits

Shadow DOM offers a level of style encapsulation that no other approach can match. In large codebases with CSS written by many developers over many years, naming collisions between classes like .price are common, and CSS gives no warning when they occur. CSS-in-JS libraries such as Emotion and styled-components sidestep this by generating random class names like .bwzfXH, but that only solves the collision problem.

Styles applied directly to HTML elements—whether from base styles or CSS resets—can still break components. Eric Meyer’s reset, for example, wipes out the user agent’s default margin and padding values, which some components rely on. Shadow DOM is the only thing that offers near-full encapsulation: code from a messy stylesheet cannot affect components inside the shadow tree, and component authors need not worry about their internal styles leaking out.

/* This will have no effect on buttons inside shadow DOM */
button { background-color: lime !important; }

Notably, inheritable styles such as color, font and line-height still pass through the shadow boundary. Preventing that inheritance requires all: initial, or ideally all: revert once browser support improves. Encapsulation also means a component’s internal elements can’t be styled from outside—a double-edged sword. It protects components from unintended interference but also locks them down entirely, making them difficult to theme or customize.

Why Custom Properties Aren’t Enough

Before CSS Shadow Parts, the only way to let consumers style a custom element from outside its shadow DOM was through CSS custom properties. That approach forces the component author to define a custom property for every CSS property the consumer might want to change. The limitation becomes obvious with state-based styles like :hover and :focus, each of which multiplies the number of properties needed.

Ionic’s button component is a case in point—it exposes 23 custom properties to cover styling options. That’s tedious to maintain and still constrains what consumers can actually change.

Styling Exposed Parts With ::part()

The ::part() selector solves this by letting component authors explicitly expose selected elements of the shadow tree to outside styling. With Safari 13.1 adding support, the selector now works across Chrome, Edge, Opera, Safari and Firefox.

To make an element styleable, the component author adds a part attribute to it inside the shadow DOM. That element then becomes eligible for styling from outside using the full range of CSS properties, not just a pre-defined subset. Exposed elements can also be targeted in different states via pseudo-classes—for example, ::part(button):hover or ::part(button):focus.

<div part="box">...</div>  
<button>Click me</button>

Here, the div is open to complete customization while the button remains visually locked down except for what the component author allows. Like HTML classes, an element can carry multiple part names:

<div part="box thing">...</div>

Selective Flexibility in Components

The power of ::part is that it gives component authors fine-grained control over what consumers can style. Parts of a component can be fully exposed, while other internal elements stay protected within the shadow DOM. That makes the approach well-suited for design systems and component libraries where a component requires some degree of themeability but certain internal styling decisions are intentionally fixed.