Skip to content
Widget Dev pages

Updated

View MarkdownOpen in ChatGPTOpen in Claude

Widget Previews

Someone browsing for a widget sees its name, its icon and a sentence. That is not much to decide on. Previews replace that with pictures of your widget doing its job, on the Hub page and in the dashboard’s widget browser, before anyone installs it.

You write a short list of showcase states. Everything else happens for you: each one is pictured in both light and dark, and the images refresh every time you publish a new version. There is nothing to upload and nothing to keep in sync.

Declare your examples

Add examples to your manifest in defineWidget. Each entry is a config to render, the size to render it at, and a label for your own reference.

export default defineWidget<LightConfig>({
  manifest: {
    name: "Light",
    minSize: { w: 1, h: 1 },
    maxSize: { w: 4, h: 4 },
    sdkVersion: "^1.7.0",
    examples: [
      {
        label: "Single light",
        size: { w: 2, h: 2 },
        config: { title: "Reading Lamp", entityIds: ["light.bedroom_ceiling"] },
      },
      {
        label: "Room group",
        size: { w: 3, h: 2 },
        config: {
          title: "Living Room",
          entityIds: ["light.living_room_main", "light.kitchen_counter"],
        },
      },
    ],
  },
  configSchema,
  component: LightWidget,
});
  • config is a real config for your widget, the same shape a user would end up with. It has to satisfy your configSchema.
  • size is in grid tiles, the same units as minSize and maxSize. Picture each state at the size it actually looks good.
  • label is optional and for your own orientation while writing them.

The first example is the one used as your widget’s thumbnail, so lead with the state that best represents it.

Pick your best few

Three or four is usually right. You are showing range, not documenting every option: the states worth picturing are the ones that look meaningfully different from each other.

Up to 20 examples are pictured. If you declare more, the extras are not rendered, so keep the list to the states you actually want people to see.

Use the demo home

Your examples reference entities from GlassHome’s demo home, so they render with sensible data instead of empty states. Some of what is available:

Domain Examples
light light.bedroom_ceiling, light.living_room_main, light.kitchen_counter, light.hallway, light.bathroom, light.studio_rgb, light.desk_rgb
climate climate.living_room_thermostat, climate.bedroom_ac
cover cover.garage_door, cover.kitchen_blinds, cover.living_room_blinds, cover.bedroom_curtains
lock lock.front_door_lock, lock.back_door_lock
switch switch.coffee_machine, switch.fan_living_room
media_player media_player.living_room_speaker

There are around 50 in total, including a generous set of sensor entities for readouts and graphs. The demo home is the same one the dashboard’s demo mode uses, so anything you can build against there works here.

light.studio_rgb and light.desk_rgb are colour-capable, which is handy if your widget has anything to show off in colour.

See them before you publish

Render your examples locally and look at them:

bun widget preview

Images land in preview/, named <widget>-<label>-<theme>.png, one pair per example. Pass a widget name to do just one:

bun widget preview light

This uses a real browser, which is not installed by default. If it is missing, the command tells you exactly this:

bun add -d playwright && bunx playwright install chromium

Iterate here, not by publishing. Local previews are rendered the same way as the published ones, so what you see in preview/ is what people will see.

Check other sizes and states

People resize widgets, so look past your examples’ own sizes. --sizes renders each example at the pixel sizes you name, into preview/sweep/, with a contact sheet per example and theme:

bun widget preview light --sizes 150x156,340x242 --theme dark
bun widget preview light --sizes grid

grid covers the common dashboard sizes, skipping heights your manifest does not allow. Other options change what is on screen before the shot:

Option Does
--config '<json>' merges over the example’s config
--service 'light.turn_off|light.hallway|{}' calls a service on the demo home first; repeat for more
--at 2026-06-15T21:00:00 sets the clock, for anything that follows the time of day
--click '<selector>' taps an element, for open or pressed states
--eval '<expression>' prints a value per render; root is your widget’s shadow root
--example 0, --theme light narrow the run

These images are for you. Hub renders your published previews from examples alone.

Publish

Nothing extra to do.

bun widget publish --name light --bump patch --scope your-scope

Your previews appear on the widget’s Hub page and in the dashboard’s widget browser shortly after. Each version keeps its own images, so an older version still shows what it looked like.

If a set of images cannot be produced, your version still publishes normally and simply shows its icon instead. Publishing never waits on previews and never fails because of them.

No previews is fine

Most widgets in the catalogue have none, and nothing marks them as incomplete. Widgets published before examples existed cannot have them, since a published version is frozen. Adding examples and publishing a new version is how any widget gets them.

Checklist

  1. 1

    Add examples to your manifest

    Three or four states that look meaningfully different. Best one first.

  2. 2

    Point their configs at demo entities

    So they render with real-looking data.

  3. 3

    Run bun widget preview

    Open preview/ and check both themes.

  4. 4

    Publish

    The images follow automatically.