Skip to content
Get the appGet app

Widget Templates

Persistent widgets live on the Home Screen, the Lock Screen, StandBy, and the Apple Watch. Unlike Live Activities, they are not tied to an ongoing activity: you create a widget once in the app, picking its template and slug, then update its content whenever you like via the API, and Aivi pushes the change to every device that displays it.

Thirteen templates cover the common shapes of glanceable data, and two of them take presses. Pick the one that matches yours; each card jumps to the template’s schema and examples.

The content object you send must match the widget’s template schema. Widget templates reuse the Live Activity building blocks ( colors, icons, values, and formatters), so the vocabulary is the same across both.

Every template renders at every Home Screen size, small through the near-fullscreen extra-large portrait size on iOS 27, on the Lock Screen, and on the Apple Watch. The data-rich templates put the larger canvases to work with more data; the simpler ones scale to fit.

Widgets are Aivi’s persistent, best-effort layer. Knowing how updates travel helps you pick the right tool:

  • Content pushes arrive in minutes, not seconds. Updating a widget stores the content and sends a low-priority background push to every device showing it. iOS batches and budgets these pushes to save battery, so a change typically appears within minutes: often faster, occasionally slower (Low Power Mode, a sleeping device). Design for “latest known state”, not for real-time.
  • Predictable data stays live without pushes. Anything the device can compute on its own keeps ticking between pushes: the Progress template’s end_date countdown and self-advancing bar, the Countdown target, the time_since / time_until formatters, and the Prices timeline highlighting the current period. One push can keep a widget correct for hours or days; prefer this over frequent pushes.
  • Real-time belongs to Live Activities. If you need second-by-second updates during an ongoing task, use a Live Activity: that is the layer iOS budgets for real-time. A widget can sit alongside it as the persistent summary.

By default a widget shows its last content indefinitely, with no judgement about age; only you know your update cadence. Set stale_after (in seconds) to opt in to staleness: once the content is older than the threshold, the widget visually demotes it and shows an “as of 14:32” line instead of presenting old data as current. You can set it when creating the widget in the app, or per request via the API.

ID: status

At-a-glance state: an icon, a hero reading, and a state line.

Status widget at the medium size: a 21° hero reading, a Comfortable state line, and a thermometer icon.

The Aivi integration sets up a Status widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint:

FieldTypeDescriptionRequired?
template"status"Identifies this template.Yes
valueValueThe hero reading. Supports text colors and the self-ticking time_since / time_until formatters. When absent, the state takes its place.No
stateValueThe supporting line under the hero.No
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No
{
"template": "status",
"value": { "value": "21°" },
"state": { "value": "Comfortable", "text_color": "green" },
"icon": { "name": "thermometer.medium", "primary_color": "orange" }
}

ID: progress

A live progress bar or countdown. With an end_date set, a single push keeps the widget counting down and the bar advancing on its own.

Progress widget at the medium size: a washer counting down 29:59 with a Washing state line and a 65% progress ring.

The Aivi integration sets up a Progress widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint:

FieldTypeDescriptionRequired?
template"progress"Identifies this template.Yes
progressProgressBarProgress bar configuration: a simple single-color bar or a segmented bar.Yes
stateValueThe state line (for example “Washing”).No
end_datestringISO 8601 timestamp. When set in the future, the widget shows a live countdown and, for simple bars, a self-advancing bar, with no extra pushes needed. Segmented bars stay static.No
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No
{
"template": "progress",
"progress": { "style": "simple", "value": 0.65, "color": "cyan" },
"state": { "value": "Washing" },
"end_date": "2026-07-01T15:42:00Z",
"icon": { "name": "washer.fill", "primary_color": "cyan" }
}

ID: gauge

A numeric reading on an instrument dial.

Gauge widget at the medium size: a 21.5°C reading on a cool-to-warm gradient dial spanning 0 to 40.

The Aivi integration sets up a Gauge widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint:

FieldTypeDescriptionRequired?
template"gauge"Identifies this template.Yes
valuefloatThe reading.Yes
min_valuefloatDial minimum. Default: 0.No
max_valuefloatDial maximum. Default: 1.No
unitstringUnit shown with the reading (for example "°C").No
labelstringLabel shown with the reading (for example "Living room").No
colorColorSolid dial color.No
gradientColor[]2–4 gradient stops for the dial.No
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No
{
"template": "gauge",
"value": 21.5,
"min_value": 0,
"max_value": 40,
"unit": "°C",
"label": "Living room",
"icon": { "name": "thermometer.medium" }
}

