v151 · html · web api · shipped
Capability Elements: <usermedia> MVP
A new <usermedia> HTML element: a browser-controlled, user-activated button that brokers camera and microphone access — running the permission flow and the getUserMedia() call for you, then handing the resulting MediaStream to the page. Ships in Chrome 151 on desktop and Android, enabled by default.
<usermedia> element itself is enabled by default in Chrome 151 (runtime feature UserMediaElement, status stable). The legacy migration surface documented below (type attribute, isValid, permissionStatus, prompt events) is experimental — gated by the UserMediaElementLegacy flag, testable via chrome://flags or --enable-blink-features=UserMediaElementLegacy — exists only to migrate origin-trial users of the earlier <permission> element, and is slated for deprecation. Do not build new code on it.
at a glance
| Shipped in | Chrome 151 (desktop, Android) — milestone listing: “Enabled by default” |
|---|---|
| Status | Enabled by default (runtime feature UserMediaElement, stable) |
| Origin trial ran | Chrome 144–148, extended twice per the ChromeStatus extension records: to Chrome 152 (April 2026) and then to Chrome 155 (July 2026) |
| Spec | Media Capture and Streams Extensions — Media capture HTML elements |
| Explainer | Media Capture Elements explainer (w3c/mediacapture-extensions) |
| TAG review | w3ctag/design-reviews #1218 |
| ChromeStatus | 4926233538330624 — Capability Elements <usermedia> MVP |
why it exists
The traditional camera/microphone flow is fully imperative: call navigator.mediaDevices.getUserMedia() from script, handle the permission prompt asynchronously, then attach the stream to a <video> element. This creates two long-standing problems:
- The permission prompt fires from script execution with no strong signal of user intent anchored to a visible control — browsers can block or de-emphasize it.
- Users who previously denied the permission hit a “permission hole”: the site cannot re-prompt, and recovery means navigating browser settings.
<usermedia> makes the element itself the trusted control. Clicking it is an unambiguous user gesture on a browser-rendered button; the browser runs the permission UX inline, performs the getUserMedia() call, and delivers the MediaStream to the page. Because the browser controls the element's rendering, a previously-denied user gets an in-page recovery path instead of a dead end. The feature evolved out of the Page-Embedded Permission Control (PEPC) work: the generic <permission> element was origin-trialled first, then split into capability-specific elements (<usermedia> here; <camera> and <microphone> are a separate, still-experimental feature).
Syntax
<!-- Declarative: no required attributes. Children are fallback content. -->
<usermedia>Camera and microphone access required</usermedia>
<script>
const el = document.querySelector("usermedia");
// Optional: configure the getUserMedia() call the element will make.
el.setConstraints({ video: { width: 1280 }, audio: {} });
</script>
The element's interface is HTMLUserMediaElement (the tag is usermedia). The normative IDL from the specification, matching Chromium's shipping IDL:
[Exposed=Window]
interface HTMLUserMediaElement : HTMLElement {
[HTMLConstructor] constructor();
readonly attribute MediaStream? stream;
readonly attribute DOMException? error;
undefined setConstraints(optional HTMLMediaStreamConstraints constraints = {});
attribute EventHandler onstream;
attribute EventHandler onerror;
attribute EventHandler oncancel;
};
dictionary HTMLMediaStreamConstraints {
MediaTrackConstraintSet video;
MediaTrackConstraintSet audio;
};
Chromium implements the same surface split across html_user_media_element.idl (plus the HTMLMediaCaptureElementBase mixin in html_media_capture_element_base.idl) and two partial mixins in modules/mediastream (stream, setConstraints), gated by RuntimeEnabled=UserMediaElement — stable in runtime_enabled_features.json5.
Content model: the element belongs to the flow, phrasing, interactive, and palpable content categories; it can appear wherever phrasing content is expected, and any children serve as fallback content (rendered when the element can't operate — for example in a non-secure context). Presentation is a user-agent-controlled button whose label and icon reflect the capture state.
Source: Media Capture and Streams Extensions — The <usermedia> HTML element.Inputs
The element takes no required content attributes — <usermedia></usermedia> alone requests both camera and microphone with default settings. Three input channels exist:
- Constraints, set imperatively via
setConstraints()— anHTMLMediaStreamConstraintsdictionary with optionalvideoandaudioMediaTrackConstraintSetmembers. If never called, the element requests{ video: {}, audio: {} }. Required (exact-style) constraints are stripped by the user agent's constraint filter so the element cannot fail silently with anOverconstrainedError; the explainer additionally notes advanced constraints are ignored. - Fallback content: child nodes are rendered when the element cannot operate (non-secure context; unsupported engine), so put a sentence or link there, not controls.
- Global HTML attributes (
id,class,lang, …) behave as usual; there is no shippedtypeorautostartattribute on the MVP surface (see superseded draft design).
Outputs
Three observable outputs:
stream— aMediaStream(camera + microphone tracks per the constraints) assigned when acquisition succeeds;nullbefore that.- Events — exactly one terminal event per acquisition attempt:
stream(success),cancel(user dismissed or denied the prompt), orerror(all other failures). error— aDOMExceptiondescribing the failure when the terminal event iscancelorerror;nullotherwise.
The browser-rendered button itself is also an output: its label and icon track the underlying stream state (requesting, live, muted, ended) without any page code.
Source: Specification — activation and event model; Explainer — user journey and interaction model.Errors
Failures never throw synchronously from markup or clicks; they surface through the error property and the cancel/error events. The exact mapping, from the specification and Chromium's implementation:
| Cause | Event | error value |
|---|---|---|
| User dismisses or denies the permission prompt | cancel | NotAllowedError (“Permission dismissed” / “Permission denied”) |
| Activation without a genuine user gesture (no transient activation) | error | InvalidStateError |
| Stream creation fails (no device, device busy, track start failure) | error | OperationError (“Stream creation failed”) |
Underlying getUserMedia() rejects (device busy, no device, over-constrained after filtering, …) | error | The rejection DOMException (e.g. NotFoundError, NotReadableError, OverconstrainedError) |
| Non-secure context | none | The element renders its fallback content instead (Chromium also reports a DevTools audits issue) |
Recovery is always the same shape: read el.error in the terminal event handler, show your own recovery UI, and let the user click the element again — the element re-arms after a failed attempt.
Context
Exposure: HTMLUserMediaElement is [Exposed=Window] — no worker exposure. Like getUserMedia(), the element only operates in a secure context; elsewhere it shows fallback content. Feature-detect with "HTMLUserMediaElement" in window — on unsupported engines the tag parses as an unknown element and your fallback content renders.
Activation requirements: acquisition starts only from a genuine user gesture — the click must be a trusted event with transient user activation; programmatic or synthetic clicks are rejected (an error event with InvalidStateError). The browser, not the site, mediates the camera/microphone permission, and may skip the prompt entirely when the user has already granted it.
Default capture: with no configuration the element requests both camera and microphone (Chromium's ApplyDefaultConstraints() installs VIDEO_CAPTURE + AUDIO_CAPTURE descriptors when nothing else is set).
Lifecycle
The element is a small state machine:
- Request state —
streamisnull. A trusted click starts acquisition: the embedded permission flow runs first (prompt only if needed), then the browser performs thegetUserMedia()call with the configured constraints. - Terminal event — exactly one of
stream/cancel/errorfires per attempt (queued as an element task). On successstreamis populated before thestreamevent fires. - Active state — while the associated
MediaStreamis active, further clicks do not start a new request; unlike<camera>/<microphone>, the MVP<usermedia>deliberately does not become a mute/unmute toggle, because coordinating independent audio/video track states is ambiguous. Track muting stays with your own UI orMediaStreamTrack.enabled. - Reset — the element monitors its tracks (including external
track.stop()); once the stream ends it returns to the request state and the next click starts a fresh acquisition. A failed attempt also re-arms the element immediately.
Examples
<usermedia id="capture">This page needs your camera and microphone.</usermedia>
<video id="preview" autoplay playsinline muted></video>
<script>
const el = document.getElementById("capture");
const video = document.getElementById("preview");
// Optional configuration; call before the user interacts.
el.setConstraints({
video: { width: 1280 },
audio: { echoCancellation: true }
});
el.addEventListener("stream", () => {
// Success: el.stream is a live MediaStream.
video.srcObject = el.stream;
});
el.addEventListener("cancel", () => {
console.log("Prompt dismissed or denied:", el.error?.message);
});
el.addEventListener("error", () => {
console.error("Capture failed:", el.error?.name, el.error?.message);
});
</script>
No getUserMedia() call, no promise handling, no permission re-prompt logic — the browser owns the entire acquisition flow.
Compatibility
| Engine / runtime | Support | Notes |
|---|---|---|
| Chrome / Edge (desktop) | 151 | Enabled by default per the milestone listing and the stable runtime feature |
| Chrome (Android) | 151 | Per the ChromeStatus feature detail (desktop + Android 151); not supported on Android WebView (requires permission-manager support) |
| Firefox | Not supported | Vendor position “Under consideration” (mozilla/standards-positions #1392) |
| Safari | Not supported | No signal; dedicated position request WebKit/standards-positions #651 unanswered; earlier discussion in WICG/PEPC #62 |
| BCD | No entry | No api/HTMLUserMediaElement.json in browser-compat-data as of 2026-07-26 |
| WPT | Upstream WPT coverage exists | html/semantics/permission-element/usermedia (idlharness, setConstraints combinations, cancel-prompt, iframe, untrusted-click, attribute-handler tests); Chromium also unit-tests the element (core, modules) |
MDN has no page for the element or HTMLUserMediaElement as of 2026-07-26, which is why this reference exists. Always feature-detect ("HTMLUserMediaElement" in window) and keep meaningful fallback content inside the tag.
Security and privacy
- Trusted UI: the element renders in a closed, UA-controlled shadow DOM; sites cannot restyle it into something deceptive, and activation is filtered to trusted gestures with transient activation — the spec requires user agents to suppress clickjacking and programmatic activation at minimum by rejecting untrusted events.
- Implicit hardware activation: a grant starts real capture immediately (camera LED, mic indicator). The explainer flags this as an area under active investigation — don't treat a click on the element as a low-cost operation.
- Constraint fingerprinting: declarative constraints expose the same device-fingerprinting surface as imperative
getUserMedia(); the explainer requires the same mitigations (fuzzing exact values, withholdingdeviceIduntil grant). - Permission model: the element mediates — never bypasses — the camera/microphone permissions. It also repairs the “permission hole”: a previously-denied user clicking the element gets a browser-explained recovery flow in-page, instead of the site silently failing.
Legacy migration surface — syntax
For sites migrating from the origin-trial <permission> element, Chrome also ships an experimental legacy surface (runtime feature UserMediaElementLegacy, status experimental). Adding a type attribute switches the element into legacy mode, where it behaves like the old <permission> element:
// Chromium html_user_media_element.idl — every member below is
// [RuntimeEnabled=UserMediaElementLegacy] (experimental, not for new code):
[Reflect] attribute DOMString type; // "camera", "microphone", or "camera microphone"
static boolean isTypeSupported(DOMString type);
readonly attribute boolean isValid;
readonly attribute DOMString invalidReason;
readonly attribute PermissionState initialPermissionStatus;
readonly attribute PermissionState permissionStatus;
attribute EventHandler onpromptaction;
attribute EventHandler onpromptdismiss;
attribute EventHandler onvalidationstatuschange;
Source: Chromium html_user_media_element.idl.
Legacy migration surface — inputs
The type content attribute is the only input: a space-separated list taking camera, microphone, or both (either order). It takes effect once, when first set — later changes or removal are ignored by the implementation. Any other token, or three or more tokens, makes the element invalid: Chromium reports an “invalid type” DevTools audits issue and the element falls back to its inert fallback rendering. isTypeSupported(type) lets migration code probe a type string before stamping it into markup.
Legacy migration surface — outputs
In legacy mode the element behaves as a permission gate, not a stream broker: it exposes the permission state machine directly through isValid (the element is usable), invalidReason (why not — for example an unsupported type or a policy block), initialPermissionStatus and permissionStatus (PermissionState: "granted" / "denied" / "prompt"), and the promptaction / promptdismiss / validationstatuschange events. It does not populate stream — in legacy mode the error getter even hard-returns null, and media acquisition stays with your own getUserMedia() call.
Legacy migration surface — errors
Legacy-mode failures are reported through state, not exceptions: an invalid type flips isValid to false with a machine-readable invalidReason, and validationstatuschange fires when validity changes. Because the legacy branch bypasses the media-capture event model, the error property always reads null there — do not rely on it when type is present. Reading any legacy member without the experimental flag simply yields undefined (the members don't exist), which is itself a usable probe.
Legacy migration surface — context
The legacy members exist only under the experimental UserMediaElementLegacy runtime feature, which shares the origin-trial token name UserMediaElement so origin-trial pages keep working while they migrate. It is testable locally with chrome://flags or --enable-blink-features=UserMediaElementLegacy. The stated intent in the IDL is to deprecate and remove type once <usermedia> adoption is stable — tracked as crbug.com/493632110. Treat the entire surface as temporary migration scaffolding.
Legacy migration surface — lifecycle
The legacy branch is decided the moment the type attribute is first parsed; from then on every virtual hook (AttributeChanged, permission-status changes, embedded-permission decisions, activation) dispatches to the legacy <permission>-element code path instead of the media-capture path. There is no transition back: removing type does not restore MVP behavior, you must replace the element. initialPermissionStatus captures the state at setup; permissionStatus live-tracks grants and revocations after that.
Legacy migration surface — examples
// Migration probe (requires the experimental flag to do anything):
const el = document.querySelector("usermedia");
if ("isTypeSupported" in HTMLUserMediaElement) {
// Legacy surface present (flag on). Ask before stamping markup:
console.log(HTMLUserMediaElement.isTypeSupported("camera microphone")); // true
console.log(HTMLUserMediaElement.isTypeSupported("geolocation")); // false
}
// Legacy usage (origin-trial carry-over; do NOT use in new code):
// <usermedia type="camera microphone"></usermedia>
// el.isValid, el.permissionStatus, el.onpromptaction now behave like the
// old <permission> element; el.stream is never populated for you.
Source: Chromium html_user_media_element.idl.
Legacy migration surface — compatibility
| Engine / runtime | Support | Notes |
|---|---|---|
| Chrome 151 (default) | Not enabled | UserMediaElementLegacy is experimental; on via flag or the UserMediaElement origin-trial token |
| Firefox / Safari | Not supported | No legacy <permission>-element implementation exists outside Chromium |
| Spec status | Not specified | Absent from the Media Capture HTML elements spec section — Chromium-only migration aid |
| Future | Deprecating | IDL TODO: deprecate type once adoption stabilizes (crbug.com/493632110) |
Legacy migration surface — security and privacy
The legacy members expose fine-grained permission state (initialPermissionStatus, permissionStatus) to script — information the Permissions API already provides for camera/microphone, so no new disclosure class, but it makes permission probing marginally easier, one more reason it is flag-gated. The trust model is unchanged from the MVP surface: UA-rendered control, trusted-gesture activation, no site access to the prompt internals. Migrating off type removes this surface from your pages entirely.
Superseded draft design
The original WICG/PEPC usermedia_element.md explainer described a different shape: constraints as JSON inside a <script type="permissionconstraints"> child, an autostart attribute, a settable stream property, an enabled property with click-to-mute, and streamready / userchangedenabled events. None of that ships in Chrome 151. The implemented MVP uses setConstraints(), a read-only stream, and stream / cancel / error events; it has no autostart (the newer explainer proposes one, but it is not in the shipping IDL) and no mute-toggle behavior. If you followed early-origin-trial documentation, update your integration.
member references
streamanderrorproperties — what you read after the terminal event.setConstraints()andHTMLMediaStreamConstraints— configuring the capture.stream,cancel, anderrorevents — the acquisition lifecycle signals.
see also
- Specification — Media capture HTML elements
- Media Capture Elements explainer
- chromestatus.com — Capability Elements <usermedia> MVP (4926233538330624)
- chromestatus.com — <camera> and <microphone> elements (sibling feature, experimental)
- Interactive demo — chrome-platform-showcase
- MDN: MediaDevices.getUserMedia() (the imperative counterpart)