Data display
Diamond stage stepper
A progress stepper drawn as rotated-square gems strung on a thread, with done/current/locked states encoded in fill and opacity — and the thread only rendered when there are two or more stages.
Reach for it when building
- multi-part series navigation
- wizard and onboarding steps
- quest or level progression
- course module sequences
- release stage indicators
- stepper
- series
- skeuomorphic
- css-only
- no-assets
<div class="dss-wrap">
<ul class="dss-strip dss-joined">
<li class="dss-stage dss-done"><span>1</span></li>
<li class="dss-stage dss-done"><span>2</span></li>
<li class="dss-stage dss-current"><span>3</span></li>
<li class="dss-stage dss-locked"><span>4</span></li>
</ul>
<p class="dss-caption">Part 3 of 4 — accent fill is done, signal fill is you-are-here</p>
</div>Colors come from shared theme tokens — --surface, --ink, --border, --accent and friends — so this CSS carries no palette
of its own. Use Runnable file to copy the tokens along with it.
.dss-wrap { display: flex; flex-direction: column; gap: 0.5rem; }
.dss-strip {
display: flex;
gap: 0.75rem;
width: fit-content;
position: relative;
list-style: none;
margin: 0;
padding: 0.5rem;
}
/* The thread the gems hang on. Rendered only via the "joined" class, added
when there are 2+ stages: a single diamond on a line reads as a broken
strip, not a short one. */
.dss-joined::before {
content: '';
position: absolute;
left: 0.9rem;
right: 0.9rem;
top: 50%;
height: 2px;
background: color-mix(in srgb, var(--dim) 55%, transparent);
}
.dss-stage {
z-index: 1;
width: 2rem;
height: 2rem;
rotate: 45deg;
border-radius: 5px;
border: 2px solid var(--dim);
background: var(--surface);
display: grid;
place-items: center;
}
/* Numbers counter-rotate so the digit stays upright inside the gem. */
.dss-stage span {
rotate: -45deg;
font-family: var(--mono);
font-size: 0.875rem;
font-weight: 700;
color: var(--ink);
}
.dss-done {
background: var(--accent);
border-color: color-mix(in srgb, var(--accent) 70%, var(--ink));
}
.dss-done span { color: var(--accent-ink); }
.dss-current {
background: var(--signal);
border-color: color-mix(in srgb, var(--signal) 70%, var(--ink));
}
.dss-current span { color: var(--surface); }
.dss-locked { opacity: 0.6; }
.dss-caption {
margin: 0;
font-size: 0.9375rem;
color: var(--dim);
}Paste this into an agent to rebuild the pattern from scratch.
Build a progress stepper for a bounded sequence — a multi-part series, a wizard, a level track — drawn as small rotated-square "gems" strung on a thread.
Each stage is a 2rem square with `rotate: 45deg` and a small border-radius, so it reads as a cut gem rather than a plain diamond. The number inside counter-rotates `-45deg` to stay upright; use the mono face and center it with `display: grid; place-items: center` on the gem. The thread is a 2px absolutely-positioned line through the vertical center of the list, inset from both ends so it starts and stops under the outermost gems, with the gems given `z-index: 1` to sit on top of it.
One guard that matters more than it looks: only render the thread when there are two or more stages. Gate it behind a class the component adds conditionally — a single diamond with a line through it reads as a broken strip, not a short one.
Three states, each encoded twice so color never carries alone: done is filled with the accent (border darkened by mixing the accent toward ink, number in the accent's contrast color); current is filled with the signal color, which makes "you are here" a different hue from "behind you"; locked keeps the neutral outline and drops to `opacity: 0.6`. A caption line below the strip states the position in words ("Part 3 of 4") for anyone who can't or doesn't read the gems.
Use a real list (`ul`/`li`) with list styling stripped, since the content is a sequence.
Every color comes from theme custom properties, and the fills have to keep their number legible in both light and dark themes.