ID: battery

Device battery levels in Apple’s Batteries idiom: level rings with device icons, and a device list at the large sizes. Made for home-automation devices (vacuums, locks, cameras, sensors) that Apple’s own widget cannot show.

Battery widget at the medium size: four battery level rings with device icons, one charging and one low in red.

The Aivi integration sets up a Battery widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint (Home Assistant 2025.7 or newer):

FieldTypeDescriptionRequired?
template"battery"Identifies this template.Yes
devicesDevice[]1–8 devices, shown in the order you send them. Small and medium show up to four rings; large lists up to eight devices.Yes
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No
FieldTypeDescriptionRequired?
namestringDevice name, shown in the large list.Yes
levelfloatBattery percentage, 0–100.Yes
chargingboolAdds the charging bolt. Default: false.No
iconstring | IconSymbol shown inside the ring.No
colorColorRing color override. By default rings are green, or red at 20 percent and below.No
{
"template": "battery",
"devices": [
{ "name": "Robot vacuum", "level": 68, "icon": "fan.fill" },
{ "name": "Door lock", "level": 45, "charging": true },
{ "name": "Doorbell cam", "level": 92, "icon": "video.fill" }
]
}

ID: budget

A top-level budget plus up to six category breakdowns, made for finance and budgeting. Renders at the large widget sizes too.

Amounts are structured money, not preformatted strings: send numbers or decimal strings, and the device formats them for the user’s locale and the given currency (symbol placement, grouping, and fraction digits, so JPY gets 0 and USD gets 2). Prefer decimal strings: a JSON number cannot carry a trailing zero.

Budget widget at the medium size: $980.70 remaining of $1,500 with a draining ring, next to Food, Vehicle, and Gadgets category rings.

The Aivi integration sets up a Budget widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint (Home Assistant 2025.7 or newer):

FieldTypeDescriptionRequired?
template"budget"Identifies this template.Yes
totalEntryThe top-level budget line. The widget name serves as its label.Yes
categoriesEntry[]Up to 6 category lines, shown in the order you send them.No
currencystringISO 4217 code (3 uppercase letters); one currency per widget.Yes
display"remaining" | "spent"Which figure headlines everywhere; the ring follows it, so remaining mode drains toward empty while spent mode fills up. Overspending shows a negative remainder in red. Default: "remaining".No
subtitleValueShown with the total, for example “20 days left”. Supports the time_until formatter for a self-updating line.No
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget.No
tap_urlstringTap action URL to open when the widget is tapped.No
FieldTypeDescriptionRequired?
spentnumber | stringAmount spent.Yes
budgetnumber | stringBudgeted amount, greater than 0. When present the entry gets a progress ring and the total shows an “of $1,500” line. When absent the entry shows the spent amount with a plain icon.No
labelstringThe category name.No
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
colorColorThe ring color. Falls back to the icon color, then the accent.No
{
"template": "budget",
"currency": "USD",
"display": "remaining",
"total": {
"spent": "519.30",
"budget": "1500",
"icon": { "name": "banknote", "primary_color": "yellow" },
"color": "yellow"
},
"subtitle": { "value": "20 days left" },
"categories": [
{
"spent": "157.60",
"budget": "300",
"label": "Food",
"icon": "fork.knife",
"color": "blue"
}
]
}

ID: trend

A current reading with a compact history sparkline: temperature, humidity, power draw, or anything you already track as a series. You post the recent history; the device draws and scales the chart. Renders at the large widget sizes too.

Trend widget at the medium size: a 21.4° hero reading with a Rising state line and an orange history sparkline.

The Aivi integration sets up a Trend widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint:

