For most of the 2000s, embedding video or audio in a web page meant embedding a plugin. Flash was the dominant choice, with QuickTime, Windows Media, and RealPlayer as alternatives — each requiring the visitor to have the right plugin installed, each with its own accessibility problems, and none of it addressable by CSS or scriptable through a standard DOM API. The HTML5 specification’s <video> and <audio> elements replaced that entire model with native browser support, and the shift changed what “embedding media” means for a frontend developer.

The Basic Syntax

The <video> element and <audio> element both work the same basic way: point at a source, and the browser handles decoding and rendering natively.

<video controls width="640" height="360">
  <source src="clip.mp4" type="video/mp4">
  <source src="clip.webm" type="video/webm">
  Your browser does not support the video element.
</video>
<audio controls>
  <source src="track.mp3" type="audio/mpeg">
  <source src="track.ogg" type="audio/ogg">
</audio>

The controls attribute is what makes the native play/pause/volume/scrubber interface appear. Without it, the element renders (for video) or is invisible (for audio) with no visible way for a visitor to interact with it — which is intentional, since many use cases (background video, JavaScript-controlled players) don’t want the native chrome at all.

Multiple Sources and Format Fallback

Browsers do not all support the same video and audio codecs, which is why the <source> element accepts multiple candidates. The browser evaluates each <source> in order and uses the first one it can play, ignoring the rest. This is why the pattern almost always lists MP4 (H.264) first, since it has the broadest support, followed by WebM as an open-format alternative for browsers or contexts that prefer it.

The content between the closing <source> tags and </video> is a fallback for browsers with no <video> support at all — by 2018 this is a vanishingly rare case, but it remains valid markup and costs nothing to include.

Attributes That Control Playback Behavior

  • autoplay — starts playback immediately on page load. Modern browsers restrict this: autoplaying video with sound is blocked by default in Chrome and Safari, but autoplaying muted video is generally allowed. Combining autoplay with muted is the standard pattern for background/hero video.
  • muted — starts the element with volume at zero. Required in practice for reliable autoplay.
  • loop — restarts playback from the beginning when it ends.
  • playsinline — on iOS Safari specifically, prevents video from automatically expanding to fullscreen on play. Without it, an inline background video on a mobile page will hijack the screen the moment it starts.
  • preload — hints to the browser how aggressively to buffer before playback starts. Values are none (don’t preload anything), metadata (fetch just duration/dimensions), and auto (browser decides, typically aggressive). preload="none" is the considerate default for pages with many embedded videos, since auto on every embed can multiply bandwidth use significantly.
<video autoplay muted loop playsinline preload="none">
  <source src="hero-bg.mp4" type="video/mp4">
</video>

Captions and Accessibility with track

The <track> element attaches text tracks — captions, subtitles, descriptions, or chapters — to a <video> element using the WebVTT format:

<video controls>
  <source src="talk.mp4" type="video/mp4">
  <track kind="captions" src="captions-en.vtt" srclang="en" label="English" default>
</video>

The kind attribute distinguishes caption types: subtitles (dialogue translation, assumes the viewer can hear), captions (dialogue plus non-speech audio cues, for viewers who can’t hear the audio), descriptions (audio-description track for visually impaired viewers), and chapters (navigation markers). The default attribute marks which track displays automatically without the viewer needing to enable it manually.

Captions are not a cosmetic add-on. For any video containing spoken content published to a general audience, captions are the difference between the content being accessible to deaf and hard-of-hearing users and not — and browser-native <track> support means this no longer requires a third-party player or Flash-based caption overlay.

Styling Media Elements with CSS

Unlike Flash embeds, <video> and <audio> are ordinary elements subject to the normal box model (see our guide to the CSS box model for how width, padding, and border interact on any element) — width, max-width, border, and border-radius all apply as expected:

video {
  max-width: 100%;
  height: auto;
  border-radius: 8px;
}

The one significant limitation: the native playback controls (controls attribute) render using the browser’s own UI, and their appearance cannot be restyled with CSS in most browsers. Teams that need a fully custom-branded player interface build their own controls with JavaScript against the HTMLMediaElement API (play(), pause(), currentTime, volume, and related events), hiding the native controls attribute and rendering custom buttons instead.

The poster Attribute and Loading Behavior

For <video> specifically, the poster attribute specifies an image to display before playback starts — the frame a visitor sees while the video itself hasn’t loaded or started yet:

