Parts
CSS variables are the main way to style steam-block, and they cover most designs. For everything beyond that, every box inside the component is also exposed as a part. With the ::part() selector your own stylesheet can reach that box and use any CSS property on it: hover states, keyframe animations, backdrop-filter, and anything else the variables don’t offer.
Parts are optional. If you never write a ::part() rule, nothing changes.
The concept
steam-block renders its layout in a shadow DOM, so normal selectors like steam-block .media can’t reach inside. A part is a named door into that shadow DOM:
/* CSS variable: the component applies the value for you */
steam-block {
--steam-block-media-border-radius: 1.5rem;
}
/* Part: you style the media box yourself, any property */
steam-block::part(media) {
border-radius: 1.5rem;
outline: 2px solid white;
outline-offset: -1rem;
}
Available parts
| Part | What it is | Template |
|---|---|---|
section | The outer box of the component | both |
media | The box around the media slot | both |
content | The box around the content slot | both |
content-inner | The wrapper directly around the slotted content | both |
left / right | The content column and the media column | split |
background / foreground | The media layer and the content overlay | hero |
The before and after layers are reachable too: ::part(media)::before, ::part(media)::after, ::part(content)::before and ::part(content)::after.
To target one template, combine the part with the host attribute: steam-block[type="hero"]::part(content). split is the default template, so steam-block:not([type="hero"]) targets every split.
Examples
Hover state on the content box
CSS variables can only react to hovering the whole component. A part can react to hovering the box itself. The look comes from the variables, the part only adds the interaction:
<style>
.parts-lift {
--steam-block-background: #f1f1f1;
--steam-block-padding: 2rem;
--steam-block-content-background: #fff;
--steam-block-content-padding: 1.5rem;
--steam-block-content-border-radius: .75rem;
}
.parts-lift::part(content) {
transition: transform .3s, box-shadow .3s;
}
.parts-lift::part(content):hover {
transform: translateY(-.5rem);
box-shadow: 0 1rem 2rem rgba(0, 0, 0, .2);
}
</style>
<steam-block class="parts-lift">
<div slot="content">
<h3>Hover this card</h3>
<p>Variables for the look, a part for the interaction.</p>
</div>
<img slot="media" src="your-image.jpg" alt="">
</steam-block>
Hover this card
Variables for the look, a part for the interaction.
An animated sheen on the media
The after layer sits on top of the media and is clipped to it. With a part you can animate it with your own keyframes:
<style>
@keyframes parts-sheen {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
.parts-sheen::part(media)::after {
background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, .5) 50%, transparent 70%);
animation: parts-sheen 3s ease-in-out infinite;
}
</style>
<steam-block class="parts-sheen" type="hero">
<img slot="media" src="your-photo.jpg" alt="">
<div slot="content"><h2>Sheen</h2></div>
</steam-block>
Sheen
A frosted glass overlay in a hero
backdrop-filter has no CSS variable. A part gives you any property, here scoped to heroes only:
<style>
steam-block[type="hero"].parts-glass::part(content) {
background: rgba(255, 255, 255, .15);
backdrop-filter: blur(12px);
border-radius: 1rem;
}
.parts-glass h2,
.parts-glass p {
color: #fff;
}
</style>
<steam-block class="parts-glass" type="hero">
<img slot="media" src="your-photo.jpg" alt="">
<div slot="content">
<h2>Frosted glass</h2>
<p>The photo blurs behind the text.</p>
</div>
</steam-block>
Frosted glass
The photo blurs behind the text.
The text color is set on the slotted h2 and p directly. They live in your own page, so plain CSS reaches them without a part.
An animated gradient border
A conic gradient painted in the border area of the content box, turning continuously. @property registers the angle as a real <angle>, which is what lets the browser animate it. Technique from How to animate borders in CSS on Let’s Build UI.
<style>
@property --parts-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@keyframes parts-spin {
to { --parts-angle: 360deg; }
}
.parts-border {
--steam-block-content-padding: 1.5rem;
}
.parts-border::part(content) {
border: 3px solid transparent;
border-radius: .75rem;
background:
linear-gradient(white, white) padding-box,
conic-gradient(from var(--parts-angle), #ff5400, #fde68a, #003049, #ff5400) border-box;
animation: parts-spin 4s linear infinite;
}
</style>
<steam-block class="parts-border">
<div slot="content">
<h3>Animated border</h3>
<p>A conic gradient turning in the border area.</p>
</div>
<img slot="media" src="your-image.jpg" alt="">
</steam-block>
Animated border
A conic gradient turning in the border area.
Both @property and @keyframes go in your own stylesheet, next to the part rule.
Good to know
A part rule wins. If you set both a CSS variable and a part rule for the same property, the part rule wins, whatever the specificity. Use that to override a variable on one instance.
Leave layout to the variables.
display,position,width,height,margin,orderand grid properties onsection,left,right,backgroundandforegroundfight the component’s own layout and can break it. For column widths, gaps, margins and alignment, use the CSS variables. They also follow the breakpoint for you.Breakpoints are yours. A part rule applies at every width. To match the component’s breakpoint, wrap it in your own media query with the same value as
--steam-block-min-width-breakpoint(768pxby default):@media (min-width: 768px) { steam-block::part(content) { backdrop-filter: blur(12px); } }Stylesheet only.
::part()is a selector, so it can’t go in an inlinestyle=""attribute. CSS variables can.Browser support.
::part()works in every current browser.