An origin trial for a new HTML <permission> element

There are a number of imperative methods for asking for permission to use powerful features like location access in web apps. These methods come with a number of challenges, which is why the Chrome permissions team is experimenting with a new declarative method: a dedicated HTML <permission> element. This element is in origin trial from Chrome 126, and ultimately we hope to standardize it.

Imperative methods for requesting permission

When web apps need access to powerful features, they need to ask for permission. For example, when Google Maps requires the user's location using the Geolocation API, browsers will prompt the user, often with the option to store that decision. This is a well defined concept in the Permissions specification.

Implicitly ask on first use versus explicitly request upfront

The Geolocation API is a powerful API and relies on the implicitly ask on first use approach. For example, when an app calls the navigator.geolocation.getCurrentPosition() method, the permissions prompt automatically pops up upon the first call. Another example is navigator.mediaDevices.getUserMedia().

Other APIs, like the Notification API or the Device Orientation and Motion API, commonly have an explicit way to request permission through a static method like Notification.requestPermission() or DeviceMotionEvent.requestPermission().

Challenges with imperative methods for asking for permission

Permission spam

In the past, websites could call methods like navigator.mediaDevices.getUserMedia() or Notification.requestPermission(), but also navigator.geolocation.getCurrentPosition() immediately when a website was loaded. A permission prompt would pop up before the user had interacted with the website. This is sometimes described as permission spam and affects both approaches, implicitly asking on first use as well as explicitly requesting upfront.

Microphone permission prompt shown when loading a website.

Browser mitigations and user gesture requirement

Permission spam led to browser vendors requiring a user gesture like a button click or a keydown event before showing a permission prompt. The problem with this approach is that it's very difficult, if not impossible, for the browser to figure out if a given user gesture should result in a permission prompt to be shown or not. Maybe the user was just clicking on the page in frustration anywhere because the page took so long to load, or maybe they were indeed clicking on the Locate me button. Some websites also became very good at tricking users into clicking on content to trigger the prompt.

Another mitigation is adding prompt abuse mitigations, like completely blocking features to begin with, or to showing the permission prompt in a non-modal, less intrusive manner.

Chrome browser showing a

Permission contextualization

Another challenge, especially on big screens, is the way the permission prompt gets commonly displayed: above the line of death, that is, outside of the area of the browser window that the app can draw onto. It's not unheard of that users would miss the prompt at the top of their browser window when they just clicked a button at the bottom of the window. This problem is often exacerbated when browser spam mitigations are in place.

Google Maps with the location permission prompt open. The location access button that triggered the prompt is far away.

No easy undo

Finally, it is too easy for users to navigate themselves into a dead-end. For example, once the user has blocked access to a feature, it requires them to be aware of the site information drop-down where they can either Reset permissions or toggle blocked permissions back on. Both options in the worst case require a full reload of the page until the updated setting takes effect. Sites have no ability to provide an easy shortcut for users to change an existing permission state and have to painstakingly explain to users how to change their settings as shown at the bottom of the following Google Maps screenshot.

Chrome site controls on Google Maps to revoke permissions.

If the permission is key to the experience, for example, microphone access for a video conferencing application, apps like Google Meet show intrusive dialogs that instruct the user how to unblock the permission.

Google Meet instructions on how to open Chrome site controls.

A declarative <permission> element

To address the challenges described in this post, the Chrome permissions team have launched an origin trial for a new HTML element, <permission>. This element lets developers declaratively ask for permission to use, for now, a subset of the powerful features available to websites. In its simplest form, you use it as in the following example:

<permission type="camera" />

It's still being actively debated whether <permission> should be a void element or not. A void element is a self-closing element in HTML that cannot have any child nodes, which, in HTML, means it may not have an end tag.

The type attribute

The type attribute contains a space-separated list of permissions you are requesting. At the time of this writing, the allowed values are 'camera', 'microphone', and camera microphone (separated by space). This element by default renders similar to buttons with barebones user agent styling.

Various permission element buttons with camera, microphone, and camera plus microphone permissions.

The type-ext attribute

For some permissions that allow for additional parameters, the type-ext attribute accepts space-separated key-value pairs, like, for example, precise:true for the geolocation permission.

The lang attribute

