Getting Started
@nanoviews/storybook is the Storybook renderer for Nanoviews, and @nanoviews/storybook-vite is the framework that runs it on the Vite builder of Storybook.
- CSF3. Stories are plain Component Story Format objects, typed with
MetaandStoryObj. - Args are signals. A story’s
renderis a Nanoviews component that receives the args as writable signals, so an edited control updates the view in place, with no remount. - Portable stories.
composeStoriesturns stories into functions that@nanoviews/testing-libraryrenders in unit tests. - One option. A single
frameworkentry in.storybook/main.tswires the renderer to the Vite builder.
Renderer and Framework
Section titled “Renderer and Framework”Storybook integrates a view library through two kinds of packages. @nanoviews/storybook is the renderer: it teaches Storybook how to mount and update a Nanoviews tree. It has no builder of its own, so it needs a framework: @nanoviews/storybook-vite wires it to @storybook/builder-vite.
The framework depends on the renderer and re-exports its whole API, so a project installs the framework alone and imports everything from @nanoviews/storybook-vite, the story API and the config types alike.
Installation
Section titled “Installation”Install the packages as dev dependencies, together with Storybook and Vite:
pnpm add -D @nanoviews/storybook-vite storybook viteyarn add -D @nanoviews/storybook-vite storybook vitenpm install -D @nanoviews/storybook-vite storybook vitestorybook (10.5.10 or a later 10.x) and vite (5 to 8) are peer dependencies of the framework, and nanoviews is a peer dependency of the renderer.
The Storybook CLI cannot scaffold a third-party framework, so the setup is done by hand: two config files and two scripts.
Directory.storybook/
- main.ts the framework and the story files
- preview.ts global parameters, decorators and loaders
Directorysrc/
- button.ts
- button.stories.ts
- package.json
.storybook/main.ts
Section titled “.storybook/main.ts”import type { StorybookConfig } from '@nanoviews/storybook-vite'
const config: StorybookConfig = { framework: '@nanoviews/storybook-vite', stories: ['../src/**/*.stories.ts']}
export default configframework names the framework package. Storybook loads its preset, which sets the Vite builder and the Nanoviews renderer. stories lists the globs of the story files, relative to the .storybook directory. The object form of framework, viteFinal and defineMain are covered in the API.
.storybook/preview.ts
Section titled “.storybook/preview.ts”Optional, but this is where global parameters, decorators and loaders live. Its default export is typed with Preview:
import type { Preview } from '@nanoviews/storybook-vite'
const preview: Preview = { parameters: { controls: { matchers: { color: /(background|color)$/i, date: /Date$/i } } }}
export default previewThe control matchers pick a control by the name of the arg: a color picker for the args whose names end with background or color, a date picker for the ones that end with Date.
Scripts
Section titled “Scripts”{ "scripts": { "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" }}storybook dev serves Storybook on port 6006 and opens it in the browser (--no-open skips that). storybook build writes a static Storybook into storybook-static.
First Story
Section titled “First Story”A button with an optional primary look and a click handler, which takes its label as children:
import { type Signalish, when } from 'nanoviews/store'import { button, component$ } from 'nanoviews'
export interface ButtonProps { primary?: Signalish<boolean> onClick?: (event: MouseEvent) => void}
export const Button = component$(({ primary, onClick }: ButtonProps, children) => ( button({ class: ['button', when(primary, 'button_primary')], onClick })( ...children )))Its stories set the args, and the render of the meta passes the label as a child:
import type { Meta, StoryObj } from '@nanoviews/storybook-vite'import { fn } from 'storybook/test'import { Button } from './button.js'
const meta: Meta<typeof Button> = { title: 'Button', args: { primary: false, onClick: fn() }, render(props) { return Button(props)('Send') }}
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {}// <button class="button">Send</button>
export const Primary: Story = { args: { primary: true }}// <button class="button button_primary">Send</button>The args of the meta apply to every story, and the args of a story are merged over them. primary is a Signalish<boolean> prop, so its arg is a plain boolean, and the Controls panel shows it as a toggle. The renderer hands Button a signal instead: a toggle is written into that signal, and the class of the same button changes. fn() from storybook/test makes a spy, which the Actions panel logs on every click.
Start Storybook with the storybook script, and both stories show up under Button in the sidebar.