Add a home screen widget
Checked against c1b2fa4 on 2026-11-10. The files it covers are unchanged since.
A home screen widget is a piece of your page’s markup marked as a
surface. You don’t write a widget extension: the host
already has a generic one that draws whatever template the page sends it.
This guide follows examples/timer, a stopwatch whose widget shows the
running time.
1. Add the surface
Section titled “1. Add the surface”Put an element with data-niso-surface="widget.small" (or widget.medium)
anywhere in the page. It isn’t drawn in the page and takes no taps, so it can
sit at the bottom of your component and use the same state as everything else:
<div class="widget small" data-niso-surface="widget.small"> <div class="head"><span class="dot" class:on={running}></span><span>Stopwatch</span></div> {#if running} <time class="big" data-timer-start={origin}>{clock(elapsed)}</time> {:else} <time class="big">{clock(elapsed)}</time> {/if} <progress value={elapsed % 60000} max="60000" data-timer-start={running ? origin : undefined} data-timer-period={running ? 60000 : undefined}></progress> <p class="foot">{laps.length ? laps.length + (laps.length === 1 ? ' lap' : ' laps') : running ? 'Running' : active ? 'Paused' : 'Ready'}</p></div>examples/timer/App.svelte, line 82
2. Size it
Section titled “2. Size it”Give the surface the size of the widget in CSS pixels. The timer uses an iPhone’s widget sizes, and the targets scale it to fit elsewhere:
.small { width: 170px; height: 170px; display: flex; flex-direction: column; justify-content: space-between; } .medium { width: 364px; height: 170px; display: flex; gap: 16px; }examples/timer/App.svelte, line 169
Keep it simple: boxes with backgrounds, borders and corner radii, text,
<img>, <progress> and <time>. That’s what a template holds; anything
else (shadows, transforms, canvas) is dropped
(What a template holds).
3. Let it tick without your script
Section titled “3. Let it tick without your script”The widget runs no JavaScript, so it can’t update every second. Instead, a
<time data-timer-start={ms}> becomes the platform’s own counting timer and
a <progress data-timer-start data-timer-period> fills by itself. The
template only changes when the stopwatch starts, stops or laps, and that’s
when the host publishes it again
(Timers that count by themselves).
4. Build and add the widget
Section titled “4. Build and add the widget”hosts/apple/build.sh timer # iOS: the app carries NisoWidgets.appexhosts/android/build.sh timer # Android: the app's Glance widgetThen add the widget from the home screen’s widget gallery as for any app.
- iOS: the app and its widget extension share the App Group
group.dev.niso.host(hosts/apple/Niso.entitlements). After each frame the app writes changed widget templates there (Sources/SurfacePublisher.swift) and asks WidgetKit to reload; the extension (hosts/apple/Widgets) draws them. Surfaces go nowhere on macOS, tvOS, visionOS and watchOS yet. - Android: the app stores the templates and the Glance widget draws
widget.smallorwidget.medium, whichever fits the size the user gave it (SurfaceWidget.kt).
A widget shows what the page last rendered, so it’s only as fresh as the
app’s last run. Updating it from a server is planned: niso_render_template
can render a template headlessly, but no host takes pushed templates yet.
Buttons
Section titled “Buttons”Widgets on iOS run no page, so buttons in a widget surface are left out. On Android they aren’t drawn in the widget either; use an activity surface for controls.