Skip to main content
The built-in blocks should be your default: zero-effort, on-brand, restyle-proof. But some results deserve pixels of their own — an order picker, a seat map, a branded price card. A widget is a sandboxed HTML document you ship as a block: full visual control inside a hard security boundary.
Reach for a widget because the card needs it, not because you can. An unnecessary widget is a defect — it ages, drifts off-theme, and costs bytes. Blocks first.

Returning a widget

A widget is just another glance block. The SDK’s widgetResult helper wraps one for you:
Or as a raw block: { "type": "widget", "html", "height?", "data?", "label?" }. It counts against the normal 3-block budget, and can’t sit inside a row.

The sandbox

Your document renders in an iframe with sandbox="allow-scripts" and deliberately without allow-same-origin:
No network means fetch in your tool handler, then inline: data as JSON, images as data: URIs, styles inline, system font stack.

The bridge

Host → widget, once, after load:
data is your block’s data verbatim. Style for both modes and switch on theme.modenever on prefers-color-scheme, which follows the OS while the notch stays dark.
args (the pending tool-call arguments) is delivered only to confirmation widgets. A result widget’s init carries data and theme — put everything the card needs in data.
Widget → host: Full wire details: Widget bridge reference.

A complete minimal widget

Three habits worth copying from it:
  1. Theme comes from the bridge, not the OS.
  2. A ResizeObserver reports real height.
  3. A guard refuses to report a collapsed measurement — a hidden frame measuring 0 would otherwise pin your card at the 60px floor.

The corner

Every card VoiceOS shows is cut at the same corner radius, and the surface does the cutting, not your document. Your card arrives as a rectangle; the host rounds it. That matters the moment you paint anything to the card’s own edge — a background, a border, a header wash, a full-bleed image. Draw that at a radius of your own and there are two corners: yours, and the host’s arc slicing across it a few pixels out. So use the variable, never a number:
--k-radius is set before your CSS runs and is 26px — the same corner the notch cuts confirmation cards at. It’s still not a constant you can inline: the same html renders into more than one surface, and a surface whose clip is concentric with a different shell uses its own value (VoiceOS’s pill clips at 25). The host sends its real value on voiceos:init, so var(--k-radius) is correct everywhere and a literal is correct in exactly one place.
Only the OUTER corner is host-owned. Rows, chips, thumbnails, inputs, and avatars are yours — match the product you’re fronting. If you build with the Widget Kit, its card shells already carry border-radius: var(--k-radius).

Height, done right

Declare an honest height and report corrections. A good estimate alone drifts as content varies; a correction alone means a visible jump on every card. Measure your real card once, hardcode the estimate, keep the observer.

Don’t hand-roll it

Everything on this page — bridge, theming, resize guards, caps, degradation — is implemented once in the Widget Kit, the same design system the Integration Studio uses for every card it generates.

The Widget Kit

Composable card components, brand accents with automatic contrast repair, and a bridge runtime you never have to write.