Permission Pre-Ask

Explains why a permission is needed before the browser's one-shot prompt, and shows this browser's re-enable steps if it is blocked.

StopsWasted the browser's one permission prompt

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/permission-pre-ask.json

The files land in your project. They're yours to edit.

Usage

permission-pre-ask-usage.tsx
import { PermissionPreAsk, type PermissionResult } from "@/components/ui/permission-pre-ask"

<PermissionPreAsk
  permission="notifications"
  reason="Only when your order ships or a delivery is running late. No offers, and you can turn it off anytime."
  query={prompt}
  request={request}
  browser="chrome"
/>

What it catches

  • Spending the browser prompt before the user knows why
  • Closed prompt vs permanent block
  • Browser-specific steps to turn a blocked permission back on (iOS uses Settings)
  • Permission granted later from settings: detected

Props

PermissionPreAsk props
PropTypeDefault
permission* (required)

Which browser permission this asks for.

PermissionKind—
reason* (required)

Why you need it and what for, in the user's terms. Shown before the browser prompt.

React.ReactNode—
title

Heading of the pre-ask.

stringTITLES[permission]
onGranted

Called once the permission is granted, whether just now or later from browser settings.

() => void—
onDecline

Called when the user picks "Not now"; the browser prompt has not been used up.

() => void—
query

Override how the state is read (tests, native wrappers).

(permission: PermissionKind) => Promise<PermissionResult>queryPermission
request

Override how the real prompt is shown.

(permission: PermissionKind) => Promise<PermissionResult>requestPermission
browser

Browser whose re-enable steps to show; detected when omitted.

BrowserName—
allowLabel

Button text to ask the browser.

string"Turn on"
laterLabel

Button text to decline for now.

string"Not now"
waitingText

Shown while the browser prompt is open.

string"Choose Allow in your browser's prompt, near the address …
dismissedText

Shown when the prompt was closed without a choice.

string"You closed the browser's prompt without choosing."
blockedTitle

`{noun}` and `{is}` (is/are) are filled in.

string"{noun} {is} blocked for this site"
recheckLabel

Button text to check again after changing settings.

string"I've turned it on"
askAgainLabel

Button text to ask again after a dismissal.

string"Ask again"

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Container uses role=status; the blocked panel is a labelled region
  • Icons are aria-hidden
  • Allow and Not now are buttons

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • The Permissions API doesn't report every permission in every browser
  • Re-enable steps are written for major browsers and can drift as they change
  • Can't re-show the browser prompt once blocked

What it stops

Wasted the browser's one permission prompt

Also known as

permissions · notifications · camera · microphone · location · geolocation · push · priming

Dependencies

Pairs well with