Mic permission gatePro
Asks for the microphone before the browser does, points at the prompt while it is up, and when the answer is no shows the exact steps for the user's browser or system. It opens by itself once the site setting flips to Allow.
On this page
Installation
Usage
import { MicPermissionGate } from "@/components/ui/chatcn/pro/mic-permission-gate"
<MicPermissionGate onSkip={() => setMode("text")}>
<VoiceSession {...voice} />
</MicPermissionGate>Leave status out and the gate runs itself: it reads the site permission, asks on Allow, closes the probe stream at once, and maps the answer to a state. It follows the Permissions API change event, so a user who unblocks the microphone from the address bar sees the gate open without a reload, and it retries by itself when a microphone is plugged in. Pass status and onRequest to drive it from your own call setup.
Recovery steps are written per browser (Chrome, Edge, Safari, Firefox and a generic fallback) and per platform, including phones, with the menu path shown as breadcrumbs. When the system is the one blocking (the browser allowed it but the operating system did not), the steps point at the system settings instead. The browser is detected from the user agent, and a small select switches it for people helping someone else.
Examples
Every state
Inline above a composer
Props
| Prop | Type | Notes |
|---|---|---|
status | "prompt" | "requesting" | "granted" | "denied" | "system-blocked" | "not-found" | "in-use" | "unsupported" | Controlled state. Leave undefined to let the gate run itself |
onStatusChange | (status) => void | Every change, including the live permission flip |
onRequest | () => Promise<status | undefined> | status | undefined | Your own request. Default opens and closes a probe stream |
variant | "card" | "inline" | Default card |
title, description | ReactNode | The ask: what the microphone is for |
privacyNote | ReactNode | Quiet line under the ask. null hides it |
allowLabel | string | Default "Allow microphone" |
onSkip, skipLabel | () => void, string | A way forward without voice. Default "Type instead" |
browser | "chrome" | "edge" | "safari" | "firefox" | "other" | Override detection |
platform | "mac" | "windows" | "linux" | "ios" | "android" | "other" | Override detection |
defaultStepsOpen | boolean | Inline only: start with the steps open |
children | ReactNode | Rendered once allowed. Without it, a short confirmation |
Keyboard
| Key | Action |
|---|---|
Tab | Moves through Allow, Type instead, the browser select and Try again |
Enter, Space | Activates the focused button, opens the browser select |
Arrow keys | Move through browsers in the open select |
Accessibility
- The card is a labelled
section; each state change is announced once through a polite live region, never on first render. - While the browser prompt is up the Allow button stays focusable with
aria-disabled, so focus never drops to the page when the state changes. - Steps are an ordered list. Menu paths read as "Settings then Privacy and security", with the chevrons hidden.
- Blocked uses the red token, busy and missing the amber one, always with a crossed microphone glyph and the title as the backup.
- The inline How to fix toggle sets
aria-expandedandaria-controls.
Motion
- The state glyph swaps with a 150 ms blur crossfade; recovery steps rise in over 180 ms.
- While the prompt is up, the arrow nudges toward the address bar three times and stops. It never moves under reduced motion.
- Nothing animates when the inline steps are toggled from the keyboard; they appear in place.