Card ↔ list view toggle
Switch a music library between cards and a list: only the invisible grid changes, and three anchored layers per item (background, cover, text) morph to their new slots with data-⚓️-fill + data-⚓️-transition="morph".
<header class="intro">
<h1>Card ↔ list view</h1>
<p>
The only thing the toggle changes is the invisible grid behind the tracks: the
<code>.cell</code>s and their empty <code>.slot</code>s snap to the new layout instantly.
The card background, cover art and text are three separate layers, each anchored to its own
slot with <code>data-⚓️-fill</code> and <code>data-⚓️-transition="morph"</code>, so they morph
to their new place and size on their own. <code>data-⚓️-scope="all"</code> lets every row reuse
the same three anchor names. Zero JavaScript.
</p>
<form class="controls" aria-label="View controls">
<fieldset class="segmented">
<legend>View</legend>
<label><input type="radio" name="view" value="grid" checked> <span aria-hidden="true">▦</span> Cards</label>
<label><input type="radio" name="view" value="list"> <span aria-hidden="true">☰</span> List</label>
</fieldset>
<label class="xray-toggle"><input type="checkbox" name="xray"> X-ray <small>(green: <code>--item</code> cells, pink: <code>--cover</code>/<code>--text</code> slots)</small></label>
</form>
</header>
<main data-⚓️-root>
<ul class="library" aria-label="Tracks">
<li class="cell" style="--hue: 250" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Blue Monday</strong>
<span>New Order · 1983</span>
</div>
</li>
<li class="cell" style="--hue: 200" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Teardrop</strong>
<span>Massive Attack · 1998</span>
</div>
</li>
<li class="cell" style="--hue: 330" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Windowlicker</strong>
<span>Aphex Twin · 1999</span>
</div>
</li>
<li class="cell" style="--hue: 170" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Hyperballad</strong>
<span>Björk · 1995</span>
</div>
</li>
<li class="cell" style="--hue: 290" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Midnight City</strong>
<span>M83 · 2011</span>
</div>
</li>
<li class="cell" style="--hue: 60" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Roygbiv</strong>
<span>Boards of Canada · 1998</span>
</div>
</li>
<li class="cell" style="--hue: 20" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Everything In Its Right Place</strong>
<span>Radiohead · 2000</span>
</div>
</li>
<li class="cell" style="--hue: 0" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Da Funk</strong>
<span>Daft Punk · 1995</span>
</div>
</li>
<li class="cell" style="--hue: 100" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Glory Box</strong>
<span>Portishead · 1994</span>
</div>
</li>
<li class="cell" style="--hue: 220" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Strobe</strong>
<span>deadmau5 · 2009</span>
</div>
</li>
<li class="cell" style="--hue: 140" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Gosh</strong>
<span>Jamie xx · 2015</span>
</div>
</li>
<li class="cell" style="--hue: 310" data-⚓️-name="--item" data-⚓️-scope="all">
<span class="slot slot-cover" data-⚓️-name="--cover" aria-hidden="true"></span>
<span class="slot slot-text" data-⚓️-name="--text" aria-hidden="true"></span>
<div class="bg" data-⚓️-to="--item" data-⚓️-fill data-⚓️-transition="morph"></div>
<div class="cover" data-⚓️-to="--cover" data-⚓️-fill data-⚓️-transition="morph" aria-hidden="true"></div>
<div class="text" data-⚓️-to="--text" data-⚓️-fill data-⚓️-transition="morph">
<strong>Porcelain</strong>
<span>Moby · 1999</span>
</div>
</li>
</ul>
</main>/* Demo styling only. The view toggle changes the invisible grid; every
visible layer is placed and sized by data-⚓️-fill on its own slot. */
@property --i {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
:root {
color-scheme: dark;
background: oklch(15% 0.01 250);
color: oklch(91% 0.01 250);
font: 400 1rem/1.5 system-ui, sans-serif;
}
body {
margin: 0;
padding: clamp(1rem, 4vw, 3rem);
}
.intro {
max-width: 64ch;
margin-block-end: 1.75rem;
}
h1 {
margin: 0 0 0.5rem;
font-size: clamp(1.4rem, 3vw, 1.9rem);
font-weight: 650;
letter-spacing: -0.02em;
}
.intro p {
margin: 0;
color: oklch(72% 0.015 250);
text-wrap: pretty;
}
code {
font: 0.88em ui-monospace, monospace;
color: oklch(84% 0.1 150);
white-space: nowrap;
}
/* ---------- controls ---------- */
.controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem 1.25rem;
margin-block-start: 1.25rem;
}
.segmented {
display: flex;
border: 0;
padding: 3px;
margin: 0;
border-radius: 999px;
background: oklch(24% 0.015 250);
}
.segmented legend {
position: absolute;
clip-path: inset(50%);
width: 1px;
height: 1px;
overflow: hidden;
}
.segmented label {
padding: 0.35rem 1rem;
border-radius: 999px;
font-size: 0.875rem;
cursor: pointer;
color: oklch(75% 0.015 250);
transition: background-color 0.2s, color 0.2s;
}
.segmented input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.segmented label:has(:checked) {
background: oklch(91% 0.01 250);
color: oklch(18% 0.01 250);
}
.segmented label:has(:focus-visible),
.xray-toggle:has(:focus-visible) {
outline: 2px solid oklch(82% 0.13 150);
outline-offset: 2px;
}
.xray-toggle {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.875rem;
color: oklch(75% 0.015 250);
cursor: pointer;
}
.xray-toggle input { accent-color: oklch(82% 0.13 150); }
/* ---------- the invisible layout (this is all the toggle touches) ---------- */
.library {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 10rem), 1fr));
max-inline-size: 60rem;
}
.cell {
--i: sibling-index();
display: grid;
grid-template: "cover" 1fr "text" 4.25rem / 1fr;
height: 15rem;
padding: 0.6rem;
box-sizing: border-box;
}
.slot-cover { grid-area: cover; }
.slot-text { grid-area: text; }
body:has([name="view"][value="list"]:checked) {
& .library {
grid-template-columns: 1fr;
gap: 0.5rem;
max-inline-size: 40rem;
}
& .cell {
grid-template: "cover text" 1fr / 3.5rem 1fr;
column-gap: 0.9rem;
height: 4.75rem;
}
& .text {
padding-block: 0.55rem;
}
}
/* ---------- the visible layers (looks only) ---------- */
.bg,
.cover,
.text {
/* a small cascade across the items; the library's morph sets the rest */
transition-delay: calc(var(--i) * 22ms);
}
.bg {
border-radius: 1rem;
background: oklch(21% 0.02 var(--hue));
box-shadow:
0 0 0 1px oklch(36% 0.04 var(--hue) / 0.7),
0 12px 28px -14px oklch(0% 0 0 / 0.8);
}
.cover {
border-radius: 0.6rem;
background:
repeating-radial-gradient(circle at 50% 50%, transparent 0 3px, oklch(0% 0 0 / 0.14) 3px 4px),
conic-gradient(from calc(var(--hue) * 1deg),
oklch(70% 0.16 var(--hue)),
oklch(45% 0.15 calc(var(--hue) + 70)),
oklch(78% 0.12 calc(var(--hue) + 140)),
oklch(70% 0.16 var(--hue)));
}
.text {
display: grid;
align-content: center;
padding-inline: 0.15rem;
overflow: hidden;
line-height: 1.3;
}
.text strong {
font-weight: 600;
font-size: 0.95rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.text span {
font-size: 0.8rem;
color: oklch(70% 0.04 var(--hue));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ---------- X-ray: outline every anchor and name it ---------- */
body:has([name="xray"]:checked) {
& [data-⚓️-name] {
outline: 1px dashed oklch(82% 0.13 150);
outline-offset: -1px;
}
& .slot { outline-color: oklch(80% 0.14 330); }
& .bg,
& .cover,
& .text {
opacity: 0.6;
}
}
@media (prefers-reduced-motion: reduce) {
.segmented label { transition: none; }
}data-⚓️-filldata-⚓️-namedata-⚓️-rootdata-⚓️-scopedata-⚓️-todata-⚓️-transition