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

# Widget confirmations

> Fully custom confirmation cards — your pixels, VoiceOS's confirm button.

A [confirmation view](/integrations/confirmations) can be a single sandboxed
widget instead of declarative blocks: every pixel of the card is yours. One
thing is not negotiable — **the confirm control is VoiceOS's**, a floating
button the host renders over your card's bottom-right corner. Your only lever on
it is the label.

That rule is structural, not stylistic: the widget sandbox has **no approve
message**, so no markup you ship can trigger — or fake — an approval.

## Declaring one

```json theme={null}
"confirmation": {
  "schemaVersion": 1,
  "root": {
    "type": "widget",
    "confirmLabel": "Send it",
    "height": 168,
    "html": "<!doctype html>…"
  }
}
```

Validation enforces the shape:

| Rule                                                          |                                                                                                                                                                             |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **At most one widget per view**                               | —                                                                                                                                                                           |
| **No `button` or `actions` blocks anywhere in a widget view** | The floating button *is* the confirm control; relabel it with `confirmLabel`.                                                                                               |
| `confirmLabel`                                                | Up to 60 chars; whitespace falls back to `"Confirm"`. Keep it a short verb phrase, and name the consequence for destructive actions — **"Delete 42 files"**, not "Confirm". |
| Budgets                                                       | Same as every widget: `html` ≤ 128 KB, `data` ≤ 16 KB, height 60–420.                                                                                                       |

## The floating button

Bottom-right corner, no bar, no background — and deliberately self-contained: an
almost-opaque dark pill with its own hairline and shadow, so it stays readable
over anything you render, white or black.

Three consequences for your layout:

1. **Keep roughly 150×44 px of the bottom-right visually quiet.** Kit-built
   cards do this automatically (`mode: "confirm"` reserves the space); in
   hand-rolled HTML, leave the padding yourself.
2. **There is no Cancel button.** Declining stays on the notch's normal paths —
   saying "no", or dismissing the card. Don't draw your own cancel affordance;
   it wouldn't do anything.
3. **The corner is cut for you.** Confirmations render edge to edge and the
   surface rounds them, so anything you paint to the card's own edge must use
   `border-radius: var(--k-radius)` — see
   [The corner](/integrations/widgets#the-corner).

## Per-invocation values: `args`

A confirmation is static manifest markup, but it confirms a *specific* call. The
bridge solves this: a confirmation widget's init message carries the pending
tool-call arguments.

```js theme={null}
{ type: "voiceos:init", data, args, theme: { mode } }
```

The kit's runtime hydrates them into your markup: any element with
`data-voiceos-key="argName"` (or built with `vField`) receives that argument's
value on load.

## Letting the user edit

Inputs stage edits back through the bridge:

```js theme={null}
{ type: "voiceos:updateInput", key: "body", value: "Shipped! 🎉" }
```

Keyed inputs rendered by the kit post this automatically as the user types.
Staged edits ride the approval — **your handler receives the edited values**,
exactly like a bound `textField` in a declarative card. Keys must name
properties of the tool's `inputSchema`; values are capped at 16 KB.

<Warning>
  The declarative `required` gate doesn't reach inside a widget, so the floating
  button won't disable itself over an empty field you render. Validate in your
  handler and throw honestly if something essential is missing.
</Warning>

## A worked example

The kit makes the whole thing a few lines — build the card at authoring time,
paste the output into the manifest (the Studio does exactly this):

```ts theme={null}
import { renderWidget, vHeader, vKeyValue, vField } from "./widgetKit";

const card = renderWidget({
  mode: "confirm",                      // reserves the button corner
  accent: "#e01e5a",
  blocks: [
    vHeader({ title: "Post to #general", subtitle: "Slack" }),
    vField({ key: "body", label: "Message", multiline: true }),
    vKeyValue([{ label: "As", value: "you" }]),
  ],
});

const confirmation = {
  schemaVersion: 1,
  root: { type: "widget", html: card.html, height: card.height, confirmLabel: "Post it" },
};
```

<Tip>
  **Pick one visual language per integration.** If your tool results are custom
  widgets, make your confirmations custom widgets too — a branded result card
  next to a generic confirmation reads as broken.
</Tip>

<Card title="Bridge reference" icon="cable" href="/integrations/reference/widget-bridge">
  Every message, cap, and validation rule on the wire.
</Card>
