Skip to content

Segmented Control Glass

Rebuild the iOS glass segmented control out of the exposed parts and states.

Code

The following example assumes that you have imported the component and set up the theme.

Glass Segmented Control
<div class="glass-demo">
6 collapsed lines
<sv-segmented-control class="glass-segmented-control" aria-label="Calendar view" value="month">
<button value="day">Day</button>
<button value="week">Week</button>
<button value="month">Month</button>
<button value="year">Year</button>
</sv-segmented-control>
</div>
<style>
106 collapsed lines
.glass-demo {
display: flex;
justify-content: center;
padding: 3em 1.5em;
background-image:
radial-gradient(120% 120% at 10% 0%, #7c3aed99, transparent 60%),
radial-gradient(120% 120% at 100% 100%, #0ea5e999, transparent 60%),
radial-gradient(80% 100% at 60% 0%, #f43f5e66, transparent 70%), linear-gradient(135deg, #2e1065, #172554 55%, #083344);
border-radius: 8px;
}
.glass-segmented-control {
/* Capsule shape */
--sv-segmented-control-bdr-rad: 20px;
--sv-segmented-control-pad: 0.25em;
/* Space between buttons, where the hairline sits */
--pill-gap: 0.2em;
--sv-segmented-control-active-clr: #fff;
--sv-segmented-control-hover-bg: rgb(255 255 255 / 3%);
inline-size: min(100%, 22rem);
color: rgb(255 255 255 / 85%);
/* Translucent body. The edge ring below replaces the border. */
&::part(container) {
gap: var(--pill-gap);
background-color: #00000040;
border: none;
box-shadow:
0 6px 16px #00000045,
inset 0 1px 1px #ffffff8c;
}
/* Blurs and saturates what sits behind the capsule */
&::part(container)::before {
position: absolute;
inset: 0;
content: "";
border-radius: inherit;
backdrop-filter: blur(6px) saturate(160%);
}
/* Edge ring that reflects the colors behind it, brightest along the top and bottom edges */
&::part(container)::after {
position: absolute;
inset: 0;
padding: 1px;
pointer-events: none;
content: "";
border-radius: inherit;
backdrop-filter: blur(4px) brightness(180%) saturate(190%);
mask:
linear-gradient(#000, #0003 50%, #000000b3),
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
mask-composite: intersect, exclude, add;
}
/* Sliding and resting pill share one fill, so the selection never flickers */
&::part(indicator),
button[data-sv-active]::before {
background-color: rgb(255 255 255 / 14%);
box-shadow: 0 1px 3px rgb(0 0 0 / 20%);
}
button {
padding: 0.6em 1em;
font-weight: 500;
/* Hairline centered in the gap */
& + button::after {
position: absolute;
inset-block: 25%;
inset-inline-start: calc((var(--pill-gap) + 1px) / -2);
inline-size: 1px;
content: "";
background-color: rgb(255 255 255 / 15%);
transition: opacity var(--sv-segmented-control-dur, 350ms) ease;
}
/* Hidden next to the selected and hovered button */
&:is(:hover, [aria-checked="true"]) {
&::after,
& + button::after {
opacity: 0;
}
}
}
}
/* Vertically stack buttons on small screens */
@media (max-width: 450px) {
.glass-segmented-control {
--sv-segmented-control-flex-dir: column;
/* Hairline turns horizontal */
button + button::after {
inset-block: calc((var(--pill-gap) + 1px) / -2) auto;
inset-inline: 1em;
inline-size: auto;
block-size: 1px;
}
}
}
</style>