CSS Shadow Parts let a component author expose specific internal elements for external styling, going further than custom properties allow. This lesson covers the part attribute and ::part() selector.
Exposing Elements with the part Attribute
Adding a part="name" attribute to an element inside your Shadow DOM template explicitly marks it as stylable from outside the component, using the ::part(name) CSS selector from consumer stylesheets. Unlike custom properties (which expose specific *values*), parts expose entire *elements*, letting consumers apply any CSS property to that specific internal element.
This is deliberately more powerful, and more explicit, than piercing Shadow DOM with a deprecated combinator — the component author still fully controls exactly which internal elements are exposed this way, and under what names.
class ProgressBar extends LitElement {
@property({ type: Number }) value = 0;
render() {
return html`
<div part="track">
<div part="fill" style="width: ${this.value}%"></div>
</div>
`;
}
}
/* From the consumer's page-level CSS: */
progress-bar::part(track) { background: #eee; border-radius: 999px; }
progress-bar::part(fill) { background: limegreen; }
The consumer can fully restyle the track and fill elements — colors, borders, even layout — without needing any custom property the component author didn't think to expose.
part Attribute and ::part() Selector
<!-- Inside the component's Shadow DOM -->
<div part="header">...</div>
/* From outside, in the consumer's stylesheet */
my-component::part(header) {
/* any CSS properties */
}
The part attribute is a native platform feature (CSS Shadow Parts spec), not something Lit invents.
::part() can only be used from *outside* the component's own Shadow DOM — it doesn't work from inside the component's own static styles.
An element can be assigned multiple part names separated by spaces: part="button primary", then targeted as ::part(button) or ::part(primary).
Parts do not cascade through nested custom elements automatically — a part inside a slotted child component needs exportparts to forward it further, an advanced technique beyond this lesson's scope.
Custom Properties vs. Parts
Choosing the right theming mechanism for a given need.
Need
Use
Consumer sets one specific value (a color, a radius)
CSS custom property
Consumer needs full control over an internal element's styling
part + ::part()
Styling top-level projected (slotted) content
::slotted()
A value that should cascade to descendants automatically
CSS custom property
An element the component author wants to expose deliberately, nothing more
part + ::part()
Designing Which Elements to Expose as Parts
Not every internal element should be a part — only expose elements that make sense as stable, independently-styleable pieces of your component's public API. Renaming or restructuring un-exposed internal elements later is a safe refactor; renaming or removing a documented part is a breaking change for consumers.
Expose structural, visually distinct pieces: a track, a fill, a header, a close button.
Avoid exposing implementation-detail wrapper <div>s that exist purely for internal layout.
Document every part name exactly like a property or event — consumers need to know what's available.
Combining Parts with Custom Properties
A well-designed component often offers both: sensible custom properties for the common theming needs (colors, spacing), plus parts for consumers who need more complete control for specific advanced cases. Most consumers use the custom properties; power users reach for ::part() when they need something the custom properties don't cover.
Trying to use ::part() from inside the component's own static styles — it only works from an external stylesheet.
Exposing every internal wrapper element as a part, turning routine internal refactors into breaking API changes.
Forgetting that parts don't automatically forward through nested custom elements without exportparts.
Not documenting available part names, leaving consumers to inspect the Shadow DOM manually to discover them.
Key Takeaways
The part attribute marks specific internal elements as styleable from outside via ::part().
Parts expose whole elements for full external styling, going further than the specific values custom properties expose.
::part() only works from a stylesheet outside the component's own Shadow DOM.
Design and document your exposed parts deliberately — they become part of your component's stable public API.
Pro Tip
When in doubt about whether to expose something as a custom property or a part, start with a custom property for the specific value you know consumers need — you can always add a part later for cases that genuinely need more control, but removing an already-documented part is a breaking change.
You now understand CSS Shadow Parts. Next, learn about adopted stylesheets and how Lit shares CSS efficiently across instances.