A video player whose progress bar is split into chapters: hovering one thickens it and pops its title and timestamp above. Each chapter's width is a flex-grow value set inline.
Also known as: youtube chapters hover css, video progress bar hover, chapter markers timeline.
<div class="hfx-video-chapters">
<div class="hfx-video-chapters-bar">
<button aria-label="Intro, 0:00" data-tip="Intro · 0:00" style="--g: 2"></button>
<button aria-label="Setup, 1:12" data-tip="Setup · 1:12" style="--g: 3"></button>
<button aria-label="Build, 3:40" data-tip="Build · 3:40" style="--g: 4"></button>
<button aria-label="Ship it, 7:05" data-tip="Ship it · 7:05" style="--g: 2.5"></button>
</div>
<div class="hfx-video-chapters-controls">
<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><span>2:31 / 9:48</span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 9V5.5A1.5 1.5 0 0 1 5.5 4H9M15 4h3.5A1.5 1.5 0 0 1 20 5.5V9M20 15v3.5a1.5 1.5 0 0 1-1.5 1.5H15M9 20H5.5A1.5 1.5 0 0 1 4 18.5V15"/></svg>
</div>
</div>.hfx-video-chapters {
--c: var(--hfx-color, #3b82f6);
font-size: var(--hfx-size, 16px);
display: flex;
flex-direction: column;
justify-content: flex-end;
width: 20em;
height: 10.5em;
padding: 0 0.9em 0.55em;
border-radius: 0.8em;
background:
linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent 48%),
radial-gradient(circle at 74% 30%, rgba(255, 255, 255, 0.85) 0 7%, transparent 7.5%),
radial-gradient(ellipse 62% 42% at 18% 100%, color-mix(in srgb, var(--c) 60%, #000) 0 98%, transparent 100%),
radial-gradient(ellipse 72% 48% at 88% 106%, color-mix(in srgb, var(--c) 78%, #000) 0 98%, transparent 100%),
linear-gradient(180deg, color-mix(in srgb, var(--c) 35%, #fff), var(--c));
color: #fff;
font-family: system-ui, sans-serif;
}
.hfx-video-chapters-bar {
display: flex;
gap: 0.2em;
}
.hfx-video-chapters-bar button {
--bar: rgba(255, 255, 255, 0.35);
position: relative;
flex: var(--g) 1 0;
height: 1.1em;
padding: 0;
border: 0;
background: none;
cursor: pointer;
}
.hfx-video-chapters-bar button:nth-child(1) { --bar: #fff; }
.hfx-video-chapters-bar button:nth-child(2) { --bar: linear-gradient(90deg, #fff 45%, rgba(255, 255, 255, 0.35) 45%); }
/* the visible segment, thinner than the hit area */
.hfx-video-chapters-bar button::before {
content: "";
position: absolute;
left: 0;
right: 0;
top: 50%;
height: 0.3em;
margin-top: -0.15em;
border-radius: 0.15em;
background: var(--bar);
transition: transform 0.2s ease;
}
.hfx-video-chapters-bar button::after {
content: attr(data-tip);
position: absolute;
bottom: calc(100% + 0.5em);
left: 50%;
padding: 0.35em 0.65em;
border-radius: 0.4em;
background: rgba(15, 23, 42, 0.85);
color: #fff;
font-size: 0.75em;
font-weight: 600;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transform: translate(-50%, 0.4em);
transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.3, 0.7, 0.4, 1);
}
.hfx-video-chapters-bar button:is(:hover, :focus-visible)::before { transform: scaleY(2); }
.hfx-video-chapters-bar button:is(:hover, :focus-visible)::after {
opacity: 1;
transform: translate(-50%, 0);
}
.hfx-video-chapters-controls {
display: flex;
align-items: center;
gap: 0.6em;
font-size: 0.8em;
font-variant-numeric: tabular-nums;
}
.hfx-video-chapters-controls svg { width: 1.35em; height: 1.35em; }
.hfx-video-chapters-controls svg:last-child { margin-left: auto; }
@media (prefers-reduced-motion: reduce) {
.hfx-video-chapters, .hfx-video-chapters *, .hfx-video-chapters::before, .hfx-video-chapters::after, .hfx-video-chapters *::before, .hfx-video-chapters *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--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.