Documentation

TypeScript Workbench previews

A Workbench preview renders a component or page from your own source code, in named states, next to your design pages. You define it in a .workbench.ts or .workbench.tsx file, with an adapter for HTML, React, Vue, Astro, React Native Web, or a technology you register yourself.

Like a story in Storybook, a preview renders your real component or page, but not a running copy of your app. Each state supplies the data the page would load, through props, providers, or mocked requests, and Workbench keeps it on the canvas: its links open other previews, and everything else it would do on a real site, such as following a route, submitting a form, or starting a download, is recorded under Actions instead. See Preview data, mocks, and actions.

This page covers what every preview shares: definitions, states, controls, lifecycle hooks, the command-line tools, and portable exports. Each framework has its own guide:

Adapter Renders Guide
react A React component, with inputs as props React
react-native-web A React Native component through React Native Web React Native Web
vue A Vue 3 single-file component or module, with inputs as props Vue
astro An Astro component or page, rendered on the server Astro
html An HTML file with its styles and scripts, or a module that mounts into the canvas HTML
Your own Anything you can mount from a browser module Custom adapters

You need:

  • workbench.yaml at the project root. name: Acme alone is enough when previews supply every page.
  • A trusted workspace, since previews run project code.
  • The framework you render with, installed in your project. Workbench compiles TypeScript, JSX, and Vue files itself, but uses your project’s React, Vue, Astro, or React Native Web. It never installs or replaces them.

Add a preview

  1. Write a definition next to the component, such as src/Button.workbench.ts:

    import { definePreview } from '@canonic2/workbench';
    
    export default definePreview({
      id: 'components/button',
      title: 'Components/Button',
      adapter: 'react',
      source: { entry: './Button.tsx', export: 'Button' },
      inputs: { label: 'Continue', disabled: false },
      controls: {
        label: { type: 'text' },
        disabled: { type: 'boolean' },
      },
      states: {
        default: {},
        disabled: { inputs: { disabled: true } },
      },
    });

    Workbench supplies @canonic2/workbench when it compiles the file. For TypeScript and your editor, install it as a development dependency; see Types. This example renders a React component; each framework guide has a complete example of its own.

  2. Run Workbench: Refresh Pages, or save workbench.yaml. The preview appears in the page list with its states, and opens on the canvas like any other page.

Workbench finds every *.workbench.ts and *.workbench.tsx file in the project. It skips hidden folders and node_modules, dist, build, and coverage. To choose other files or turn previews off, see Previews in the configuration reference.

The title places the preview in the sidebar. Its first segment names the collection, its last the page, and any segments between them a group: Components/Forms/Button is Button, in a Forms group, in the Components collection. A title without / goes in a Previews collection. Without a title, the id is used.

Set icon: 'monitor' on definePreview for a page preview, or use previews.icons in workbench.yaml to assign icons by title prefix. A definition’s icon wins over the longest matching prefix, then previews.icon, then the default component. Collection icons are configured separately; an icon-only handwritten collection can style a collection shared with Storybook. See preview icon settings and collection precedence.

Collections and groups with exactly matching names are shared with your hand-written pages and imported Storybook catalogs. For example, a preview titled Web App/Pages/Jobs and a story titled Web App/Pages/Account appear in one Pages group under Web App.

A file that fails to load or has an invalid definition is reported with the other configuration problems. The remaining previews still load.

To place a preview yourself, give a page in workbench.yaml the definition as its src, such as src: src/Button.workbench.ts. The page keeps its place and label, and takes its states from the definition. The file must still match the discovery patterns.

A .workbench.ts or .workbench.tsx file can also default-export defineDocs({...}) from @canonic2/workbench instead of definePreview, to declare a Markdown page: Markdown with live examples and the docs lenses that render them. It is discovered with the previews and placed by its title the same way, except that a title without / goes in a Docs collection. If workbench.yaml also lists its Markdown file, the listed page is used instead. To give a preview page its own docs, set docs on its entry in workbench.yaml. See Docs.

