Building widgets

Widget anatomy

What a widget document is, what the runtime provides, and the sandbox it runs in.

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="perch-glow">
    <div class="perch-safe">
      <div class="perch-content"><!-- your UI --></div>
    </div>
    <script>
      window.PERCH.onData((d) => render(d));
      window.PERCH.ready();
    </script>
  </body>
</html>

Three things are doing work here:

  • window.PERCH.onData(cb) — your renderer; called with the latest JSON on every update. See The window.PERCH API.
  • window.PERCH.ready() — tells the runtime the UI is hydrated.
  • The perch-* classes — layout + brand helpers the runtime injects. 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 network. fetch, XMLHttpRequest, WebSockets, and beacon requests are blocked by connect-src 'none'. Data only ever arrives through onData.
  • No external assets. Images must be data: URIs; fonts must be data: 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.

This is the D7 rule: a widget is a pure function of injected data, holds no token, and does no I/O. The native app does all networking on your behalf.

What the runtime provides

ProvidedWhat it is
window.PERCHThe data + signals bridge (reference).
--perch-* CSS variablesBrand colors + safe-area insets (theming).
perch-safe, perch-display, perch-content, perch-glowLayout + glow helper classes.
perch-landscape / perch-portraitOrientation classes toggled on <body> automatically.

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.