Skip to main content

The @widget runtime

Everything your widget needs is imported from the @widget module:

import { onLoad, showTemplate, registerBinding, Event, props, Template } from '@widget';

This page is a reference. If you're just starting, follow Build your first widget first.

The mental model​

A widget has two kinds of code:

  • The core (.ts) — plain code that runs once when the widget loads. This is where you call onLoad, choose which template to show, and register your event bindings. The core is not React.
  • Templates (.tsx) — React components that draw the widget. Inside a template you use hooks (useProp, useLatestEvent, …) that re-render when their value changes.

Each template file becomes a member of Template — main.tsx is Template.Main, crash.tsx is Template.Crash.

A minimal widget:

// main.ts  (the core)
import { onLoad, showTemplate, setCrashTemplate, Template } from '@widget';

onLoad(() => {
setCrashTemplate(Template.Crash); // shown if a template throws
showTemplate(Template.Main); // paint the Main template
});
// main.tsx  (a template)
import { useProp } from '@widget';

export default function Main() {
const text = useProp('message');
return <h1>{text}</h1>;
}

Lifecycle​

FunctionWhen it runs
onLoad(handler)Once, when the widget starts. The handler receives a context with the current binding snapshot, the configured props, and the canvas size.
onUpdate(handler)Whenever the streamer changes a property value.
onResize(handler)When the widget's size changes.

Templates​

FunctionPurpose
showTemplate(Template.Name, props?)Paint a template to the live overlay and the editor preview.
showEditorTemplate(Template.Name, props?)Paint a template only on the Overlay Editor design canvas (use it to show sample data while a streamer designs).
showSettingsPreviewTemplate(Template.Name)Paint a template in the settings modal preview (the settings-preview render context). Until called, the preview shows the editor template.
showDemoTemplate(Template.Name, props?)Paint a template on the widget page's demo view (and the Widget Creator preview with Page view on). Until called, those views show the showTemplate template, and once called later showTemplate calls are ignored there. Call it in onLoad.
setCrashTemplate(Template.Name)A fallback template shown if your main template throws — so a bug shows a graceful placeholder instead of nothing.

Reading configuration & state​

In the core, read configured properties off the props object:

import { props } from '@widget';
const colour = props.accentColour;

In a template, use hooks so the UI updates when values change:

HookReturns
useProp(key)A configured property value.
useSize()The widget's current { width, height }.
useStore(key)A value from your widget's saved storage.
useCounter(name)A named counter you can increment across sessions.
useReducedMotion()true if the viewer prefers reduced motion — honour it for accessibility.
useSettingsPreviewFocus(){ tab, field } in the settings modal preview: the open tab's id and the property under the pointer or holding focus (its JS name). Either may be null. Always { tab: null, field: null } elsewhere.

A group-list property returns an array of { uuid, kind, values } entries, in order, with disabled entries removed. Entries of a region type also carry their geometry as fractions of the widget's box: rect: RegionRect ({ x, y, w, h }) for a box, point: RegionPoint ({ x, y }) for a point, or points: RegionPoint[] for a path. Multiply by useSize() for pixels. See Ordered lists.

See Property schema for how properties are defined and named.

Events​

Declare events on the Bindings surface, then consume them:

