Skip to content

Embedding SayShape ​

bash
npm install sayshape

It works with any bundler (Vite, webpack, esbuild) and has no runtime dependencies. TypeScript types come with it.

ts
import { SayShape, coreComponents } from "sayshape";
import { mapComponents } from "sayshape/map";

const canvas = document.querySelector("canvas")!;
const ui = new SayShape(canvas, {
  components: [...coreComponents, ...mapComponents],
});

ui.setSpec(`state count=0
card
  text "Clicked {count} times"
  button "Click me" on=inc:count`);

The canvas fills its parent's width, follows it when the parent resizes, and grows to fit its content. It's focusable, and draws its own focus ring around the focused element.

Options ​

OptionDefaultDescription
componentsrequiredThe components this instance understands
theme"auto"A Theme, or "auto" to follow light and dark mode and --sayshape-* CSS variables (Themes)
widthparent widthA fixed width in CSS pixels
minHeight0Minimum canvas height
padding20Space around the whole screen
animatetrueFade new elements in; off when the person prefers reduced motion
imageHosts[]Hosts images may load from besides your own site (Images)
loadImagean <img> with CORSCustom image loading, for tests, server rendering or signed URLs
onWarningconsole.warnProblems found while running, like a blocked image
editoran <input> over the fieldHow text fields are typed into; sayshape/testing passes a fake one
todaythe device's local dayToday's date as () => "YYYY-MM-DD", for date pickers
viewportfrom the windowThe visible part of the canvas, for placing popups

Methods ​

MethodDescription
setSpec(text, { resetState? })Replace the whole spec
beginStream({ keepState? })Start a streamed spec; clears state unless keepState
push(chunk)Append streamed text; only complete lines render
endStream()Finish the stream and render the last line
on(event, handler)Subscribe to an event; returns an unsubscribe function
state.get(key), state.set(key, value)Read or change state from your app
go(screen), back()Show another screen, or the previous one
screens, currentScreen, canGoBackThe spec's screen names, the visible one, and whether back() can move
notify(text)Show a short message, as notify: does
imagesLoaded()Resolves once every requested image has loaded or failed
copyText()The screen as plain text, for a Copy button: fields as Label: value, tables and chart data as tab-separated rows that paste into a spreadsheet. Buttons are left out and passwords never included
setTheme(theme)Switch themes
find(type)All nodes of a type, e.g. ui.find("button")
diagnostics, spec, treeThe current problems, source text and parsed tree
inspectorDebugging overlays and frame capture (Architecture)
destroy()Remove listeners and stop rendering

Events ​

EventPayloadWhen
action{ name, value, node }An emit: action ran
change{ key, value }A state value changed, including values a spec declares
diagnosticsDiagnostic[]The spec's problems changed
renderMetricsA frame was painted (element count and timings)
navigate{ screen, from }The visible screen changed
notify{ text }A message was shown
inspect{ node, box } or nullInspect mode selected something

Lifecycle in a component framework ​

Create the instance once the canvas is in the document, and destroy it when the canvas goes away. In Vue, for example (this site does exactly this):

ts
let ui: SayShape | null = null;
onMounted(() => {
  ui = new SayShape(canvasRef.value!, { components: coreComponents });
  ui.setSpec(spec);
});
onBeforeUnmount(() => ui?.destroy());

With server-side rendering or static generation, import and construct SayShape only in the browser: it needs a canvas, window and fonts.

Fonts ​

The default theme draws with Instrument Sans and IBM Plex Mono, falling back to system fonts. Load them in your page if you want that look; the engine lays out again when web fonts finish loading. Canvas text doesn't start a font download on its own, so load them before the first render, for example with document.fonts.load('400 15px "Instrument Sans"').

Keyboard and touch ​

Tab and Shift+Tab move between interactive elements, and Enter or Space presses the focused one. Sliders also follow the arrow keys, Page Up, Page Down, Home and End; selects and date pickers open with Enter, Space or the arrows. On touch screens, vertical swipes still scroll the page and pinches still zoom, while horizontal drags go to the canvas, so sliders can be dragged.

SayShape v0.1 is an early prototype under the MIT License. Status · GitHub