Name the preview lens

To rename the preview’s own lens, set previews.lensLabel in workbench.yaml:

previews:
  lensLabel: Design

This changes the default Workbench label to Design for discovered previews. An explicitly listed page can set its own lensLabel to override that value. These are YAML settings, separate from the preview definition’s title, which places and names the page in the sidebar. They rename the existing lens without adding a second button. If a preview is used as a kind: workbench implementation, set that implementation’s label instead. See Customize lens labels.

Define a preview

Key Description
id Required. Kebab-case segments separated by /, such as components/button. Unique in the project, across previews and docs pages.
title Where the preview appears in the sidebar. Defaults to id.
adapter Required. html, react, vue, astro, react-native-web, or a registered name.
source Required. entry is the file to render, relative to the definition and inside the project. export names its export and defaults to default.
inputs Data passed to the source: props for React and Vue, Astro.props for Astro.
controls Inputs you can edit in Preview controls. See Controls.
states A map of kebab-case state IDs to states.
links The addresses the source links or submits to, each mapped to the preview it opens. See Links and navigation.
requests Answers to the page’s fetch and XMLHttpRequest calls. See Request mocks.
sizes The artboard sizes the preview supports: any of fit, laptop, mobile, and resizable. See Sizes.
docs Text shown under Documentation in Preview controls.
fixtures, globals Data and settings that environments, hooks, and request handlers read from the context. See Fixtures and globals.
styles Stylesheets to load with the preview, relative to the definition.
assets Extra local files or folders the source or a compiler plugin reads at runtime, relative to the definition.
environment A module, relative to the definition, that wraps or configures every state. See Environments.
setup, play, ready Lifecycle hooks for every state.

inputs, fixtures, and globals must be plain, cloneable data.

States

Each state can set:

  • label, the name in the sidebar. It defaults to the ID in title case, so is-busy reads Is Busy.
  • inputs, fixtures, and globals, which override the preview’s values key by key; see What you can set, and where.
  • source, to render a different entry or export in this state.
  • requests, tried before the preview’s request mocks.
  • setup, play, and ready hooks.

The first state is the one a preview opens in. Every state also opens directly with ?state=<id> on the preview’s address; an unknown state shows an error. A preview without states has one state, default.

SVG icon sprites registered when your preview module loads remain available when you switch states or return to that preview.

Returning to a preview preserves form edits, filters, scroll, open menus, and live application state. The canvas keeps up to three inactive sessions for 15 minutes after leaving them. Reload starts the current session again; Reset state restores its declared inputs. A source change rebuilds the preview. Closing the canvas releases its sessions.

The <html> element carries data-wb-state with the current state ID, so CSS keyed off it works as it does in design pages.

Adapters

adapter picks how the source renders. The built-in adapters are html, react, vue, astro, and react-native-web; see the table at the top of this page for each one’s guide. Any other name must be registered.

Data, controls, and actions

A preview gets the data its real page would load, and you can change it per state and while you review. Preview data, mocks, and actions covers all of it:

  • Inputs and controls: props for the component, and fields under Preview controls that edit them live. Reset state drops your edits and the action log.
  • Fixtures and globals: data and settings for the code around the component.
  • Environments: providers, plugins, stores, and setup, for one preview or, from workbench.config.ts, for the whole project.
  • Request mocks: answers to the page’s own fetch and XMLHttpRequest calls, with empty, loading, error, and offline states.
  • Actions: what the page tried to do, listed under Actions in Preview controls.
  • Links and navigation: links that open other previews, with the top bar’s Actions switch on.

Lifecycle hooks

