Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Creating Widgets

Define home screen widgets using the Widget() function.

Status: the full Widget({...}) snippets on this page compile-link cleanly on the host LLVM target via docs/examples/widgets/snippets.ts, so the API shapes are verified against the codegen. The actual cross-compile targets (--target ios-widget/android-widget/watchos-widget/wearos-tile) still aren’t driven by the doc-tests harness — each requires --app-bundle-id and a platform SDK (#194). For the canonical end-to-end shape see examples/widget_demo.ts. Fragments below that show partial syntax (just the entryFields object, just a render: body, etc.) are rendered as plain text — the full declarations they appear inside are covered by the verified anchors.

Widget Declaration

Widget({
    kind: "WeatherWidget",
    displayName: "Weather",
    description: "Shows current weather",
    entryFields: {
        temperature: "number",
        condition: "string",
        location: "string",
    },
    render: (entry) =>
        VStack([
            HStack([
                Text(entry.location),
                Spacer(),
                Image("cloud.sun.fill"),
            ]),
            Text(`${entry.temperature}°`),
            Text(entry.condition),
        ]),
})

Widget Options

PropertyTypeDescription
kindstringUnique identifier for the widget
displayNamestringName shown in widget gallery
descriptionstringDescription in widget gallery
entryFieldsobjectData fields with types ("string", "number", "boolean", arrays, optionals, objects)
renderfunctionRender function receiving entry data, returns widget tree. Optional 2nd param for family.
configobjectConfigurable parameters the user can edit (see below)
providerfunctionTimeline provider function for dynamic data (see below)
appGroupstringApp group identifier for sharing data with the host app

Entry Fields

Entry fields define the data your widget displays. Each field has a name and type:

entryFields: {
  title: "string",
  count: "number",
  isActive: "boolean",
}

Array, Optional, and Object Fields

Entry fields support richer types beyond primitives:

entryFields: {
  items: [{ name: "string", value: "number" }],  // Array of objects
  subtitle: "string?",                             // Optional string
  stats: { wins: "number", losses: "number" },     // Nested object
}

These compile to a Swift TimelineEntry struct:

struct WeatherEntry: TimelineEntry {
    let date: Date
    let temperature: Double
    let condition: String
    let location: String
}

Conditionals in Render

Use ternary expressions for conditional rendering:

Widget({
    kind: "ConditionalWidget",
    displayName: "Conditional",
    description: "Renders based on entry data",
    entryFields: {
        isActive: "boolean",
        count: "number",
    },
    render: (entry) =>
        VStack([
            Text(entry.isActive ? "Active" : "Inactive"),
            entry.count > 0 ? Text(`${entry.count} items`) : Spacer(),
        ]),
})

Template Literals

Template literals in widget text are compiled to Swift string interpolation:

Widget({
    kind: "TemplateLiteralWidget",
    displayName: "Template Literal",
    description: "Template literals compile to Swift string interpolation",
    entryFields: {
        name: "string",
        score: "number",
    },
    render: (entry) =>
        // Template literal: `${entry.name}: ${entry.score} points`
        // Compiles to: Text("\(entry.name): \(entry.score) points")
        Text(`${entry.name}: ${entry.score} points`),
})

Configuration Parameters

The config field defines user-editable parameters that appear in the widget’s edit UI:

Widget({
    kind: "CityWeather",
    displayName: "City Weather",
    description: "Weather for a chosen city",
    config: {
        city: { type: "string", displayName: "City", default: "New York" },
        units: {
            type: "enum",
            displayName: "Units",
            values: ["Celsius", "Fahrenheit"],
            default: "Celsius",
        },
    },
    entryFields: { temperature: "number", condition: "string" },
    render: (entry) => Text(`${entry.temperature}° ${entry.condition}`),
})

Provider Function

The provider field defines a timeline provider that fetches data for the widget:

Widget({
    kind: "StockWidget",
    displayName: "Stock Price",
    description: "Shows current stock price",
    config: {
        symbol: { type: "string", displayName: "Symbol", default: "AAPL" },
    },
    entryFields: { price: "number", change: "string" },
    provider: async (config) => {
        const res = await fetch(`https://api.example.com/stock/${config.symbol}`)
        const data = await res.json()
        return { price: data.price, change: data.change }
    },
    // Inline-options form — the chain form `.font("title")` parses but is
    // dropped at HIR-lowering time (#195).
    render: (entry) =>
        VStack([
            Text(`$${entry.price}`, { font: "title" }),
            Text(entry.change, { color: "green" }),
        ]),
})

Note: the chain-style modifiers (.font("title").color("green")) parse but are dropped at HIR-lowering time — see #195. The verified extract above uses the inline-options form Text("...", { font: "title" }), which is what actually round-trips through the widget codegen.

Placeholder Data

When the widget has no data yet (e.g., first load), the provider can return placeholder data by providing a placeholder field:

Widget({
  kind: "NewsWidget",
  entryFields: { headline: "string", source: "string" },
  placeholder: { headline: "Loading...", source: "---" },
  // ...
});

Family-Specific Rendering

The render function accepts an optional second parameter for the widget family, allowing different layouts per size:

render: (entry, family) =>
  family === "systemLarge"
    ? VStack([
        Text(entry.title).font("title"),
        ForEach(entry.items, (item) => Text(item.name)),
      ])
    : HStack([
        Image("star.fill"),
        Text(entry.title).font("headline"),
      ]),

Supported families: "systemSmall", "systemMedium", "systemLarge", "accessoryCircular", "accessoryRectangular", "accessoryInline".

App Group

The appGroup field specifies a shared container for data exchange between the host app and the widget:

Widget({
  kind: "AppDataWidget",
  appGroup: "group.com.example.myapp",
  // ...
});

Multiple Widgets

Define multiple widgets in a single file. They’re bundled into a WidgetBundle:

Widget({
  kind: "SmallWidget",
  // ...
});

Widget({
  kind: "LargeWidget",
  // ...
});

Next Steps

  • Components — Available widget components and modifiers
  • Overview — Widget system overview