I'm a React developer, and I've written my share of Storybook stories too.
Still, a lot of my day looked like this: build a component, open the app in the browser, click through to the screen where it shows up, and check whether it looks right. Then change something and do it all again.
Storybook works best when you build components in it first and then build features on top of them. In practice, feature deadlines often flipped that order for me. The feature shipped first, and the stories got written afterwards in a hurry, if at all.
Then I saw Compose
At some point I got to look at an Android project written with Jetpack Compose, and its @Preview felt like a different world:
@Preview
@Composable
fun PrimaryButtonPreview() {
PrimaryButton(text = "Save")
}
The syntax was tiny. You write only the cases you care about, right next to the code, and you see them while you work. No separate setup to
keep alive.
As a React developer, I wanted exactly that. So I picked the setup I use most, React with Vite. I'd also wanted to build a Vite plugin for a
while, and the two ideas met.
That became Vitrine, and 0.2.0 just came out.
One comment, your own Vite
You add a comment above a component you export:
/** @preview */
export const PrimaryButton = () => <Button tone="primary">Save</Button>;
and open /__vitrine on the Vite dev server you're already running. The component is there.
Setup is one plugin next to your React plugin:
// vite.config.ts
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import vitrine from "vite-plugin-react-vitrine";
export default defineConfig({
plugins: [react(), vitrine()],
});
Because the previews go through your own dev server, aliases, CSS, plugins and HMR behave exactly like in your app.
You can try it without installing anything in the live demo.
Writing only the cases you care about
Like Compose previews, you can add a few named cases when one isn't enough:
import { preview } from "vite-plugin-react-vitrine/preview";
preview(Button, {
args: { children: "Save" },
variants: {
primary: { name: "Primary", args: { tone: "primary" } },
danger: { name: "Danger", args: { tone: "danger" } },
},
});
The gallery also gives you:
- Props controls generated from your TypeScript prop types
- Variants side by side, and a Matrix that renders every combination of two props
-
Actions, a log of calls to
onClick,onChangeand otheron*props - Globals for values like theme or locale that a provider sets
- Viewports that trigger real media queries, since each preview renders in its own frame
- History to compare the current version with an earlier save
- Links that reopen the same preview, variant and values
And when you want to share the gallery with reviewers, vitrine-plugin-static builds it into static files. The live demo is built that way.
Next to your editor, if you want
The closest thing to the Compose experience is the optional VS Code extension. It opens the same gallery in a panel beside your code, and as you move your cursor between @preview exports, the panel switches to that preview. It can also save the props you set in the gallery as a new variant in your file.
You don't need it, though. The gallery is just a page on your dev server, so it works in any browser.
Try it
npm install -D vite-plugin-react-vitrine
- Live demo: https://vitrine-4kr.pages.dev/demo/
- Docs: https://vitrine-4kr.pages.dev/docs
- GitHub: https://github.com/yuntaengtaeng/vitrine
It supports React 18 and 19 and Vite 6.4 and later.
What's next
Vitrine is still 0.x, and I'd love to hear how it fits into your workflow: what breaks in your setup, and what would make it something you reach for every day. If you'd like to contribute, I've opened a few beginner-friendly issues on GitHub.
I'm from Korea and English isn't my first language, so sorry if anything reads a bit off. Thanks for reading!


Top comments (0)