The button text is provided by the browser and meant to be consistent, so it cannot be directly customized. The browser changes the language of the text based on the inherited language of the document or the parent element chain, or an optional lang attribute. This means that developers don't need to localize the <permission> element themselves. If the <permission> element proceeds beyond the origin trial stage, several strings or icons may be supported for each permission type to increase the flexibility. If you're interested in using the <permission> element and need a specific string or icon, get in touch!

Behavior

When the user interacts with the <permission> element, they can cycle through various stages:

  • If they hadn't allowed a feature before, they can allow it on every visit, or allow it for the current visit.

    Permission prompt to allow a feature this time or on every visit.

  • If they had allowed the feature before, they can continue allowing it, or stop allowing it.

    Permission prompt to continue allowing or stop allowing.

  • If they had disallowed a feature before, they can continue not allowing it, or allow it this time.

    Permission prompt to continue not allowing or allow this time.

The text of the <permission> element automatically updates based on the status. For example, if permission was granted to use a feature, the text changes to say the feature is allowed. If permission first needs to be granted, the text changes to invite the user to use the feature. Compare the earlier screenshot with the following screenshot to see the two states.

Permission buttons with the texts

CSS design

To ensure users can easily recognize the button as a surface to access powerful capabilities, the <permission> element's styling is restricted. If the styling restrictions don't work for your use case, we'd love to hear about how and why! While not all styling needs can be accommodated, we are hoping to discover safe ways to allow more styling of the <permission> element after the origin trial. The following table details some properties that have restrictions or special rules applied to them. In case any of the rules are violated, the <permission> element will be disabled and can't be interacted with. Any attempts to interact with it will result in exceptions that can be caught with JavaScript. The error message will contain more details on the detected violation.

Property Rules

color, background-color

Can be used to set the text and background color, respectively. The contrast between the two colors needs to be sufficient for clearly legible text (contrast ratio of at least 3). The alpha channel has to be 1.

font-size, zoom

Must be set within the equivalent of small and xxxlarge. The element will be disabled otherwise. Zoom will be taken into account when computing font-size.

outline-offset

Negative values will be corrected to 0.
margin (all) Negative values will be corrected to 0.

font-weight

Values under 200 will be corrected to 200.

font-style

Values other than normal and italic will be corrected to normal.

word-spacing

Values over 0.5em will be corrected to 0.5em. Values under 0 will be corrected to 0.

display

Values other than inline-block and none will be corrected to inline-block.

letter-spacing

Values over 0.2em will be corrected to 0.2em. Values under -0.05em will be corrected to -0.05em.

min-height

Will have a default value of 1em. If provided, the maximum computed value between the default and the provided values will be considered.

max-height

Will have a default value of 3em. If provided, the minimum computed value between the default and the provided values will be considered.

min-width

Will have a default value of fit-content. If provided, the maximum computed value between the default and the provided values will be considered.

max-width

Will have a default value of three times fit-content. If provided, the minimum computed value between the default and the provided values will be considered.

padding-top

Will only take effect if height is set to auto. In this case, values over 1em will be corrected to 1em and padding-bottom will be set to the value of padding-top.

padding-left

Will only take effect if width is set to auto. In this case, values over 5em will be corrected to 5em and padding-right will be set to the value of padding-left.

transform

Distorting visual effects won't be allowed. For now, we only accept 2D translation and proportional up-scaling.

The following CSS properties can be used as normal:

  • font-kerning
  • font-optical-sizing
  • font-stretch
  • font-synthesis-weight
  • font-synthesis-style
  • font-synthesis-small-caps
  • font-feature-settings
  • forced-color-adjust
  • text-rendering
  • align-self
  • anchor-name aspect-ratio
  • border (and all border-* properties)
  • clear
  • color-scheme
  • contain
  • contain-intrinsic-width
  • contain-intrinsic-height
  • container-name
  • container-type
  • counter-*
  • flex-*
  • float
  • height
  • isolation
  • justify-self
  • left
  • order
  • orphans
  • outline-* (with the exception noted before for outline-offset)
  • overflow-anchor
  • overscroll-behavior-*
  • page
  • position
  • position-anchor
  • content-visibility
  • right
  • scroll-margin-*
  • scroll-padding-*
  • text-spacing-trim
  • top
  • visibility
  • x
  • y
  • ruby-position
  • user-select
  • width
  • will-change
  • z-index

