The short version
Use a <track> element pointing at a WebVTT file. If your file is .srt, convert it first β browsers do not read SubRip at all. The three things that break most often are the file format, the server's content type, and cross-origin access.
A minimal working example
<video controls width="640"> <source src="video.mp4" type="video/mp4"> <track src="captions.vtt" kind="subtitles" srclang="en" label="English" default> </video>
Save the captions as captions.vtt beside the video. That is a complete, working setup β no library, no configuration.
The four attributes that matter
srcβ path to the WebVTT file.kindβ usesubtitleswhen you are translating dialogue,captionswhen you are also conveying sound effects and speaker identity. The distinction is about accessibility, not about file format.srclangβ a BCP 47 language tag such asen,zh-Hansorpt-BR. Required for the track to be offered as a subtitle option.labelβ the text shown in the player's caption menu. Omit it and the menu entry can appear blank.
Add default to the track you want enabled automatically. Supply several tracks without marking any default and captions start switched off, leaving the viewer to find the menu.
Why your captions silently do nothing
Browsers fail quietly here. A broken track usually produces no console error and no visible message β the captions simply never appear.
1. The file is SubRip
An .srt file is ignored even when you point src straight at it. Convert it with the SRT to VTT converter. If you can see a comma inside the timestamp, that is your problem.
2. The server sends the wrong content type
WebVTT files must be served as text/vtt. Plenty of servers and default static-host configurations serve an unfamiliar extension as application/octet-stream or text/plain, and browsers then refuse the track. Check the response headers in your browser's network panel. On a static host, add a rule mapping .vtt to text/vtt.
3. The file is on a different origin
Track files follow cross-origin rules. If the video and the captions live on different domains, the caption request needs CORS headers and the <track> element needs crossorigin="anonymous". Without both, the track is blocked.
4. The WebVTT file is malformed
WebVTT requires the WEBVTT header, a blank line before the first cue, and a blank line between cues. A file missing any of those parses as empty. Re-convert from the original source, or run it through Clean subtitles.
Styling the captions
WebVTT cues can be styled from your own CSS using the ::cue pseudo-element β a genuine advantage over SubRip.
video::cue {
background: rgba(0,0,0,.75);
color: #fff;
font-size: 1.1rem;
}
video::cue(.speaker) { color: #ffd479; }
To use a class, write it into the cue as <c.speaker> in the WebVTT text. Class-based cue styling is supported unevenly across browsers, so treat it as an enhancement rather than a requirement.
Serving captions in more than one language
Add one <track> per language. The player builds its caption menu from label and srclang.
<track src="captions.en.vtt" kind="subtitles" srclang="en" label="English" default> <track src="captions.es.vtt" kind="subtitles" srclang="es" label="EspaΓ±ol"> <track src="captions.de.vtt" kind="subtitles" srclang="de" label="Deutsch">
Do you need a player library?
Not for basic captions. Native <track> support in modern browsers handles loading, the caption menu, timing and styling on its own. Reach for a JavaScript player library when you need adaptive bitrate streaming, DRM, or caption rendering that must look identical on every platform β not simply to display subtitles.