APIUse inPurpose
registerBinding(Event.X, handler)coreRun a handler each time a moment event fires.
useEvent(Event.X, handler)templateSame, as a hook.
useLatestEvent(Event.X)templateThe most recent payload for an event (or a live value's current value).
useEventLog(Event.X, limit?)templateThe last N occurrences — great for a chat or event feed.
useBinding(path, selector?)templateRead a specific live value by path, with an optional selector.
simulateEvent(Event.X, payload?)anyFeed the widget a sample event, with payload merged over the built-in sample. Works only in the settings preview and the Workshop preview; elsewhere it warns once and does nothing. See Settings modal.

The full list of events and their payloads is in Event bindings.

Render contexts​

A widget renders in a few places: the live overlay, the Overlay Editor canvas, the Workshop's own preview, and the settings-preview context, the preview inside a settings modal. showTemplate paints the live overlay and the editor preview, showEditorTemplate the design canvas, and showSettingsPreviewTemplate the settings preview. showDemoTemplate paints the demo view of a widget page, where a container has no child widgets to show.

Assets & audio​

APIPurpose
asset('name')The URL of an asset you uploaded on the Assets surface.
playSound('name', options?)Play an uploaded sound; returns a handle you can stop.
import { asset } from '@widget';
export default function Main() {
return <img src={asset('logo')} alt="" />;
}

Time & animation​

These hooks make smooth, time-based widgets easy:

HookPurpose
useNow(intervalMs?)The current time, ticking — for clocks.
useElapsed(since, intervalMs?)Seconds elapsed since a timestamp — for uptime.
useCountdown(until, intervalMs?)Seconds remaining until a timestamp — for ad/goal timers.
useTween(target, options?)A value that eases toward target — smooth number transitions.
useRaf(callback)A per-frame callback for custom animation.
useInterval(fn, ms) / useTimeout(fn, ms)Cleanup-safe timers.
usePresence()The widget's enter/active/leave phase, for entrance/exit animations.

Chat & emotes​

Building a chat widget is well-supported:

APIPurpose
ChatMessageA ready-made component that renders a chat message with emotes, badges and an attached GIF.
ChatBadgesRenders a chat author's badges.
ChatGifRenders a message's attached GIF on its own, and nothing when there isn't one.
parseChatMessage(chat, emotes?, opts?)Tokenise a message into text / emote / mention / link / GIF runs for custom rendering.
getChatGif(chat)The message's attached GIF, or undefined — the "is this a GIF message?" test.
isGif(chat)true when the message is a GIF.
isAllEmotes(chat, emotes?)true when the message is nothing but emotes — handy for showing them big.
trimBits(chat)The message text with its cheers (Cheer100) removed.
useChannelEmotes()The channel's current emote sets (Twitch + 7TV + BTTV + FFZ).
pickEmoteUrl(urls, targetPx) / pickBadgeUrl(badge, size)Choose the best-resolution image for a size.

GIF messages​

Viewers can attach a GIF with Twitch's GIF picker. It arrives as a gif fragment and is also hoisted to chat.gif ({ id, url }) for convenience, so if (chat.gif) tells you a message is a GIF message.

<ChatMessage> draws it inline at 160px by default — set gifSize to resize it. To put the GIF on its own row instead, turn the inline copy off and place it yourself:

<div>
<ChatMessage chat={chat} includeGif={false} />
<ChatGif chat={chat} size={200} />
</div>

Twitch requires the GIF url be used unmodified, so render it as given — don't proxy, resize or re-host the file itself.

Emote-only messages​

isAllEmotes is true when a message holds only emotes, with nothing but spaces between them. 7TV, BTTV and FFZ emotes arrive as plain text, so pass the channel's emote sets for them to count:

const emotes = useChannelEmotes();
const big = isAllEmotes(chat, emotes);

A word, a mention, a link, a cheer or a GIF makes it false.

Cheers​

A cheer such as Cheer100 arrives as a text fragment carrying a cheermote marker (prefix and bits), so it renders as plain text unless you drop it. trimBits(chat) returns the message without its cheers, turning Cheer100 great stream Cheer50 into great stream. It only removes words Twitch marked as cheers, so something like abc100 stays. A GIF's placeholder text is left out too, and a message that was only cheers comes back as an empty string.

Built-in components​

The runtime ships a few components so you don't reinvent the basics:

ComponentPurpose
TextStyled text with outlines, anchoring and colour fills.
StyledTextText drawn in a text style. See StyledText.
ImageAn image with fit, radius and other display options.
ProgressBarA configurable fill bar.

StyledText​

StyledText draws text in a text style: outlines, gradients, shadow, glow and more. Declare a textStyle property and pass its value as look:

import { StyledText, useProp } from '@widget';

export default function Main() {
const look = useProp('title-style');
return <StyledText look={look} text="Starting soon" size={96} />;
}
PropMeaning
lookThe style to draw in. null draws nothing.
textThe text.
sizeFont size in pixels.
widthWrap width in pixels. Without it, the text doesn't wrap.
alignleft, center (the default) or right.
marksMarked words, as { start, end } ranges into text. They draw in the style's marked look.
lineHeightA multiple of size. The default is 1.2.
fontA font family to use instead of the style's.
weightA weight to use instead of the style's.
markedFillA #rrggbb colour for marked words when the style has none of its own.

The text is drawn on a canvas that is a little larger than its layout box, so glows and shadows aren't clipped. It waits for the font to load, then redraws.

Utilities​

APIPurpose
formatNumber/time formatting helpers.
band(value, breakpoints, fallback)Map a number into one of several buckets (e.g. small/medium/large).
regionToPixels(item, size)A region entry's rect, point or points in pixels.
pointInRegion(item, x, y, size?)Whether a spot is inside a region entry: a box, or a polygon (a closed path). Pixels when size is given, else fractions.
randomPointInRegion(item, rng?)A random spot inside a box or polygon (on the line for a two-point path).
pointAlongPath(points, t, { closed?, size? })The spot a fraction t of the way along a path, with angle, the direction of travel. t wraps on a closed path.
escapeHtml(text) / sanitizeHtml(html)Make untrusted text safe to render.
RUNTIME_VERSIONThe runtime API version your widget is running against.
Render untrusted text safely

Chat messages, user input and viewer names come from the public. If you ever insert them as HTML, run them through sanitizeHtml (or just render them as text and let React escape them). Never trust raw input.

Advanced: container widgets​

A widget can be a container that holds other widgets. Containers use a few extra APIs:

APIPurpose
useChildren() / childrenThe child widgets, so the container can position or animate them.
setSubtreeContext(next)Publish live values to nested children (they read them via the variable picker).
useSubtreeContext(path?)Read a value published by an ancestor container.
BindA component that displays a bound value inside a template.

Containers are how the built-in Alert, Group and Show / Hide primitives work. They're an advanced topic — start with leaf widgets.

Next​