Video Chapter Player
A free video chapters react section built on native playback, with captions on by default.
Video with chapters
Folded shut
A dark frame and a closed bloom, standing in for where Halyard started: two thousand trial sign-ups a month, four reps working the list alphabetically, and no way to tell which of them would ever buy.
Chapters
Read the transcript
Ambient footage with no spoken dialogue, running about five seconds. A stylised purple bloom opens against a dark background, from folded petals to fully open. Nothing is said and no figures appear on screen: the film is the title sequence, and the numbers it introduces are set out in the written case study. Halyard moved from form-field lead scoring to usage-based scoring in January, and by the end of March was passing 38% more qualified trials to sales while its four reps called about forty fewer people a week.
Customise
Code
<MediaVideoChapters videoSrc={videoSrc} posterSrc={posterSrc} posterAlt={posterAlt} captionsSrc={captionsSrc} chapters={chapters} transcript={transcript} />Video Chapter Player is a free video chapters react component for Next.js, and the deliberate part is how little of the player it replaces. Playback is the browser's own: real controls, a poster, preload="metadata", captions on by default and no autoplay under any circumstances. Chapters seek by setting currentTime, and only resume play if the video was already playing, because pressing a chapter on a paused video is a request to look somewhere rather than to start.
The active chapter is derived from throttled timeupdate readings and only becomes React state when the index actually changes, so a playing video does not re-render its chapter list several times a second. Chapters are sorted by start time rather than trusted in the order they arrive, which is what keeps the indicator from jumping backwards on data from a CMS. A hidden tab pauses and never auto-resumes, the complete transcript sits in a native disclosure and stays available even when the media fails, and with reduced motion enabled the indicator and description change instantly while playback, seeking, captions and the transcript are unchanged.
Install
Add the @te registry to your components.json once:
{
"registries": {
"@te": "https://templateempire.io/r/{name}.json"
}
}Then install the component:
npx shadcn@latest add @te/media-video-chaptersUsage
import { MediaVideoChapters } from "@/components/media-video-chapters";
const chapters = [
{ id: "intro", title: "Introduction", start: 0, description: "What this covers." },
{ id: "setup", title: "Setting up", start: 42, description: "Installing the pieces." },
{ id: "ship", title: "Shipping it", start: 128, description: "Getting it live." },
];
export default function Page() {
return (
<MediaVideoChapters
videoSrc="/media/walkthrough.mp4"
posterSrc="/media/walkthrough-poster.jpg"
posterAlt="A screen recording of the dashboard being configured."
captionsSrc="/media/walkthrough.en.vtt"
chapters={chapters}
transcript={transcript}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| videoSrc | string | required | Native video source. Never autoplayed under any setting. |
| posterSrc | string | required | Poster shown before playback begins. |
| posterAlt | string | required | Describes what the video shows. It labels the player, and it is what replaces the media entirely if the source fails to load. |
| captionsSrc | string | required | WebVTT captions, added as the default track so they are on without anyone having to find them. |
| chapters | ReadonlyArray<{ id: string; title: string; start: number; description: string; thumbnailSrc?: string }> | required | Two to eight chapters with unique ids. Sorted by start time rather than trusted in the given order, and a chapter whose start is not a finite number at or above zero is dropped rather than coerced. |
| transcript | string | required | The complete transcript, shown in a native disclosure below the chapter list. |
| layout | "side" | "below" | "side" | Where the chapter list sits at wide sizes. Both stack on narrow screens. |
| thumbnailSize | number | 72 | Chapter thumbnail width in CSS pixels, clamped to 48-112. Thumbnails are decorative; the title and description carry the meaning. |
| indicatorStiffness | number | 360 | Spring stiffness for the active chapter marker, clamped to 140-560. |
| panelDuration | number | 0.28 | How long the description crossfade takes in seconds, clamped to 0-0.7. Zero makes it instant. |
| mediaRatio | "16/9" | "4/3" | "16/9" | The media box's aspect ratio. Held by CSS, so the section reserves its space before the video loads and nothing shifts. |
| radius | number | 24 | Media and panel corner radius in CSS pixels, clamped to 0-44. |
| className | string | undefined | Additional classes merged onto the section with cn(). |
Customisation
Video Chapter Player declares three tokens of its own, each falling back to a global semantic token, so it retints from an ancestor without touching the component source. Geometry and timing come from props rather than tokens, because they are clamped: --video-chapters-accent colours the active chapter marker and every focus ring, --video-chapters-panel is the chapter and transcript surface, and --video-chapters-divider is the separator between chapters.
:root {
--video-chapters-accent: var(--chart-1);
--video-chapters-panel: var(--muted);
--video-chapters-divider: oklch(from var(--border) l c h / 0.7);
}
<MediaVideoChapters layout="below" mediaRatio="4/3" radius={8} {...media} />Frequently asked questions
- Is Video Chapter Player free for commercial use?
- Yes. Video Chapter Player is MIT licensed, so you can use it in personal and commercial projects without attribution.
- Why native video controls instead of a custom player?
- Because the browser's player is already keyboard operable, already exposes its state to assistive technology, already honours the reader's caption and volume preferences across sites, and already behaves correctly on a phone. A custom scrubber has to reimplement all of that and usually reimplements some of it. What this component adds is the part the browser has no opinion about: chapters that seek, a description that follows them, and a complete transcript. Captions are added as the default track, so they are on rather than available.
- Is Video Chapter Player accessible for keyboard and screen reader users?
- Yes. Playback is the native player's, so all of its keyboard handling comes for free. Every chapter is a real button in the normal Tab order, with Home, End and the up and down arrows added on top rather than instead - a reader who has not learned about the arrow keys can still reach chapter four with Tab. The active chapter carries aria-current and repeats its state in visible text, so it never depends on the marker or the accent colour, and thumbnails are hidden from assistive technology because the title beside them already says the same thing. The transcript is a native disclosure that stays available even if the media fails, and a hidden tab pauses playback without ever resuming it on its own.