FieldTypeDescriptionRequired?
template"trend"Identifies this template.Yes
valuefloatThe current reading, shown as the hero.Yes
pointsfloat[]Recent history, 2–48 points, oldest first. The device scales the sparkline to the data.Yes
unitstringUnit shown with the reading (for example "°").No
min_valuefloatFixed lower chart bound. Unset scales to the data.No
max_valuefloatFixed upper chart bound. Unset scales to the data.No
stateValueSupporting line under the hero (for example “Rising”).No
colorColorSparkline color. Falls back to the accent, then the icon color.No
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No
{
"template": "trend",
"value": 21.4,
"unit": "°",
"points": [18.2, 18.9, 19.6, 20.8, 21.6, 21.2, 20.9, 21.4],
"state": { "value": "Rising" },
"icon": { "name": "thermometer.medium", "primary_color": "orange" }
}

ID: countdown

A moment in time rendered as a self-updating countdown: garbage day, the next departure, an appliance finishing. One push sets the target; the device keeps the countdown current with no further pushes.

Countdown widget at the medium size: 2 days, 2 hr until garbage day, with a Paper & cardboard subtitle and the target date.

The Aivi integration sets up a Countdown widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint:

FieldTypeDescriptionRequired?
template"countdown"Identifies this template.Yes
targetstringISO 8601 timestamp (with timezone) to count down to.Yes
subtitleValueSupporting line (for example “Paper & cardboard”).No
expired_textstringShown once the target has passed. Without it, the widget shows the time elapsed since the target instead.No
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No
{
"template": "countdown",
"target": "2026-07-04T06:30:00Z",
"subtitle": { "value": "Paper & cardboard" },
"expired_text": "Out now",
"icon": { "name": "trash.fill", "primary_color": "green" }
}

ID: list

Rows of labeled values: the generic multi-entity readout for anything that does not fit a more specific template. Small and medium show up to four rows (small drops the labels to fit); large lists up to eight. Renders at the large widget sizes too.

List widget at the medium size: four labeled rows: Living room 21.4°, Front door Locked in green, Humidity 54%, Garage Closed.

The Aivi integration sets up a List widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint (Home Assistant 2025.7 or newer):

FieldTypeDescriptionRequired?
template"list"Identifies this template.Yes
rowsRow[]1–8 rows, shown in the order you send them.Yes
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No
FieldTypeDescriptionRequired?
labelstringThe row label.Yes
valueValueTrailing value, with optional text color and formatters.No
iconstring | IconLeading symbol.No
{
"template": "list",
"rows": [
{
"label": "Living room",
"value": { "value": "21.4°" },
"icon": { "name": "thermometer.medium", "primary_color": "orange" }
},
{
"label": "Front door",
"value": { "value": "Locked", "text_color": "green" },
"icon": "lock.fill"
},
{
"label": "Humidity",
"value": { "value": "54%" },
"icon": { "name": "humidity.fill", "primary_color": "teal" }
}
]
}

ID: prices

A price timeline for dynamic tariffs: hourly electricity prices and the like. Post the periods you know (for example today plus tomorrow, hourly); the device charts them and highlights the current one, so a single nightly push stays correct all day. Renders at the large widget sizes too.

Prices widget at the medium size: the current price in ct/kWh with its level, next to a day of hourly price bars colored by level with the current hour highlighted.

The Aivi integration sets up a Prices widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint:

FieldTypeDescriptionRequired?
template"prices"Identifies this template.Yes
entriesEntry[]1–48 pricing periods in strictly increasing start order. Each period lasts until the next one begins.Yes
unitstringDisplay unit (for example "ct/kWh"); prices are unit-agnostic numbers.No
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No
FieldTypeDescriptionRequired?
startstringISO 8601 timestamp the period starts at.Yes
pricefloatThe price for the period.Yes
level"low" | "medium" | "high"Explicit price banding. Unset lets the device derive bands from the posted range.No
{
"template": "prices",
"unit": "ct/kWh",
"entries": [
{ "start": "2026-07-02T00:00:00+02:00", "price": 14.2 },
{ "start": "2026-07-02T01:00:00+02:00", "price": 12.8 },
{ "start": "2026-07-02T02:00:00+02:00", "price": 11.9, "level": "low" },
{ "start": "2026-07-02T03:00:00+02:00", "price": 22.4 }
]
}

ID: energy

Home energy at a glance: generation sources, grid, battery, and home consumption. Sources are generation of any kind (solar, wind, hydro, a generator) and default to the solar presentation when unnamed. Renders at the large widget sizes too.

Energy widget at the medium size: Solar 3.2 kW, Grid -1.4 kW, Battery 800 W at 76%, and Home 1 kW, each with its icon.

