A track list in the Spotify style: hovering a row tints it, rolls the track number up into a play button and fades in a like button. The number and the icon share one grid cell.
Also known as: spotify hover effect css, track list hover play button, song row hover.
<ol class="hfx-playlist-row">
<li>
<button class="hfx-playlist-row-play" aria-label="Play Night Drive"><span>1</span><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M7.5 5.2v13.6a.8.8 0 0 0 1.2.7l10.6-6.8a.8.8 0 0 0 0-1.4L8.7 4.5a.8.8 0 0 0-1.2.7z"/></svg></button>
<span class="hfx-playlist-row-cover" style="--hue: 0deg"></span>
<span class="hfx-playlist-row-title"><b>Night Drive</b><small>Lumen</small></span>
<button class="hfx-playlist-row-like" aria-label="Like Night Drive"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7.5-4.6-7.5-10A4.3 4.3 0 0 1 12 7.2 4.3 4.3 0 0 1 19.5 10c0 5.4-7.5 10-7.5 10z"/></svg></button>
<time>3:42</time>
</li>
<li>
<button class="hfx-playlist-row-play" aria-label="Play Paper Planes"><span>2</span><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M7.5 5.2v13.6a.8.8 0 0 0 1.2.7l10.6-6.8a.8.8 0 0 0 0-1.4L8.7 4.5a.8.8 0 0 0-1.2.7z"/></svg></button>
<span class="hfx-playlist-row-cover" style="--hue: 150deg"></span>
<span class="hfx-playlist-row-title"><b>Paper Planes</b><small>Juno Park</small></span>
<button class="hfx-playlist-row-like" aria-label="Like Paper Planes"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7.5-4.6-7.5-10A4.3 4.3 0 0 1 12 7.2 4.3 4.3 0 0 1 19.5 10c0 5.4-7.5 10-7.5 10z"/></svg></button>
<time>2:58</time>
</li>
<li>
<button class="hfx-playlist-row-play" aria-label="Play Low Tide"><span>3</span><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M7.5 5.2v13.6a.8.8 0 0 0 1.2.7l10.6-6.8a.8.8 0 0 0 0-1.4L8.7 4.5a.8.8 0 0 0-1.2.7z"/></svg></button>
<span class="hfx-playlist-row-cover" style="--hue: 270deg"></span>
<span class="hfx-playlist-row-title"><b>Low Tide</b><small>The Harbors</small></span>
<button class="hfx-playlist-row-like" aria-label="Like Low Tide"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7.5-4.6-7.5-10A4.3 4.3 0 0 1 12 7.2 4.3 4.3 0 0 1 19.5 10c0 5.4-7.5 10-7.5 10z"/></svg></button>
<time>4:15</time>
</li>
</ol>.hfx-playlist-row {
--c: var(--hfx-color, #3b82f6);
font-size: var(--hfx-size, 16px);
width: 20em;
margin: 0;
padding: 0;
list-style: none;
font-family: system-ui, sans-serif;
}
.hfx-playlist-row li {
display: grid;
grid-template-columns: 1.6em 2.4em 1fr auto 2.4em;
align-items: center;
gap: 0.7em;
padding: 0.4em 0.6em;
border-radius: 0.6em;
transition: background-color 0.2s ease;
}
.hfx-playlist-row-play {
display: grid;
place-items: center;
width: 1.6em;
height: 1.6em;
padding: 0;
overflow: hidden;
border: 0;
background: none;
color: inherit;
font: inherit;
cursor: pointer;
}
.hfx-playlist-row-play > * {
grid-area: 1 / 1;
transition: transform 0.3s cubic-bezier(0.3, 0.7, 0.4, 1), opacity 0.2s ease;
}
.hfx-playlist-row-play span {
color: color-mix(in srgb, currentColor 55%, transparent);
font-variant-numeric: tabular-nums;
}
.hfx-playlist-row-play svg {
width: 1.2em;
height: 1.2em;
color: var(--c);
opacity: 0;
transform: translateY(120%);
}
.hfx-playlist-row-cover {
width: 2.4em;
height: 2.4em;
border-radius: 0.35em;
background:
radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.55), transparent 45%),
linear-gradient(135deg, color-mix(in srgb, var(--c) 45%, #fff), var(--c) 60%, color-mix(in srgb, var(--c) 70%, #000));
filter: hue-rotate(var(--hue, 0deg));
}
.hfx-playlist-row-title {
display: grid;
min-width: 0;
line-height: 1.25;
}
.hfx-playlist-row-title b { font-weight: 600; }
.hfx-playlist-row-title small {
color: color-mix(in srgb, currentColor 60%, transparent);
font-size: 0.8em;
}
.hfx-playlist-row-like {
display: grid;
padding: 0;
border: 0;
background: none;
color: var(--c);
cursor: pointer;
opacity: 0;
transform: scale(0.6);
transition: opacity 0.2s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.hfx-playlist-row-like svg { width: 1.15em; height: 1.15em; }
.hfx-playlist-row time {
color: color-mix(in srgb, currentColor 60%, transparent);
font-size: 0.8em;
font-variant-numeric: tabular-nums;
text-align: right;
}
.hfx-playlist-row li:is(:hover, :focus-within) { background: color-mix(in srgb, var(--c) 12%, transparent); }
.hfx-playlist-row li:is(:hover, :focus-within) .hfx-playlist-row-play span {
opacity: 0;
transform: translateY(-120%);
}
.hfx-playlist-row li:is(:hover, :focus-within) .hfx-playlist-row-play svg {
opacity: 1;
transform: none;
}
.hfx-playlist-row li:is(:hover, :focus-within) .hfx-playlist-row-like {
opacity: 1;
transform: none;
}
@media (prefers-reduced-motion: reduce) {
.hfx-playlist-row, .hfx-playlist-row *, .hfx-playlist-row::before, .hfx-playlist-row::after, .hfx-playlist-row *::before, .hfx-playlist-row *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}The same effect using Tailwind utility classes. No custom CSS or config needed.
<ol class="w-80">
<li class="group grid grid-cols-[1.5rem_2.5rem_1fr_auto_2.5rem] items-center gap-3 rounded-lg px-2.5 py-1.5 transition-colors hover:bg-blue-500/10">
<button aria-label="Play Night Drive" class="grid h-6 w-6 place-items-center overflow-hidden">
<span class="[grid-area:1/1] text-gray-500 transition duration-300 group-hover:-translate-y-full group-hover:opacity-0">1</span>
<svg class="[grid-area:1/1] h-5 w-5 translate-y-full text-blue-500 opacity-0 transition duration-300 group-hover:translate-y-0 group-hover:opacity-100" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M7.5 5.2v13.6a.8.8 0 0 0 1.2.7l10.6-6.8a.8.8 0 0 0 0-1.4L8.7 4.5a.8.8 0 0 0-1.2.7z"/></svg>
</button>
<span class="h-10 w-10 rounded-md bg-gradient-to-br from-blue-300 to-blue-600"></span>
<span class="grid leading-tight"><b class="font-semibold">Night Drive</b><small class="text-gray-500">Lumen</small></span>
<button aria-label="Like Night Drive" class="scale-75 text-blue-500 opacity-0 transition group-hover:scale-100 group-hover:opacity-100">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20s-7.5-4.6-7.5-10A4.3 4.3 0 0 1 12 7.2 4.3 4.3 0 0 1 19.5 10c0 5.4-7.5 10-7.5 10z"/></svg>
</button>
<time class="text-right text-sm text-gray-500">3:42</time>
</li>
</ol>--hfx-color on the element or a parent.
Currently previewing #3b82f6.--hfx-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.