::part and exportparts
The part="name" attribute marks an element inside the shadow root as a named styling hook. Page CSS can then target it with the ::part(name) pseudo-element on the host — crossing the shadow boundary without losing encapsulation for everything else. The component author controls exactly which internals are exposed.
<theme-card></theme-card>Page CSS uses ::part() to style the exposed internals:
theme-card::part(title) { color: #7c3aed; font-size: 1.2rem; font-weight: bold; }theme-card::part(body) { color: #374151; font-size: 0.95rem; margin-top: 0.5rem; }The component’s own shadow stylesheet should not include these part styles — it only sets structural or default layout rules. The ::part() rules live entirely in page CSS (or in the css prop of LivePreview), and they win because of the explicit opt-in.
exportparts
Section titled “exportparts”When a component composes another custom element internally, its shadow-DOM children’s parts are not automatically visible to the page. The exportparts attribute re-exports inner parts under new names:
<outer-card exportparts="label: card-label"></outer-card>Now page CSS can write outer-card::part(card-label) to reach the inner component’s label part without needing to know the inner element’s tag name.
The css prop below is injected as a <style> block in the page, outside the component’s shadow root — exactly where ::part() rules belong.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
part + ::part() | A clear, opt-in styling API — only elements you mark can be styled from outside, preserving encapsulation for everything else | The part name becomes a public contract you have to maintain like an API — renaming or removing it later is a breaking change for anyone depending on ::part() |
exportparts | Re-exports the parts of a nested composed component without exposing its entire shadow tree | Adds a layer of mapping you must keep in sync by hand — if the inner component renames its part, the outer component’s exportparts breaks |
Common mistakes
Section titled “Common mistakes”- Treating a part as just an internal detail — renaming or removing
part="name"without considering it a version of a public API that outside CSS depends on, silently breaking any::part()a consumer wrote. - Forgetting
exportpartswhen composing a component — the parts of a nested custom element aren’t automatically exposed to the outer host; you must always declareexportparts="inner: outer-name"explicitly. - Putting
partin the wrong place —partmust be on an element inside the shadow root only; you can’t use::part()to reference the host itself — use:host/:host()for host styling instead.
💡 Real-world examples
Adobe Spectrum (Spectrum Web Components) — uses
::partextensively as the styling API for theming internals of every component, with each part name formally documented like a real public API. (opensource.adobe.com)Shoelace — documents every component’s part names on its docs page and treats renaming a part in a later version as a breaking change.