The Aivi integration sets up an Energy widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint (Home Assistant 2025.7 or newer):

FieldTypeDescriptionRequired?
template"energy"Identifies this template.Yes
sourcesNode[]Up to 3 generation sources.No
gridNodeThe grid connection.No
batteryNodeThe home battery.No
homeNodeHome consumption.No
iconstring | IconAn SF Symbols icon name or an Icon with rendering options.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No

All four slots share this one node shape; the slot a node sits in decides its defaults and how its power sign is read. In practice level belongs to the battery node and name chiefly renames generation sources; the remaining fields apply everywhere.

FieldTypeDescriptionRequired?
namestringDisplay label overriding the slot’s default (“Solar”, “Grid”, …); chiefly for generation sources (“Wind”, “Hydro”).No
powerfloatInstantaneous power in watts. The sign follows the slot’s convention (see above).Yes
todayfloatToday’s cumulative energy in kWh.No
levelfloatCharge percent (0–100); battery node only.No
iconstring | IconSymbol for the node.No
colorColorColor override for the node.No
{
"template": "energy",
// Unnamed sources render as "Solar"; set "name" for "Wind", "Hydro", …
"sources": [{ "power": 3200, "today": 12.4 }],
// Negative grid power: exporting. Positive would be importing.
"grid": { "power": -1400, "today": 3.1 },
// Positive battery power: charging. Negative would be discharging.
"battery": { "power": 800, "level": 76 },
"home": { "power": 1000, "today": 9.8 }
}

ID: showcase

A picture of a thing, its state, when it last reported, and up to six readings: a car, a robot vacuum, a 3D printer, a plant.

Showcase widget at the medium size: a white car, Locked, reported 2m ago, with Battery 78%, Range 312 km and Odometer 24 180 km.

The Aivi integration sets up a Showcase widget from a form in Home Assistant, with a live preview. To use a plain automation instead, import the blueprint (Home Assistant 2025.7 or newer):

The picture needs a public URL that every phone can load; see Hosting Pictures.

FieldTypeDescriptionRequired?
template"showcase"Identifies this template.Yes
statsStat[]1–6 readings. Small shows the first, medium three, large four, and the portrait and iPad sizes all six, with the first number as the big reading.Yes
imagePictureThe picture of the thing. See Pictures.No
badgeImageA small mark beside the report time, such as a brand logo. Not shown at the small size or with a cover picture.No
state{ icon, text }The state: an optional icon (a symbol name or Icon) in front of text, a { value, text_color } word of 1–24 characters, for example a lock and Locked.No
reported_atstringWhen the thing last reported, in ISO 8601 with a time zone, for example 2026-10-05T07:41:12Z. Shown as “2 min ago”. It doesn’t make the widget stale.No
iconstring | IconAn SF Symbols icon name or an Icon. Shown in place of the picture on the Lock Screen and the Apple Watch.No
accentColorPalette color that themes the whole widget. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped.No
FieldTypeDescriptionRequired?
urlstringAn https:// URL, at most 2048 characters.Yes
altstringWhat the picture shows, at most 64 characters, for VoiceOver. Default: the widget’s name.No
fit"contain" | "cover"contain (the default) is a cut-out on a transparent background, shown whole. cover is a photo, cropped to fill its space.No
FieldTypeDescriptionRequired?
labelstring1–24 characters, for example "Range".Yes
valueValueThe reading, for example "312".Yes
unitstringDrawn after the value, for example "km". At most 8 characters.No
iconstringAn SF Symbol name that stands in for the label on the Lock Screen when the widget has no icon.No
maxnumberGreater than 0. Draws the first stat as a gauge filled to value / max on the round Lock Screen widget. Needs a plain number as the value, such as 78 or -3.5, and no formatter. A 0–100 value with a % unit fills the gauge without it.No
{
"template": "showcase",
"image": { "url": "https://example.com/aivi/car-side.png", "alt": "The car" },
"badge": { "url": "https://example.com/aivi/brand.png", "alt": "Brand" },
"state": { "icon": "lock.fill", "text": { "value": "Locked", "text_color": "green" } },
"reported_at": "2026-10-05T07:41:12Z",
"stats": [
{ "label": "Battery", "value": { "value": "78" }, "unit": "%" },
{ "label": "Range", "value": { "value": "312" }, "unit": "km", "icon": "road.lanes" },
{ "label": "Odometer", "value": { "value": "24 180" }, "unit": "km" }
],
"icon": "car.fill",
"accent": "blue"
}