<video controls poster="thumbnail.jpg" preload="metadata">
  <source src="clip.mp4" type="video/mp4">
</video>

Without a poster, browsers typically show a blank black frame (or, with preload="metadata", the video’s own first frame once metadata has loaded) until playback begins, which can look broken on a slow connection. Supplying a lightweight poster image gives visitors an immediate visual cue that the media exists and is loading, rather than an empty rectangle.

Responsive Video With the Padding-Ratio Technique

Because <video> participates in the ordinary box model like any other element, making it responsive — scaling proportionally to its container’s width rather than rendering at a fixed pixel size — currently requires a padding-based aspect-ratio technique. Percentage values for padding-top and padding-bottom resolve against the width of the containing block, which is the mechanism this trick exploits:

<div class="video-wrapper">
  <video controls>
    <source src="clip.mp4" type="video/mp4">
  </video>
</div>
.video-wrapper {
  position: relative;
  width: 100%;
  padding-top: 56.25%; /* 16:9 ratio: height/width × 100 */
}
.video-wrapper video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

The wrapper’s padding-top reserves a correctly proportioned space based purely on the wrapper’s own width, and the video is absolutely positioned to fill that reserved space exactly. It’s a workaround rather than an elegant primitive, but it’s the reliable, broadly supported way to keep video proportions correct across arbitrary container widths today.

What This Replaced, and Why It Mattered

Before native <video> and <audio> support matured, embedding media required a plugin, and every plugin brought its own security surface, its own accessibility gaps (screen readers generally could not interact with Flash content at all), its own mobile-support gaps (iOS never supported Flash), and its own performance cost. The HTML5 media elements moved playback into the browser engine itself: keyboard-accessible by default, scriptable through a standard DOM API rather than a proprietary plugin API, stylable (partially) with ordinary CSS, and requiring no separate runtime installed on the visitor’s machine.

The practical result for a frontend developer: embedding video or audio today means writing two lines of semantic HTML and, for anything beyond default playback, a small amount of standard JavaScript — with no plugin dependency, no proprietary format lock-in beyond basic codec support, and native accessibility hooks built in from the start.

The load() Method and Dynamically Changing Sources

The HTMLMediaElement API includes a load() method for cases where a <video> or <audio> element’s source needs to change after the page has already loaded — swapping a video playlist item, for instance:

const player = document.querySelector('video');
player.querySelector('source').src = 'new-clip.mp4';
player.load();
player.play();

Simply changing a <source> element’s src attribute does not, by itself, make the browser re-evaluate it — the browser only reads <source> elements when the media element is first initialized or explicitly told to reconsider them. Calling .load() forces that re-evaluation, after which .play() can start the newly assigned source. This is a common point of confusion for anyone building a custom playlist or a video switcher, since the intuitive approach (just changing the attribute) silently does nothing without the explicit load() call.

Frequently Asked Questions

Do I need to provide both MP4 and WebM versions of a video?

For maximum compatibility, yes. MP4 (H.264) has the broadest browser support and should be listed first. WebM is a strong second source for browsers and contexts that support it, and including it avoids depending on a single codec’s licensing and support future. Many production sites ship MP4 only today given how consistent H.264 support has become.

Why won’t my video autoplay?

Modern browsers block autoplay of video with audio by default, as an anti-annoyance measure. Autoplaying muted video is generally allowed. Add the muted attribute alongside autoplay, and if the video needs to work reliably on iOS Safari, add playsinline as well to prevent it from forcing fullscreen.

Can I style the native video player controls?

Not reliably. The controls attribute renders the browser’s own native UI, which is not exposed to CSS styling in most browsers. To get a fully custom-branded player, hide the native controls and build custom buttons using JavaScript against the HTMLMediaElement API (play(), pause(), volume, and related properties and events).

What’s the difference between subtitles and captions in the track element?

Subtitles assume the viewer can hear the audio and just need dialogue translated or transcribed. Captions include dialogue plus descriptions of relevant non-speech sounds, intended for viewers who cannot hear the audio at all. Both use the same <track> element syntax; the distinction is set via the kind attribute.

Is Flash still worth using for embedding media in new projects?

No. Adobe has already announced Flash Player’s end-of-life for the end of 2020, and browser vendors are progressively restricting or requiring click-to-activate for Flash content ahead of that date. Any new project embedding video or audio should use native HTML5 media elements — they require no plugin, work on mobile platforms that never supported Flash, and have a stable, non-deprecated future.