A widget is a single, self-contained HTML document. No build step, no bundler, no external files — everything (markup, CSS, JS) lives in one string you hand to the hub. The phone loads it fullscreen and feeds it data.
The minimum
<!doctype html>
<html lang="en">
<head><meta charset="utf-8" /></head>
<body class="preen-glow">
<div class="preen-screen"><!-- device-centered UI --></div>
<div class="preen-safe"><!-- edge-anchored, notch-safe UI --></div>
<script>
window.PREEN.onData((d) => render(d));
window.PREEN.ready();
</script>
</body>
</html>
Three things are doing work here:
window.PREEN.onData(cb)— your renderer; called with the latest JSON on every update. See The window.PREEN API.window.PREEN.ready()— tells the runtime the UI is hydrated.- The
preen-*classes — layout + brand helpers the runtime injects. The twodivs are stackable full-bleed layers, one per coordinate space:preen-screenspans the entire glass (center a hero element here and it’s centered on the device),preen-safeclears the notch (edge-anchored content and safe-confined backgrounds). Use one, the other, or both. See Theming.
The sandbox (what you can’t do)
Before your code runs, the runtime injects this Content-Security-Policy as a
<meta> tag:
default-src 'none'; connect-src 'none';
img-src data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'; font-src data:
Consequences:
- No direct network from widget code.
fetch,XMLHttpRequest,EventSource, WebSockets, and beacon requests are blocked byconnect-src 'none'. Live feed data arrives throughonData; datastore results arrive through the SDK’s Promises. - No external assets. Images must be
data:URIs; fonts must bedata:or system fonts. Inline<style>and<script>are allowed. - You may add your own
<meta>CSP, but it can only further restrict — CSP policies intersect.
Inline JavaScript runs normally: rendering, timers, animation, event handlers, and local calculations all run on the phone. The boundary is direct I/O and credentials. The native app owns the network connections, while the SDK provides scoped datastore requests and consent-gated Mac actions. See Calling the Mac.
The runtime also cancels page navigation; a tapped HTTP or HTTPS link opens in the phone’s browser. The widget page itself stays in its sandbox. This web sandbox does not also sandbox scripts that you approve to run on the Mac; see the action boundary.
What the runtime provides
| Provided | What it is |
|---|---|
window.PREEN | The data + signals bridge (reference). |
--preen-* CSS variables | Brand colors + safe-area insets (theming). |
preen-screen, preen-safe, preen-display, preen-unsafe-cutout, preen-unsafe-chin, preen-content, preen-glow | Layout regions + glow helper classes. |
preen-landscape, preen-portrait | Orientation classes, kept in sync on <html> and <body> — style per-orientation in pure CSS (.preen-landscape .row { flex-direction: row }). Never add your own resize or orientationchange listeners; the runtime handles both. |
preen-relay | Present on <html> and <body> only when the phone is reaching the Mac through the Preen Plus cloud relay instead of the local network — so an “away” affordance is pure CSS (.preen-relay .badge { display: block }). Readable in JS as PREEN.viaRelay. See The window.PREEN API. |
preen-haptics | Present on <html> and <body> only when the phone can play haptics (iPhone, not iPad) — so a visual stand-in for a haptic tick is pure CSS (.preen-haptics .tick { display: none }). Readable in JS as PREEN.hapticsAvailable; fire one with PREEN.haptic(kind). See Haptics. |
preen-cutout-top, preen-cutout-left, preen-cutout-right, preen-cutout-none | Which screen edge holds the hardware cutout right now — kept in sync on <html> and <body> alongside the orientation classes (the unsafe strips re-anchor off these automatically). none marks cutout-less glass (iPad): every inset is 0, the three regions are the same full-screen rectangle, and both strips vanish. |
Authoring tip
You rarely write widget HTML by hand — you describe it to Claude Code and it
calls publish_widget. But knowing the contract above lets you review what it
produces and debug when a widget shows — instead of data.
The Mac as a display
The same HTML widget can run on a Mac Notch paired to a specific display. Each display can have its own Notch and widget selection, receiving the same live data and SDK. Add optional leading, trailing, header, and body regions for a custom compact and expanded layout; see Mac top-edge surface.