ID: switchboard

A grid of up to eight pressable tiles, each a button (a scene, a script) or a toggle (a light, a fan). Every tile leads with a round mark that previews what the press does: a picture, a gradient of the scene’s colors, or a symbol on a color. Under it sit the label and a second line with the state or a subtitle such as the room. A tile fills with its mark’s color while it is on or active. A press on the Home Screen reaches your own system; Buttons and Toggles covers how presses travel, what a tile shows while it waits, and how you confirm the result.

Switchboard widget at the medium size: six tiles. Morning with a gradient mark, Evening filled with its sunset gradient reading Active, Garage door filled orange reading Open · Driveway, Dog cam with the dog's picture filled in its warm tone reading On · Hallway, Porch light filled warm white reading On · 2700 K, and Fountain off.

The Aivi integration turns your lights, switches, locks, garage doors, scenes and scripts into tiles, and presses reach Home Assistant with no webhook or open port. To build it with your own automations instead, the Buttons and Toggles page has a recipe that publishes a switchboard from your entities and turns presses into service calls, with a webhook and two automations. There is no blueprint for the Switchboard.

FieldTypeDescriptionRequired?
template"switchboard"Identifies this template.Yes
actionsAction[]1–8 actions, shown in the order you send them. Every id must be unique.Yes
iconstring | IconAn SF Symbols icon name or an Icon. Shown on the Lock Screen and the Apple Watch.No
accentColorPalette color for tiles that set no color of their own. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped outside a tile.No
FieldTypeDescriptionRequired?
idstringWhat your system receives when the tile is pressed. Lowercase letters, digits, _ and -, starting with a letter or digit, at most 32 characters (^[a-z0-9][a-z0-9_-]{0,31}$).Yes
labelstringThe tile’s label, 1–24 characters.Yes
subtitleValueA second line under the label, for example the room or “2700 K”. The same Value as the Budget and Countdown subtitles, so its text_color is a named color.No
iconstringAn SF Symbols name, 1–64 characters. A plain string: tiles draw their symbol in one color, so the Icon object’s options don’t apply. Always required, because some surfaces draw only the symbol (see Marks).Yes
colorAction colorThe mark’s color behind the symbol, and the tile’s fill while it is on or active when there is no gradient or picture. Falls back to the widget’s accent.No
gradientAction color[]2 or 3 stops, for example a scene’s light colors. Drawn as the mark in place of the symbol, and as the fill while the tile is on or active.No
imageImageA picture drawn as the mark once the phone has it. See Pictures.No
kind"button" | "toggle" | "link"Default: "button". "link" is for the Remote only (see Links).No
urlstringLinks only. The URL the tile opens, at most 256 characters. javascript, data, file, blob, about and aivi URLs are rejected.For links
activeboolButtons only. Highlights the tile, for example the scene that is running now. Only you set it; pressing never does. Default: false.No
onboolToggles only. Whether the thing is on right now. A press asks for a change; your next update confirms it.For toggles
state_textState wordsYour own words for the tile’s states, such as “Open” and “Closed” for a garage door. Omit it for the app’s words.No

Any other field is rejected, including active on a toggle, on on a button, and active, on and state_text on a link.

The second line under a tile’s label names its state: On, Off, Turning on, and so on. state_text replaces those words with yours, one key per state. The keys depend on the action’s kind:

KindKeyShown whenThe app’s word
toggleonThe toggle is on.On
toggleoffThe toggle is off.Off
toggleturning_onA press to turn it on is waiting for your update.Turning on
toggleturning_offA press to turn it off is waiting for your update.Turning off
toggleno_responseYour update didn’t arrive within 15 seconds.No response
buttonidleThe button is at rest.(none)
buttonactiveYou published the button with "active": true.Active
buttonsentAfter a press, until your next update arrives.Sent

