> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corti.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Map device buttons

> Map the buttons on a handheld dictation microphone to recording and commands over WebHID

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/d6WKOaS9_Ts?start=554&end=582" title="Map Handheld Mic Buttons — walkthrough" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen />

<Info>
  **Follow along with the runnable example.** This guide walks through the [Device buttons example](https://github.com/corticph/corti-examples/tree/main/sdk/typescript/applets/src/applets/device-buttons) in `corti-examples` — bootstrap your build from it or use it as reference.
</Info>

Handheld dictation microphones (Philips SpeechMike, SpeechOne, PowerMic, Foot Control) have physical buttons (Record, Stop, F1–F4, EOL/Prio, and more). This guide maps those buttons to recording control and commands directly in the browser over **WebHID**, with no native driver or browser extension.

<Note>
  This guide is about wiring a device's **buttons**. For choosing the right microphone hardware for audio quality, see [Recommended Microphones](/stt/microphones).
</Note>

<Warning>
  WebHID is **Chromium-only** (Chrome / Edge) and requires a secure context (`localhost` is fine). `requestDevice()` must be called from a user gesture, and granted devices reconnect automatically on later visits.
</Warning>

***

## Connect a device

Use the [`dictation_support`](https://www.npmjs.com/package/dictation_support) library, which decodes each device's raw HID reports into semantic button events. Create a `DictationDeviceManager`, subscribe to button events via `addButtonEventListener`, and call `requestDevice()` from a user gesture (a **Connect** button click). See the [device buttons example](https://github.com/corticph/corti-examples/tree/main/sdk/typescript/applets/src/applets/device-buttons) in `corti-examples` for the setup code.

***

## From button reports to actions

`dictation_support` reports a **bitmask of the buttons currently pressed** — a *level*, not an edge. Holding Record keeps its bit set across successive reports. To get discrete presses and releases, diff the previous mask against the new one.

```ts title="JavaScript" theme={null}
const rising  = (prev: number, next: number, bit: number) => (next & bit) !== 0 && (prev & bit) === 0;
const falling = (prev: number, next: number, bit: number) => (next & bit) === 0 && (prev & bit) !== 0;
```

Map each button bit to one of three actions, then resolve the effects on each transition:

```ts title="JavaScript" theme={null}
type ButtonAction =
  | { type: "toggle" }                      // press flips recording on/off
  | { type: "push" }                        // hold to record, release to stop
  | { type: "command"; commandId: string }; // fire a command on press

function computeEffects(prev: number, next: number, mappings: Record<number, ButtonAction>) {
  const effects = [];
  for (const [bit, action] of Object.entries(mappings).map(([b, a]) => [Number(b), a] as const)) {
    if (action.type === "command" && rising(prev, next, bit)) {
      effects.push({ kind: "command", commandId: action.commandId });
    } else if (action.type === "push") {
      if (rising(prev, next, bit))  effects.push({ kind: "record", op: "start" });
      if (falling(prev, next, bit)) effects.push({ kind: "record", op: "stop" });
    } else if (action.type === "toggle" && rising(prev, next, bit)) {
      effects.push({ kind: "record", op: "toggle" });
    }
  }
  return effects;
}
```

<Tip>
  Keeping this bitmask-diffing logic pure (no WebHID calls) makes it easy to unit-test button behavior without a physical device. Let users **learn** a mapping by pressing the button — capture the next-pressed bit — so only the buttons a device actually has appear in your UI.
</Tip>

***

## Drive recording and commands

Route each resolved effect to its target. Recording effects call `dictation.startRecording()`, `dictation.stopRecording()`, or `dictation.toggleRecording()` on the [Dictation Web Component](/sdk/dictation/overview); command effects dispatch client-side against your own registry, exactly as in the [Commands](/stt/guides/dictation-commands) guide.

<Note>
  The Corti API does not execute button-triggered commands — a command-mapped button fires locally against your own command handler. Only spoken command phrases are recognized server-side.
</Note>

***

## Activation model

Tie the buttons to the microphone you're actually recording with: only let them drive a surface while that surface has the **handheld device selected as its mic**. Selecting a built-in mic leaves the buttons inert; selecting the handheld activates them. This mirrors how the Web Component's keyboard push-to-talk / toggle keys drive the same `startRecording` / `stopRecording` / `toggleRecording` methods.

<Tip>
  Persist button mappings per **API client** (e.g. keyed by `clientId:tenant`) so a user's device setup follows them across sessions.
</Tip>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Commands" icon="terminal" href="/stt/guides/dictation-commands">
    Build the command handler that button-mapped commands dispatch to.
  </Card>

  <Card title="Recommended Microphones" icon="microphone" href="/stt/microphones">
    Choose the right handheld or headset mic for dictation.
  </Card>

  <Card title="Example code" icon="github" href="https://github.com/corticph/corti-examples/tree/main/sdk/typescript/applets/src/applets/device-buttons">
    The complete, runnable Device buttons example in `corti-examples`.
  </Card>
</CardGroup>

<Note>Please [contact us](mailto:help@corti.ai) for help or questions.</Note>
