![Thomas Steiner](https://web.dev/images/authors/thomassteiner.jpg) Thomas Steiner [GitHub](https://github.com/tomayac) [LinkedIn](https://www.linkedin.com/in/thomassteinerlinkedin) [Mastodon](https://toot.cafe/@tomayac) [Bluesky](https://bsky.app/profile/tomayac.com) [Homepage](https://blog.tomayac.com/)

<br />

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.

> [!TIP]
> **Success:** From Chrome 144 you can use the new [`<geolocation>` HTML element](https://developer.chrome.com/blog/geolocation-html-element.md.txt). This element represents a major shift in how sites request user location data---moving away from script-triggered permission prompts toward a declarative, user-action-oriented experience. It reduces the boilerplate code required to handle permission states and errors, and provides a stronger signal of user intent, which helps avoid browser interventions (like quiet blocks).

## Imperative methods for requesting permission

When web apps need access to
[powerful features](https://www.w3.org/TR/permissions/#powerful-features), they
need to ask for permission. For example, when
[Google Maps](https://maps.google.com/) requires the user's location using the
[Geolocation API](https://developers.google.com/maps/documentation/geolocation/overview),
browsers will prompt the user, often with the option to store that decision.
This is a
[well defined concept](https://w3c.github.io/permissions/#requesting-more-permission)
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()`](https://developer.mozilla.org/docs/Web/API/Geolocation/getCurrentPosition)
method, the permissions prompt automatically pops up upon the first call.
Another example is
[`navigator.mediaDevices.getUserMedia()`](https://developer.mozilla.org/es/docs/Web/API/MediaDevices/getUserMedia).

Other APIs, like the
[Notification API](https://developer.mozilla.org/docs/Web/API/Notification) or
the
[Device Orientation and Motion API](https://developer.mozilla.org/docs/Web/API/Device_orientation_events/Detecting_device_orientation),
commonly have an explicit way to request permission through a static method like
[`Notification.requestPermission()`](https://developer.mozilla.org/docs/Web/API/Notification/requestPermission_static)
or
[`DeviceMotionEvent.requestPermission()`](https://developer.mozilla.org/docs/Web/API/DeviceMotionEvent).

> [!NOTE]
> **Note:** Historically, there were plans for yet another way to get permission, namely an explicit `navigator.permissions.request()` method, but it was removed from the Permissions API following the discussion in [Issue #83](https://github.com/w3c/permissions/issues/83) in the specification's repository. The question of the "right" approach between these methods (implicitly prompt upon use and explicitly request permission) is a much discussed topic without a clear answer.

## 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.](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--tqnca8pwh2j.png)

### 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](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--z86xo6snv3.png)

### Permission contextualization

Another challenge, especially on big screens, is the way the permission prompt
gets commonly displayed: above the
[line of death](https://textslashplain.com/2017/01/14/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.](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--5hc72fet2n6.png)

### 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.](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--qqvxct5tzs.png)

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.](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--6q6trb1ksxu.png)

## 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:

```html
<permission type="camera" />
```

> [!NOTE]
> **Note:** [Page Embedded Permission Control](https://github.com/WICG/PEPC/blob/main/explainer.md) (PEPC, pronounced "PEP-C") is the name of the larger holistic program to embed more controls into the content area. This is why you occasionally may see people refer to `<permission>` as PEPC.

It's still being [actively debated](https://github.com/WICG/PEPC/issues/18)
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`](https://github.com/WICG/PEPC/blob/main/explainer.md#:%7E:text=PEPC%20attributes-,type,-Used%20to%20specify)
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.](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--j1v5jdughdp.png)

### The `type-ext` attribute

For some permissions that allow for additional parameters, the
[`type-ext`](https://github.com/WICG/PEPC/blob/main/explainer.md#:~:text=commonly%20grouped%20together)-,type-ext,-Allows%20specifying%20additional)
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`](https://github.com/WICG/PEPC/blob/main/explainer.md#:%7E:text=As%20discussed%20above.-,lang,-The%20global%20lang)
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](https://developer.chrome.com/blog/permission-element-origin-trial.md.txt#feedback)!

> [!NOTE]
> **Note:** During the origin trial, the `lang` attribute won't be supported. Instead, the language the browser is set to will be used.

## 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.](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--d7s5i67i3u9.png)
- If they had allowed the feature before, they can continue allowing it, or stop
  allowing it.

  ![Permission prompt to continue allowing or stop allowing.](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--4yyra8y8m5m.png)
- 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.](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--ji4c3lp59.png)

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](https://developer.chrome.com/static/blog/permission-element-origin-trial/pepcarticle--qxdtldehi2.png)

## 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](https://developer.chrome.com/blog/permission-element-origin-trial.md.txt#feedback) 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.

> [!NOTE]
> **Note:** During the origin trial, the `:invalid` pseudo-class won't be supported.

```css
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](https://developer.mozilla.org/docs/Web/API/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).

  > [!NOTE]
  > **Note:** During the origin trial, this will be called `ondismiss`, but the final name will be `onpromptdismiss`. You can feature-detect the supported event name using, for example, `'ondismiss' in
  > HTMLPermissionElement.prototype`.

- `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).

  > [!NOTE]
  > **Note:** During the origin trial, this will be called `onresolve`, but the final name will be `onpromptaction`. You can feature-detect the supported event name using, for example, `'onresolve' in
  > HTMLPermissionElement.prototype`.

- `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.

> [!NOTE]
> **Note:** During the origin trial, this event won't be fired.

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.

```html
<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>`.

```javascript
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](https://developer.chrome.com/origintrials#/view_trial/4531184175088140289).
Read [Get started with origin trials](https://developer.chrome.com/docs/web-platform/origin-trials.md.txt) 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

Explore the [demo](https://permission.site/pepc.html) and check out the [source code](https://github.com/chromium/permission.site/blob/master/pepc.html) on GitHub. Here's a screenshot of the experience on a supporting browser.

![Demo of the permission element showing three permission buttons.](https://developer.chrome.com/static/blog/permission-element-origin-trial/demo.png)

## 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](https://github.com/WICG/PEPC/issues), or file a new
one. Public signals in the [repo](https://github.com/WICG/PEPC/) 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](https://github.com/WICG/PEPC/) 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](https://github.com/w3c/tpac2023-breakouts/issues/35). You can read the [public session notes](https://docs.google.com/document/d/1pJFCADgasiqojESz2VePkVwaDdlfDqpN5JSqo4T4mMs/edit?usp=sharing). The Chrome team has also asked for formal Standards Positions from both vendors, see the [Related links](https://developer.chrome.com/blog/permission-element-origin-trial.md.txt#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](https://github.com/WICG/PEPC/issues/18) whether `<permission>` should be a void element or not. If you have feedback, chime in on the Issue.

## Related links

- [Explainer](https://github.com/WICG/PEPC/blob/main/explainer.md)
- [WebKit Standards Position](https://github.com/WebKit/standards-positions/issues/270)
- [Mozilla Standards Position](https://github.com/mozilla/standards-positions/issues/908)

## Acknowledgements

This document was reviewed by
[Balázs Engedy](https://de.linkedin.com/in/bal%C3%A1zs-engedy-2b237753),
[Thomas Nguyen](https://www.linkedin.com/in/thomas-nguyen-01482621/),
[Penelope McLachlan](https://www.linkedin.com/in/pjmclachlan/),
[Marian Harbach](https://twitter.com/harbachmarian),
[David Warren](https://www.linkedin.com/in/david-warren-a5447b8/), and
[Rachel Andrew](https://rachelandrew.co.uk/).