The words only rename the states: when each state shows is the same as without them, as described in What a tile shows.

  • Leave a key out, or send null, to keep the app’s word. A string is used as written, and "" hides the word.
  • Each key stands alone. Renaming on doesn’t change turning_on, so set every word you want changed.
  • One short line. Surrounding whitespace is trimmed, and a word can be at most 24 characters after trimming, with no line breaks, tabs, or other control characters.
  • Keys match the kind. A toggle’s keys on a button, a button’s keys on a toggle, and any other key are rejected with 422.
  • A hidden word leaves the subtitle. When the word is "", the second line shows the subtitle alone, in every state, or stays empty without one.
  • VoiceOver still names the state. It reads your word, and the app’s word where you hid it, so on and off are always announced.
  • Words count toward the 4 KiB content limit. Eight toggles with every word at its full 24 characters fit, but not together with rich marks on every tile.

A garage door that reads Open · Driveway, and Opening or Closing while a press waits:

{
"id": "garage",
"label": "Garage door",
"subtitle": { "value": "Driveway" },
"icon": "door.garage.open",
"kind": "toggle",
"on": true,
"state_text": {
"on": "Open",
"off": "Closed",
"turning_on": "Opening",
"turning_off": "Closing"
}
}

A lock:

{
"id": "front_door",
"label": "Front door",
"icon": "lock.fill",
"kind": "toggle",
"on": true,
"state_text": {
"on": "Locked",
"off": "Unlocked",
"turning_on": "Locking",
"turning_off": "Unlocking"
}
}

A scene button that reads Starting after a press and Running while you publish it as active:

{
"id": "evening",
"label": "Evening",
"icon": "sunset.fill",
"active": true,
"state_text": { "active": "Running", "sent": "Starting" }
}

And the smallest change, renaming only on and off:

{
"id": "vent",
"label": "Vent",
"icon": "wind",
"kind": "toggle",
"on": false,
"state_text": { "on": "Open", "off": "Shut" }
}
FieldTypeDescriptionRequired?
urlstringAn https:// URL with a host and no whitespace (^https://[^\s/?#]+[^\s]*$), at most 2048 characters. The phone fetches it; Aivi never downloads it.Yes
altstringWhat the picture shows, at most 64 characters. VoiceOver reads it after the label while the picture is drawn.No

Any other field in an Image is rejected.

An action’s color and gradient stops take either a named color or a hex color, #RRGGBB (six digits, no alpha, no three-digit shorthand). Hex is stored and returned in upper case, so #3d3dff comes back as #3D3DFF. Elsewhere in a widget, including its accent and a subtitle’s text_color, colors are named only, and hex is rejected with a 422.

A tile draws the richest mark it can:

  1. The picture from image, once the phone has it.
  2. Otherwise the gradient.
  3. Otherwise the symbol from icon, on color (or the widget’s accent).

Send several, and the poorer ones stand in while a picture loads or when it can’t be shown. Surfaces that don’t show color always draw the symbol: the Lock Screen and Apple Watch families, the tinted and clear Home Screen styles, and the Always-On display. So does a locked phone that hides the widget’s content: it shows the symbol, never the picture.

The mark also says what the tile’s fill will be: a filled tile uses the gradient, else the picture’s average color, else the color. Text on a pale fill turns dark so it stays readable. A toggle that is off shows a neutral tile with a muted mark, and a picture turns grey. The Buttons and Toggles page shows every state.

The actions fill a fixed grid in the order you send them. Slots you don’t use show as quiet empty tiles, so the grid keeps its shape. A size with fewer slots than actions shows the first ones.

SizeLayout
SmallA 2 × 2 grid: the first four actions, each a mark over its label.
MediumA 2 × 3 grid: the first six actions, each a mark beside its label and second line.
Large and extra largeThe widget’s name on top, then a 2 × 4 grid of all eight: a mark beside the label and second line.
Extra large portrait (iOS 27)The name, then a 2 × 4 grid of tall tiles: a large mark at the top, the label and second line at the bottom.
Rectangular (Lock Screen, Apple Watch)The name, the symbols of the first four actions (lit when on or active), and how many are on.
Circular and inline (Lock Screen, Apple Watch)The widget’s icon (a switch symbol by default) and how many are on. Inline adds the name.
Corner (Apple Watch)How many are on, with the name along the edge.

The second line shows the state (On, Off, Active, or your own state words) and the subtitle together when they fit (“On · 2700 K”), and the state alone when they don’t. An idle button shows its subtitle. While a press is on its way, the line shows its progress instead; see What a tile shows.