Additionally, all logically equivalent properties can be used (for example, inline-size is equivalent to width), following the same rules as their equivalent.

Pseudo-classes

There are two special pseudo-classes that allow for styling the <permission> element based on the state:

  • :granted: The :granted pseudo-class allows for special styling when a permission was granted.
  • :invalid: The :invalid pseudo-class allows for special styling when the element is in an invalid state, for example, when it is served in a cross-origin iframe.
permission {
  background-color: green;
}

permission:granted {
  background-color: light-green;
}

/* Not supported during the origin trial. */
permission:invalid {
  background-color: gray;
}

JavaScript events

The <permission> element is meant to be used together with the Permissions API. There are a number of events that can be listened for:

  • onpromptdismiss: This event is fired when the permission prompt triggered by the element has been dismissed by the user (for example by clicking the close button or clicking outside the prompt).

  • onpromptaction: This event is fired when the permission prompt triggered by the element has been resolved by the user taking some action on the prompt itself. This doesn't necessarily mean the permission state has changed, the user might have taken an action that maintains the status quo (such as continuing to allow a permission).

  • onvalidationstatuschange: This event is fired when the element switches from being "valid" to "invalid". The element is considered "valid" when the browser trusts the integrity of the signal if the user were to click it, and "invalid" otherwise, for example, when the element is partly occluded by other HTML content.

You can register event listeners for these events directly inline in the HTML code (<permission type="…" onpromptdismiss="alert('The prompt was dismissed');" />), or using addEventListener() on the <permission> element, as shown in the following example.

<permission type="camera" />
<script>
  const permission = document.querySelector('permission');
  permission.addEventListener('promptdismiss', showCameraWarning);

  function showCameraWarning() {
    // Show warning that the app isn't fully usable
    // unless the camera permission is granted.
  }

  const permissionStatus = await navigator.permissions.query({name: "camera"});
  
  permissionStatus.addEventListener('change', () => {
    // Run the check when the status changes.
    if (permissionStatus.state === "granted") {
      useCamera();
    }
  });

  // Run the initial check.
  if (permissionStatus.state === "granted") {
    useCamera();
  }
</script>

Feature detection

If a browser doesn't support an HTML element, it won't show it. This means that if you have the <permission> element in your HTML code, nothing happens if the browser doesn't know it. You may still want to detect support using JavaScript, for example, to create a permission prompt triggered through a click of a regular <button>.

if ('HTMLPermissionElement' in window) {
  // The `<permission>` element is supported.
}

Origin trial

To try the <permission> element on your site with real users, sign up for the origin trial. Read Get started with origin trials for instructions on how to prepare your site to use origin trials. The origin trial will run from Chrome 126 to 131 (February 19, 2025).

Demo

Feedback

We would love to hear from you how <permission> works for your use case. Feel free to respond to one of the Issues in the repository, or file a new one. Public signals in the repo for the <permission> element will let us and other browsers know you're interested in it.

FAQ

  • How is this better than a regular <button> paired with the Permissions API? A click of a <button> is a user gesture, but browsers have no way of verifying that it's connected to the request for asking for permission. If the user has clicked a <permission>, the browser can be sure that the click is related to a permission request. This allows the browser to facilitate flows that otherwise would be a lot more risky. For example, allowing the user to easily undo the blocking of a permission.
  • What if other browsers don't support the <permission> element? The <permission> element can be used as a progressive enhancement. On non-supporting browsers, a classic permission flow can be used. For example, based on the click of a regular <button>. The permissions team are also working on a polyfill. Star the GitHub repo to be notified when it's ready.
  • Was this discussed with other browser vendors? The <permission> element was actively discussed at W3C TPAC in 2023 in a breakout session. You can read the public session notes. The Chrome team has also asked for formal Standards Positions from both vendors, see the Related links section. The <permission> element is an ongoing topic of discussions with other browsers and we're hoping to standardize it.
  • Should this actually be a void element? It's still being actively debated whether <permission> should be a void element or not. If you have feedback, chime in on the Issue.

Acknowledgements

This document was reviewed by Balázs Engedy, Thomas Nguyen, Penelope McLachlan, Marian Harbach, David Warren, and Rachel Andrew.