v153 · html · web api · shipped
Capability elements: <camera> and <microphone>
Two declarative, user-activated HTML controls from the capability-elements (ex-PEPC) family: <camera> requests video capture only, <microphone> requests audio capture only. Like the <usermedia> MVP that shipped in Chrome 151, each embeds a browser-controlled, strictly styled button that runs the permission flow and the capture request for you, then hands the resulting media to the page — but scoped to a single capability instead of a combined camera+microphone stream.
CameraAndMicrophoneElements in runtime_enabled_features.json5 carries no status field — and per that file's own header, features without a status are not enabled anywhere by default. The stable-status flip is therefore pending at trunk (or branch-level) at fetch time: feature-detect ("HTMLCameraElement" in window) before relying on the elements in any given build. The specification is a working draft with explicitly unfinished sections (noted below).
at a glance
| Shipped in | Chrome 153 (desktop, Android) — milestone listing: “Enabled by default” (the listing is authoritative). The Intent to Ship estimated desktop + Android 152; the live listing and ship stage record 153 |
|---|---|
| Runtime feature | CameraAndMicrophoneElements (single flag gates both elements; constructors CHECK it) — see the stability note above for its current trunk status |
| Interfaces | HTMLCameraElement, HTMLMicrophoneElement — both include the shared HTMLMediaCaptureElementBase mixin |
| Spec | Media Capture and Streams Extensions — Media capture HTML elements (working draft; the ChromeStatus spec link's #the-camera-html-element fragment does not resolve in the current draft) |
| Explainer | Media Capture Elements explainer (w3c/mediacapture-extensions) |
| TAG review | w3ctag/design-reviews #1218 (shared with the <usermedia> MVP; status “issues addressed” per the intent) |
| ChromeStatus | 5153829504024576 — Capability elements: <camera> and <microphone> |
why they exist
The <usermedia> MVP (gendn reference, Chrome 151) proved the model: make the element itself the trusted control — a click on a browser-rendered button is an unambiguous user gesture, the browser runs the permission UX inline (with an in-page recovery path for previously-denied users), performs the capture request, and delivers the media. But <usermedia> always brokers a combined camera+microphone request. Many applications need exactly one: a voice recorder wants audio only, a document scanner wants video only. Requesting both wastes a permission decision and confuses users.
<camera> and <microphone> are the single-capability siblings: the same mechanism, security model, strict styling, and permission-recovery path, each scoped to one kind of capture — <camera> installs a VIDEO_CAPTURE permission descriptor, <microphone> an AUDIO_CAPTURE one. The feature continues the PEPC (Page Embedded Permission Control) lineage: generic <permission> element → <usermedia> MVP → single-capability elements.
Syntax
<!-- Declarative: no required attributes. Children are fallback content. -->
<camera>Camera access required</camera>
<microphone>Microphone access required</microphone>
<script>
const cam = document.querySelector("camera");
// Optional: configure the capture request the element will make.
cam.setConstraints({ video: { width: 1280 } });
</script>
The normative IDL from the specification (HTMLMicrophoneElement is identical except for the name):
[Exposed=Window]
interface HTMLCameraElement : HTMLElement {
[HTMLConstructor] constructor();
readonly attribute MediaStreamTrack? track;
readonly attribute DOMException? error;
undefined setConstraints(optional MediaTrackConstraintSet constraints = {});
attribute EventHandler ontrack;
attribute EventHandler onerror;
attribute EventHandler oncancel;
};
Chromium's shipping IDL diverges from the draft (see spec vs shipping). The shipping surface, composed from html_camera_element.idl / html_microphone_element.idl plus the shared HTMLMediaCaptureElementBase mixin and its two modules/mediastream partials (stream, setConstraints):
[Exposed=Window, RuntimeEnabled=CameraAndMicrophoneElements, HTMLConstructor]
interface HTMLCameraElement : HTMLElement {};
HTMLCameraElement includes HTMLMediaCaptureElementBase;
// (same for HTMLMicrophoneElement)
interface mixin HTMLMediaCaptureElementBase {
readonly attribute DOMException? error;
attribute EventHandler onstream; // NOT "ontrack" (divergence)
attribute EventHandler oncancel;
attribute EventHandler onerror;
};
// partial mixins applying to all three capability elements:
partial interface mixin HTMLMediaCaptureElementBase {
readonly attribute MediaStream? stream; // NOT "track" (divergence)
undefined setConstraints(optional HTMLMediaStreamConstraints constraints = {});
};
dictionary HTMLMediaStreamConstraints {
MediaTrackConstraintSet video;
MediaTrackConstraintSet audio;
};
Content model: both elements belong to the flow, phrasing, interactive, and palpable content categories; they 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 underlying track state (requested, live, muted, stopped). Both constructors CHECK the CameraAndMicrophoneElements runtime feature.
Inputs
Neither element takes required content attributes — <camera></camera> alone requests video with default settings, <microphone></microphone> audio. Three input channels exist:
- Constraints, set imperatively via
setConstraints()— in Chromium anHTMLMediaStreamConstraintsdictionary with optionalvideoandaudiomembers (the draft specifies a singleMediaTrackConstraintSetinstead). If never called, the browser requests with empty default constraints for the element's kind. The contract has three non-obvious rules — first-call-wins, member backfill, and a bare-scalar sanitizer — documented on the member page. - Fallback content: child nodes render 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 are no capability-specific content attributes on the shipping surface.
Outputs
Three observable outputs:
stream— aMediaStreamwhose track set contains the single captured track (videokind for<camera>,audiofor<microphone>), assigned when acquisition succeeds;nullbefore that. Note this is aMediaStream, not the draft'sMediaStreamTrack.- 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 capture state without any page code.
Source: Chromium user_media_request_provider_impl.cc — OnSuccess/OnError; Chromium html_media_capture_element_base.cc; Specification — event 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 Chromium's implementation (shared with <usermedia>):
| Cause | Event | error value |
|---|---|---|
| User dismisses or denies the permission prompt | cancel | NotAllowedError (“Permission dismissed” / “Permission denied”) |
| Activation without transient user activation | error | InvalidStateError (“The permission element activation must be triggered by a user gesture.”; a DevTools audits issue is also reported) |
| Stream creation throws during request setup | error | OperationError (“Stream creation failed”) |
| Underlying capture request rejects (device busy, no device, …) | error | The rejection DOMException (e.g. NotFoundError, NotReadableError, NotAllowedError, OverconstrainedError) |
| Constraints missing the element's kind (defensive branch) | error | NotSupportedError (“No video constraints set” / “No audio constraints set”) — present in user_media_request_provider_impl.cc but not reachable through the current JS surface, because setConstraints() backfills both members and the no-call path installs defaults |
| Non-secure context | none | The element renders its fallback content; Chromium 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 — a failed attempt re-arms the element. The draft specification's error-description section is explicitly marked “Description still TBD”, so treat the table above as the shipping-contract source of truth.
Context
Exposure: both interfaces are [Exposed=Window] — no worker exposure. Like getUserMedia(), the elements only operate in a secure context; elsewhere they show fallback content. Feature-detect with "HTMLCameraElement" in window / "HTMLMicrophoneElement" in window — on unsupported engines the tags parse as unknown elements and your fallback content renders.
Activation requirements: acquisition starts only from a genuine user gesture — the click must carry transient user activation; programmatic or untrusted activation is rejected (a DevTools audits issue, then an error event with InvalidStateError where the activation path reaches the element). 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: each element installs exactly one permission descriptor — VIDEO_CAPTURE for <camera>, AUDIO_CAPTURE for <microphone> — and the capture request is built with only the matching kind, even though the constraints dictionary has both members.
Platform scope: the Intent to Ship answers “No” to support on all six Blink platforms without naming the excluded set; the same mechanism's <usermedia> MVP excludes Android WebView (it requires permission-manager support). Treat WebView availability as an explicit unknown.
Lifecycle
Each 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 the capture request with the configured constraints for the element's kind. - Terminal event — exactly one of
stream/cancel/errorfires per attempt (queued as a DOM-manipulation task). On success thestreamsupplement is populated before thestreamevent fires. - Active state — while the associated
MediaStreamis active, further clicks do not start a new request (the provider returns early when an active stream exists). The elements do not become mute/unmute toggles in the shipping implementation — the draft spec likewise says activation with an existing track “currently ignores activation … The behaviour for an element with a track still needs to be determined”. Track muting stays with your own UI orMediaStreamTrack.enabled. - Reset — once the stream is no longer active (including external
track.stop()), the next click starts a fresh acquisition. A failed attempt also re-arms the element immediately.
Examples
<camera id="cam">This page needs your camera.</camera>
<microphone id="mic">This page needs your microphone.</microphone>
<video id="preview" autoplay playsinline muted></video>
<script>
const cam = document.getElementById("cam");
const mic = document.getElementById("mic");
const video = document.getElementById("preview");
// Optional configuration; call before the user interacts.
// First call wins — later calls are ignored (see setConstraints reference).
cam.setConstraints({ video: { width: 1280, facingMode: "environment" } });
mic.setConstraints({ audio: { echoCancellation: true, noiseSuppression: true } });
cam.addEventListener("stream", () => {
// Success: cam.stream is a live MediaStream with one video track.
video.srcObject = cam.stream;
});
mic.addEventListener("stream", () => {
// mic.stream is a live MediaStream with one audio track.
});
for (const el of [cam, mic]) {
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. The Chrome Platform Showcase has no demo for this feature yet (route checked 404 on 2026-07-28), so there is no embedded live example; the sibling <usermedia> demo exercises the same underlying mechanism.
Compatibility
| Engine / runtime | Support | Notes |
|---|---|---|
| Chrome / Edge (desktop) | 153 (listing) | Milestone listing: Enabled by default. Caveat: trunk CameraAndMicrophoneElements carries no status: stable as of 2026-07-28 — flip pending; feature-detect before relying |
| Chrome (Android) | 153 (listing) | Per the ship stage (desktop 153, Android 153). Android WebView: explicit unknown — the intent answers “No” to all-six-platforms support without naming exclusions |
| Firefox | Not supported | Vendor position “No signal” (ChromeStatus record) |
| Safari | Not supported | Vendor position “No signal” (ChromeStatus record) |
| BCD | No entry | No api/HTMLCameraElement.json or api/HTMLMicrophoneElement.json in browser-compat-data as of 2026-07-28 — compat data above is interim, from the chromestatus listing |
| WPT | None found upstream | Zero html/semantics/permission-element/ camera/microphone tests in the public WPT tree (Chrome 151 stable run summary, fetched 2026-07-28; the sibling usermedia suite has 13 files). This contradicts the intent's “fully tested by web-platform-tests: Yes” answer — flagged for re-check |
MDN has no page for either element or interface as of 2026-07-28 (HTMLCameraElement and HTMLMicrophoneElement both 404), which is why this reference exists. Always feature-detect and keep meaningful fallback content inside the tags.
Security and privacy
- Trusted UI: the elements render in a UA-controlled presentation the site cannot restyle 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). A click on the element is not a low-cost operation.
- Single-capability scoping: each element can only ever request its own kind —
<camera>cannot produce an audio track and vice versa — so the permission prompt and the delivered stream match the control's label. This removes the over-requesting risk of a combined control used for a single purpose. - Constraint fingerprinting: declarative constraints expose the same device-fingerprinting surface as imperative
getUserMedia(); the same mitigations apply (the sanitizer also drops object-form constraints, see the setConstraints reference). - Permission model: the elements mediate — never bypass — the camera/microphone permissions, and repair the “permission hole”: a previously-denied user clicking the element gets a browser-explained recovery flow in-page.
spec vs shipping — known divergences
The working-draft specification and Chromium's shipping IDL currently disagree on the surface shape. The shipping contract (what your code actually sees in Chrome) wins for implementation; the draft may converge later:
| Surface | Draft spec | Shipping Chromium |
|---|---|---|
| Media delivery | track attribute (MediaStreamTrack?) | stream attribute (MediaStream?) — the single track is inside the stream (partial mixin IDL) |
| Success event | track event / ontrack | stream event / onstream (mixin IDL; dispatched in provider OnSuccess) |
| Constraints parameter | setConstraints(optional MediaTrackConstraintSet constraints = {}) | setConstraints(optional HTMLMediaStreamConstraints constraints = {}) — the two-member {video, audio} dictionary shared with <usermedia> (constraints IDL) |
| Click with active media | “currently ignores activation … behaviour … still needs to be determined” | Also ignored (early return on active stream) — no mute toggle ships |
| Error descriptions | “Description still TBD” | Exact DOMException mapping in the errors table |
member references
streamanderrorproperties — what you read after the terminal event.setConstraints()andHTMLMediaStreamConstraints— first-call-wins, backfill, and the bare-scalar sanitizer.stream,cancel, anderrorevents — the acquisition lifecycle signals.
see also
- gendn — Capability Elements <usermedia> MVP (Chrome 151) — the combined-stream sibling sharing the same mechanism
- Specification — Media capture HTML elements
- Media Capture Elements explainer
- blink-dev — Intent to Ship: Capability elements <camera> and <microphone>
- chromestatus.com — Capability elements: <camera> and <microphone> (5153829504024576)
- chromestatus.com — Capability Elements <usermedia> MVP (4926233538330624)
- TAG design review #1218
- MDN: MediaDevices.getUserMedia() (the imperative counterpart)