On the large sizes, the header shows the time of your last update next to the name once the content is stale, and otherwise only the name: the tiles already show what is on and what is on its way. The Lock Screen and Apple Watch summaries, a switchboard inside a Board, and VoiceOver count what is on instead: toggles that are on and buttons that are active. In the tinted and clear Home Screen styles, iOS draws the tiles in its own colors instead of yours.

Open All sizes on the preview above to see each size with the same content.

The content the renders above show:

{
"template": "switchboard",
"actions": [
{
"id": "morning",
"label": "Morning",
"subtitle": { "value": "Kitchen" },
"icon": "sunrise.fill",
"gradient": ["#FFF3C4", "#FFC46B"]
},
{
"id": "evening",
"label": "Evening",
"subtitle": { "value": "Living room" },
"icon": "sunset.fill",
"gradient": ["#FF9A5A", "#FF4F8B"],
"active": true
},
{
"id": "garage",
"label": "Garage door",
"subtitle": { "value": "Driveway" },
"icon": "door.garage.open",
"kind": "toggle",
"on": true,
"color": "orange",
"state_text": {
"on": "Open",
"off": "Closed",
"turning_on": "Opening",
"turning_off": "Closing"
}
},
{
"id": "dog_cam",
"label": "Dog cam",
"subtitle": { "value": "Hallway" },
"icon": "video.fill",
"kind": "toggle",
"on": true,
"gradient": ["#E8C07A", "#B98545"],
"image": {
"url": "https://getaivi.app/demo/switchboard/dog-cam.jpg",
"alt": "The dog in the hallway"
}
},
{
"id": "porch",
"label": "Porch light",
"subtitle": { "value": "2700 K" },
"icon": "lightbulb.fill",
"kind": "toggle",
"on": true,
"gradient": ["#FFE4AD", "#FFAE5E"]
},
{
"id": "fountain",
"label": "Fountain",
"subtitle": { "value": "Garden" },
"icon": "drop.fill",
"kind": "toggle",
"on": false,
"color": "#30B0C7"
},
{
"id": "cat_feeder",
"label": "Cat feeder",
"subtitle": { "value": "Kitchen" },
"icon": "pawprint.fill",
"color": "brown",
"image": {
"url": "https://getaivi.app/demo/switchboard/cat-feeder.jpg",
"alt": "The cat by its bowl"
}
},
{ "id": "all_off", "label": "All off", "subtitle": { "value": "Whole home" }, "icon": "power", "color": "gray" }
]
}

ID: remote

One device and its controls: a picture, one big reading, up to three stats, and up to five buttons and toggles you press on the Home Screen. A car’s lock and climate, a garage door, a robot vacuum. Presses work as on the Switchboard; see Buttons and Toggles.

Remote widget at the medium size: a blue car, reported 2m ago, at 78%, with Lock (on), Climate and Trunk buttons.
FieldTypeDescriptionRequired?
template"remote"Identifies this template.Yes
valueValueThe big reading, for example a car’s charge.Yes
actionsAction[]1–5 buttons, toggles and links, the Switchboard’s action object. Every id must be unique.Yes
imagePictureThe picture of the device. See Pictures.No
statsStat[]Up to 3 more readings, at the large size and up.No
reported_atstringWhen the device last reported, as in the Showcase.No
iconstring | IconAn SF Symbols icon name or an Icon.No
accentColorPalette color for the widget, and for buttons that set no color. Falls back to the icon color.No
tap_urlstringTap action URL to open when the widget is tapped outside a button.No

There is no state field. On the Lock Screen and the Apple Watch, the widget names the first active button, or else the first toggle that is on, by its state word for that state, or its label.

  • Buttons. Actions with no image, gradient or color are drawn as plain buttons: symbols in a row in small and medium, a symbol over its label in large. Give any action one of those, and every action is drawn as a Switchboard tile with its label.
  • How many show. Small shows the first two actions, medium five (four as tiles), and large all five.
  • Lock Screen and Apple Watch show the reading and that state, and can’t be pressed.
{
"template": "remote",
"image": { "url": "https://example.com/aivi/car-front.jpg", "fit": "cover" },
"reported_at": "2026-10-05T07:41:12Z",
"value": { "value": "78%" },
"stats": [{ "label": "Range", "value": { "value": "312" }, "unit": "km" }],
"actions": [
{
"id": "lock", "kind": "toggle", "label": "Lock", "icon": "lock.fill", "on": true,
"state_text": { "on": "Locked", "off": "Unlocked" }
},
{ "id": "climate", "kind": "toggle", "label": "Climate", "icon": "fan.fill", "on": false },
{ "id": "trunk", "label": "Trunk", "icon": "car.side.rear.open.fill" }
],
"icon": "car.fill",
"accent": "blue"
}