Hooks and sources receive a context with id, state, inputs, fixtures, globals, an abort signal, action(name, ...values), navigate(to), and error(error). Examples in a page’s docs get the same context, with empty inputs, fixtures, and globals.

  • setup(context) runs before the source mounts and may return a cleanup function. It runs from the project’s environment, then the preview’s environment, then the preview, then the state. Request mocks are already active, so setup can fetch.
  • play({ canvas, ...context }) runs after mounting, for an interaction sequence such as opening a menu. The preview’s runs before the state’s.
  • ready(context) runs last, for anything the preview must wait for.

An environment module can export setup and ready too, and a hook for its adapter, such as wrap for React.

A preview is ready once its hooks finish, its fonts load, and its visible images load or 3 seconds pass. Screenshots and handoffs wait for that, and fail with the rendering error, or with Workbench preview did not finish rendering before capture after 8 seconds.

Before a preview renders again, its signal is aborted and cleanups run in reverse order. Tie subscriptions, timers, and listeners to context.signal or a cleanup. A failed cleanup doesn’t stop the others, and Reset state recovers after a failed render.

Errors are shown in the preview’s frame: thrown errors, rejected promises, React error boundaries, Vue’s error handler, and anything a custom adapter passes to context.error(error).

Saving the definition or any file it uses reloads the open preview within a second or so. This is a full reload, not hot module replacement. If you add or remove a definition, or change its title or states, run Workbench: Refresh Pages to update the sidebar.

Workbench compiles previews with its bundled native compiler. The first build depends on the size of the source and its imports; unchanged builds are cached across worker restarts. Source and configuration changes trigger a fresh build. Your project does not need to install a compiler. Live source maps remain available to developer tools without adding their size to the preview script.

Register other technologies

Adapter names aren’t a fixed list. workbench.config.ts at the project root, or the file named by previews.config, registers adapters and sets compiler plugins, aliases, dedupe, resolveExtensions, and define for every preview. An adapter whose name isn’t built in or registered fails with Unknown adapter. See Custom adapters.

Compare a design with a preview

A page can show a preview as a lens next to its design, with a workbench implementation. See Compare a design with a Workbench preview.

Types

Install the authoring types so tsc and your editor resolve @canonic2/workbench:

npm install --save-dev @canonic2/workbench

Use pnpm add -D or yarn add -D with those package managers. The types check that each state’s inputs match the preview’s. Workbench still compiles previews with its own copy, so the installed version affects types only; keep it at the version of your extension to type the newest fields.

Without an installed package, the init command below writes the same types to a workbench-env.d.ts file. Include that file in your tsconfig.json.

Command-line tools

The extension includes a command-line tool at preview/cli.cjs in its install folder. Run it with Node 24 or later:

node ~/.vscode/extensions/canonic.canonic-workbench-<version>/preview/cli.cjs check .
Command What it does
init <project> Writes workbench-env.d.ts, the types for @canonic2/workbench, and a workbench.yaml named after the folder. Existing files are left alone.
check <project> Finds and compiles every preview without opening a browser. Prints Built <id> for each, lists every failure, and exits with an error if there were any.
build <project> [output] Writes the standalone viewer to output, or workbench-static in the project. The folder must be empty or missing. Successful previews are written even when others fail; failures are listed and the exit code is nonzero.

Portable exports

Export… › Source package (ZIP) includes compiled previews in a browser/ folder, alongside their editable sources. See Design-system export. Serve the extracted folder with any static HTTP server and open browser/index.html. The build command writes the same viewer on its own. Viewing it needs no Workbench, Electron, package installation, or build step.

The viewer has:

  • a searchable list of previews, and the build warnings, if any,
  • State and Size menus, with Width and Height for Resizable,
  • Reload, and Preview controls with the same inputs, reset, actions, and documentation as the canvas,
  • Open preview, which opens the preview on its own page. That page also accepts ?state=<id>.

The viewer’s address keeps the preview, state, size, and resizable dimensions, so you can copy it to share a selection.

Astro previews carry every authored state but no input controls; see Astro portable exports.