Morphos & Kosmesis are here.Explore
PraxisJS

Storybook

@praxisjs/storybook — Storybook framework adapter for PraxisJS. Write stories with full reactivity, HMR, and source preview.

Storybook

@praxisjs/storybook is a Storybook framework adapter that mounts PraxisJS components into the canvas with full reactivity, HMR, and source preview. Requires Storybook ≥ 8 and @storybook/builder-vite.

npm install -D @praxisjs/storybook
pnpm add -D @praxisjs/storybook
yarn add -D @praxisjs/storybook
bun add -d @praxisjs/storybook

Setup

.storybook/main.ts

import type { StorybookConfig } from '@praxisjs/storybook'

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.tsx'],
  addons: ['@storybook/addon-essentials'],
  framework: {
    name: '@praxisjs/storybook',
  },
}

export default config

The adapter automatically wires up @praxisjs/vite-plugin with HMR enabled. You do not need to add the Vite plugin separately in .storybook/main.ts.

.storybook/preview.ts

No extra setup needed. The preset registers @praxisjs/storybook/preview, which provides renderToCanvas and a default render, so a preview.ts only holds your own parameters and decorators.

A story without a render function mounts the meta's component with the story's args as props:

const meta: Meta = { title: 'Components/Badge', component: Badge }
export default meta

export const Primary: StoryObj = { args: { label: 'New' } } // <Badge label="New" />

A meta with neither component nor a story render fails with an error that names the story's title.


Writing stories

Import Meta and StoryObj from @praxisjs/storybook for full type safety:

import { StatefulComponent } from '@praxisjs/core'
import { Component, State } from '@praxisjs/decorators'
import type { Meta, StoryObj } from '@praxisjs/storybook'

@Component()
class Counter extends StatefulComponent {
  @State() count = 0

  render() {
    return (
      <div>
        <p>{() => this.count}</p>
        <button onClick={() => { this.count++ }}>+</button>
      </div>
    )
  }
}

const meta: Meta = {
  title: 'Components/Counter',
  tags: ['autodocs'],
}
export default meta

type Story = StoryObj

export const Default: Story = {
  render: () => <Counter />,
}

export const StartAt10: Story = {
  render: () => <Counter />,
  // Pass props via render args:
  // render: (args) => <Counter initialCount={args.count} />,
}

Composition

Storybook Composition lets you view stories from other published Storybooks — including ones built with other frameworks — inside your PraxisJS Storybook's sidebar. Add refs to .storybook/main.ts:

import type { StorybookConfig } from '@praxisjs/storybook'

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.tsx'],
  framework: {
    name: '@praxisjs/storybook',
  },
  refs: {
    'design-system': {
      title: 'Design System',
      url: 'https://your-design-system.example.com',
      expanded: false,
      sourceUrl: 'https://github.com/your-org/design-system',
    },
  },
}

export default config

Each key becomes an entry in the sidebar. expanded controls whether the ref's stories are expanded by default (defaults to true), and sourceUrl is an optional link back to the ref's repository. Use { disable: true } instead of { title, url } to turn off a ref that a shared preset registered for you.


Type reference

ExportDescription
MetaType for the default story export (ComponentAnnotations)
StoryObjType for named story exports (StoryAnnotations)
StorybookConfigType for .storybook/main.ts
StorybookRefsType for the refs field — see Composition
PraxisRendererLow-level renderer type (rarely used directly)

How it works

  • renderToCanvas mounts the story with @praxisjs/runtime's render(). PraxisJS has no re-render pass, so every render of a canvas (new args from the controls, forceRemount, HMR) first disposes what that canvas holds: its effects stop and onUnmount runs. Each canvas is tracked on its own, so stories rendered side by side on a docs page don't affect each other. It returns a teardown that Storybook calls when the story leaves the canvas.
  • viteFinal merges @praxisjs/vite-plugin (with HMR) into Vite's config and adds a plugin that embeds the raw source of each *.stories.* / *.story.* file for the Code panel.
  • previewAnnotations registers @praxisjs/storybook/preview (renderToCanvas plus the default render).
  • managerEntries registers the custom manager panel.

On this page