Some templates show pictures from a URL: the Showcase’s picture and badge, the Remote’s picture, an action’s image on the Switchboard and Remote, and the Remote Live Activity’s picture. Aivi never fetches or stores a picture: each phone downloads it, shrinks it, and keeps it.

  • Keep them small. PNG, JPEG, HEIC, WebP, or GIF (the first frame). An action’s picture can be at most 512 pixels on a side and 256 KB; any other picture at most 2048 pixels and 1 MB. A larger file is refused, and the widget shows the gradient, the symbol, or the icon instead. A URL that fails is not tried again for 30 minutes.
  • A new picture can take a while. The Aivi app fetches pictures when it opens, after it syncs, and in the background a few times a day; a widget also fetches missing ones on its next update. Until then, it shows the gradient, the symbol, or the icon.
  • Change the URL to change the picture. The phone keeps a picture for 24 hours without checking whether it changed. To show a new one sooner, give it a new URL, for example dog-cam.jpg?v=2.
  • Use a public, stable URL. Every phone showing the widget fetches it, without your credentials. In Home Assistant, don’t forward an entity’s entity_picture as is: it is usually relative, authenticated, or changing. Hosting Pictures shows where to put your own.
  • Not on every widget surface. Lock Screen and Apple Watch widgets, the tinted and clear Home Screen styles, the Always-On display, and a widget that hides its content on a locked phone show the icon or symbol instead. A Live Activity shows its picture wherever it has room for one.

The quickest path is the Aivi integration: choose the entities for each widget in a panel in Home Assistant, with a live preview and no YAML. If you prefer plain automations, the ready-made widget blueprints cover eleven of the templates, all but the Switchboard and the Remote, imported in a few clicks, with the payload plumbing handled for you. The Home Assistant widgets guide walks through the whole setup, from creating the widget to placing it on screen.

For anything the blueprints don’t cover, widgets use the same RESTful command pattern as Live Activities. Add a command for the widget endpoint to configuration.yaml:

rest_command:
update_widget:
url: "https://api.getaivi.app/widgets/{{ slug }}"
method: PATCH
headers:
content-type: application/json
authorization: Token [token]
payload: "{{ payload }}"

Then call it from an automation whenever the source data changes. This example keeps a Gauge widget in sync with a temperature sensor:

- triggers:
- trigger: state
entity_id: sensor.living_room_temperature
actions:
- action: rest_command.update_widget
data:
slug: living-room
payload: >-
{
"content": {
"template": "gauge",
"value": {{ states('sensor.living_room_temperature') | float(0) }},
"min_value": 10,
"max_value": 35,
"unit": "°C",
"label": "Living room",
"icon": "thermometer.medium"
}
}

There is no need to update on a timer: widgets show the latest known state, so updating only when the value actually changes is enough. For slow-moving data (battery levels, prices), a time-pattern trigger every hour or a single nightly update works just as well.

Shortcuts can update a widget when run manually, from the Action button, or on a schedule via a personal automation, no server required.

  1. Add a Text action containing the request body:

    {
    "content": {
    "template": "progress",
    "progress": { "style": "simple", "value": 0.6, "color": "cyan" },
    "state": { "value": "1.5 of 2.5 L" },
    "icon": { "name": "drop.fill", "primary_color": "cyan" }
    }
    }

    Build the dynamic parts (numbers, dates) with earlier Shortcuts actions and insert them into the Text as variables.

  2. Add a Get Contents of URL action pointing at https://api.getaivi.app/widgets/water-intake and expand Show More:

    • Method: PATCH
    • Headers: Authorization → Token YOUR_API_TOKEN and Content-Type → application/json
    • Request Body: File, set to the Text from step 1
  3. Run the shortcut. The widget updates on every device that shows it.

For the full endpoint reference (request body, stale_after, responses, and errors), see Update a widget in the API reference.