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

# Control your application by voice

> ‘Drive your app’s UI (tabs, panels, buttons, dialogs) with voice commands, gated by what’s actionable on screen’

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/d6WKOaS9_Ts?start=402&end=476" title="Control Your Application by Voice — 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 [App control example](https://github.com/corticph/corti-examples/tree/main/sdk/typescript/applets/src/applets/app-control) in `corti-examples` — bootstrap your build from it or use it as reference.
</Info>

The [Commands](/stt/guides/dictation-commands) guide dispatches recognized commands to **text** actions in an editor. App control applies the same declare → event → dispatch loop to the **application itself** — switching tabs, opening panels, clicking buttons, confirming dialogs.

The reusable idea is an **app-control registry**: the application-layer analog of the [`EditorAdapter`](/stt/guides/dictation-text-insertion#write-insertion-logic-once-with-an-adapter). Your app registers its actionable UI as named controls; command dispatch resolves a spoken target to a control and runs it — gated by whether that control is available right now.

<Note>
  This guide assumes you've read [Commands](/stt/guides/dictation-commands) — declaring `commands` in the config and handling the `command` event work exactly the same here.
</Note>

***

## Register your app's controls

Each actionable piece of UI registers itself as a control with a spoken `label`, a `kind`, a `run` effect, and optionally `isAvailable()` (for contextual commands) and `getState()` (for app awareness).

```ts title="JavaScript" theme={null}
// Tab navigation.
registry.register({
  id: "tab-notes",
  label: "notes",
  kind: "navigation",
  run: () => setTab("notes"),
  getState: () => (currentTab === "notes" ? "active" : null),
});

// A collapsible panel (arg carries "open"/"close").
registry.register({
  id: "details",
  label: "details",
  kind: "toggle",
  run: (arg) => setDetailsOpen(arg === "open"),
  getState: () => (detailsOpen ? "open" : "closed"),
});

// A dialog action that only exists while the dialog is open.
registry.register({
  id: "confirm",
  label: "confirm",
  kind: "action",
  isAvailable: () => dialogOpen,
  run: () => acceptDialog(),
});
```

The registry resolves a spoken target to a control by matching its label, aliases, or id — stripping filler words like *the*, *panel*, *button* so "open the details panel" resolves to the `details` control.

***

## Declare the commands

Declare the command set as usual. Use `enum` variables for finite target sets (tabs, panels) and **verb-led full phrases** for buttons — they recognize more reliably than a generic `click {button}` slot and are more natural to speak.

```ts title="JavaScript" theme={null}
const APP_COMMANDS = [
  {
    id: "switch_tab",
    phrases: ["go to {tab}", "switch to {tab}", "open {tab} tab"],
    variables: [{ key: "tab", type: "enum", enum: ["overview", "orders", "notes"] }],
  },
  {
    id: "open_panel",
    phrases: ["open {panel}", "show {panel}"],
    variables: [{ key: "panel", type: "enum", enum: ["details"] }],
  },
  { id: "new_order", phrases: ["create new order", "add new order", "click new order"] },
  { id: "confirm_dialog", phrases: ["confirm", "yes confirm", "ok confirm"] },
  { id: "cancel_dialog", phrases: ["cancel", "dismiss", "close dialog"] },
];
```

***

## Route commands to controls

Handle the `command` event and route each `id` to `registry.run(target, arg)`. The registry resolves the target, checks `isAvailable()`, and runs the control — so contextual gating (e.g. `confirm` only while a dialog is open) lives with the control, not the command handler.

```ts title="JavaScript" theme={null}
function handleCommand(e: CustomEvent) {
  const { id, variables } = e.detail.data;
  const v = variables ?? {};
  switch (id) {
    case "switch_tab":     return registry.run(v.tab);
    case "open_panel":     return registry.run(v.panel, "open");
    case "new_order":      return registry.run("new order");
    case "confirm_dialog": return registry.run("confirm");
    case "cancel_dialog":  return registry.run("cancel");
  }
}
```

***

## Application awareness

Because every control exposes `isAvailable()` and `getState()`, the registry can produce a live snapshot of what's on screen and actionable — useful for a debug overlay, or to make commands contextual.

```ts title="JavaScript" theme={null}
const actionable = registry.snapshot();
// → [{ id: "details", label: "details", kind: "toggle", state: "closed", available: true }, ...]
```

<Tip>
  Availability gating prevents surprising actions — a spoken "confirm" does nothing unless a dialog is actually open. Register controls with accurate `isAvailable()` and the command surface adapts to app state for free.
</Tip>

***

## Web or native

The registry is DOM-agnostic — it only knows `label`, `run`, and the availability/state hooks. The same `register` → `resolve` → `run` contract can be implemented by a desktop or OS host over an IPC bridge, so your command-and-control logic runs unchanged against a web UI or a native application.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Commands" icon="terminal" href="/stt/guides/dictation-commands">
    The declare → event → dispatch loop these app-control commands build on.
  </Card>

  <Card title="Dictation Box" icon="keyboard" href="/stt/guides/dictation-dictation-box">
    Move text between a scratch box and your form fields by voice.
  </Card>

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

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