---
description: "Write stories for the Markdown preview and explore its states in Storybook."
---

# Develop the Preview in Storybook

Render the Markdown preview on its own, with one story for each kind of Markdown, and change how it renders without opening a note in the app.

The preview shows whatever the open note holds, so checking a nested list or a heading level in the app means first writing a note that has one. A story fixes that input instead: it renders `MarkdownPreview` with Markdown of its own. `@gtkx/storybook` shows the stories in a native explorer window; [Storybook](/v2/guide/storybook) covers it in full.

## Set up Storybook

Install the explorer as a development dependency:

```sh
npm install -D @gtkx/storybook@beta
```

Add a script that starts it to `package.json`:

```json{4} [package.json]
{
    "scripts": {
        "dev": "gtkx dev",
        "storybook": "gtkx storybook"
    }
}
```

`.storybook/main.ts` tells the explorer where the stories are, as patterns relative to the project root:

```ts [.storybook/main.ts]
import { defineConfig } from "@gtkx/storybook/config";

export default defineConfig({
    stories: ["src/**/*.stories.tsx"],
});
```

## Write stories

A stories file sits next to the component it renders and follows Storybook's [Component Story Format](https://storybook.js.org/docs/api/csf). Its default export, the metadata, names the component and the [arguments](https://storybook.js.org/docs/writing-stories/args) every story starts from. Each named export is a story that overrides some of them. Create `src/preview/markdown-preview.stories.tsx`:

````tsx [src/preview/markdown-preview.stories.tsx]
import type { Meta, StoryObj } from "@gtkx/storybook";
import { MarkdownPreview } from "./markdown-preview.js";

const meta = {
    title: "Preview/Markdown",
    component: MarkdownPreview,
    args: {
        text: "",
        tags: [],
        onActivateLink: () => true,
    },
    argTypes: {
        onActivateLink: { action: "activate-link" },
    },
} satisfies Meta<typeof MarkdownPreview>;

type Story = StoryObj<typeof meta>;

const Headings = {
    args: {
        text: "# Heading 1\n\n## Heading 2\n\n### Heading 3\n\n#### Heading 4\n\n##### Heading 5\n\n###### Heading 6\n",
    },
} satisfies Story;

const Formatting = {
    args: {
        text: "Text can be **bold**, *italic* or `inline code`.",
    },
    argTypes: {
        text: { control: "text" },
    },
} satisfies Story;

const Links = {
    args: {
        text: "[Reading list](<Reading list.md>) links to another note.\n\n[GTKX](https://gtkx.dev) links to a website.\n",
    },
} satisfies Story;

const Lists = {
    args: {
        text: [
            "- Oat milk",
            "- Coffee beans",
            "  - Whole, not ground",
            "- Lemons",
            "",
            "1. Preheat the oven",
            "2. Mix the dough",
            "3. Bake for 20 minutes",
        ].join("\n"),
    },
} satisfies Story;

const Blocks = {
    args: {
        text: [
            "> Simplicity is prerequisite for reliability.",
            ">",
            "> Edsger W. Dijkstra",
            "",
            "```ts",
            'const greeting = "Hello";',
            "console.log(greeting);",
            "```",
            "",
            "---",
            "",
            "After the rule.",
        ].join("\n"),
    },
} satisfies Story;

const Tagged = {
    args: {
        text: "# Ideas\n\nA notes app over **plain files**.\n",
        tags: ["work", "ideas"],
    },
} satisfies Story;

export default meta;
export { Blocks, Formatting, Headings, Links, Lists, Tagged };
````

- `title` groups the stories under Preview/Markdown in the explorer's sidebar.
- `args` gives `MarkdownPreview` empty Markdown, no tags and a link handler. Each story sets `text` to the Markdown it shows, and Tagged also sets `tags`.
- The explorer shows a [control](https://storybook.js.org/docs/essentials/controls) only for an argument whose `argTypes` entry declares one. Formatting declares a text control for `text`. A text control is a one-line entry, so the other stories, whose Markdown spans several lines, have none.
- `action: "activate-link"` lists each call to `onActivateLink` under Actions, with the link's target, and still calls the handler from `args`. That handler returns `true`, which tells the label the link is handled (see [Follow links](/v2/tutorial/the-markdown-preview#follow-links)), so GTK doesn't try to open it.

The stories import `MarkdownPreview` rather than define it, because Fast Refresh updates a component only when it lives in its own module. [Test the App](/v2/tutorial/testing) renders the same stories in its tests.

## Run Storybook

```sh
npm run storybook
```

The explorer lists the stories in its sidebar. Selecting one shows the preview beside the story's Controls and Actions. Its application ID is the app's with a `.Storybook` suffix, `dev.gtkx.Notes.Storybook`. Under the app's own ID, a second launch would only activate the running app (see [The application element](/v2/guide/application-lifecycle#the-application-element)), so the explorer runs beside `npm run dev`.

## Run it

Select Formatting and edit its text control: the preview renders each change as you type. Select Links and click a link: Actions lists `activate-link` with the link's target.

Keep the explorer open, change `src/preview/markdown-preview.tsx` and save it: the story updates in place, as the app does with [Fast Refresh](/v2/tutorial/fast-refresh).

<figure class="result">
    <img class="result-light" src="/figures/tutorial-storybook-light.png" width="880" height="540" loading="lazy" alt="The GTKX Storybook window with the Preview/Markdown stories in the sidebar and Formatting selected: the preview shows bold, italic and inline code, and the Controls panel shows the story's Markdown text." />
    <img class="result-dark" src="/figures/tutorial-storybook-dark.png" width="880" height="540" loading="lazy" alt="The GTKX Storybook window with the Preview/Markdown stories in the sidebar and Formatting selected: the preview shows bold, italic and inline code, and the Controls panel shows the story's Markdown text." />
    <figcaption>Captured from <a href="#write-stories"><code>src/preview/markdown-preview.stories.tsx</code></a> above, with the Formatting story selected.</figcaption>
</figure>
