Skip to main content

Your First Storybook

This tutorial walks you through creating a component storybook using Holt Book. By the end, you'll have an interactive component showcase running in your browser.

Prerequisites

  • Rust 1.88 or later
  • A Leptos project (or we'll create one)
  • Basic familiarity with Leptos components

Step 1: Create a Leptos Project

If you don't have an existing project, create one:

cargo new my-components
cd my-components

Add dependencies to your Cargo.toml:

[dependencies]
leptos = "0.8"
holt-kit = "0.1"
holt = "0.1"

Step 2: Set Up the Storybook Structure

Create the basic directory structure for your storybook:

my-components/
├── src/
│ ├── lib.rs
│ └── components/
│ └── mod.rs
└── stories/
└── mod.rs

Step 3: Create a Component

Let's build a simple Card component following Holt's behavior/presentation pattern.

Create src/components/card.rs:

use leptos::prelude::*;
use tailwind_fuse::tw_merge;

/// Card component with optional header and footer
#[component]
pub fn Card(
#[prop(optional)] class: &'static str,
children: Children,
) -> impl IntoView {
let base_classes = "rounded-lg border bg-card text-card-foreground shadow-sm";

view! {
<div class=tw_merge!(base_classes, class)>
{children()}
</div>
}
}

#[component]
pub fn CardHeader(
#[prop(optional)] class: &'static str,
children: Children,
) -> impl IntoView {
view! {
<div class=tw_merge!("flex flex-col space-y-1.5 p-6", class)>
{children()}
</div>
}
}

#[component]
pub fn CardTitle(
#[prop(optional)] class: &'static str,
children: Children,
) -> impl IntoView {
view! {
<h3 class=tw_merge!("text-2xl font-semibold leading-none tracking-tight", class)>
{children()}
</h3>
}
}

#[component]
pub fn CardContent(
#[prop(optional)] class: &'static str,
children: Children,
) -> impl IntoView {
view! {
<div class=tw_merge!("p-6 pt-0", class)>
{children()}
</div>
}
}

Export it from src/components/mod.rs:

mod card;
pub use card::*;

Step 4: Write a Story

Stories showcase your component in different states. Create stories/card.rs:

use holt_book::prelude::*;
use crate::components::*;

#[story]
pub fn CardDefault() -> impl IntoView {
view! {
<Card>
<CardHeader>
<CardTitle>"Card Title"</CardTitle>
</CardHeader>
<CardContent>
<p>"Card content goes here."</p>
</CardContent>
</Card>
}
}

#[story]
pub fn CardWithCustomClass() -> impl IntoView {
view! {
<Card class="w-96">
<CardHeader>
<CardTitle>"Fixed Width Card"</CardTitle>
</CardHeader>
<CardContent>
<p>"This card has a fixed width of 24rem."</p>
</CardContent>
</Card>
}
}

#[story]
pub fn CardMinimal() -> impl IntoView {
view! {
<Card>
<CardContent>
<p>"A card with just content, no header."</p>
</CardContent>
</Card>
}
}

Register stories in stories/mod.rs:

mod card;
pub use card::*;

use holt_book::prelude::*;

pub fn register_stories() -> Stories {
stories![
CardDefault,
CardWithCustomClass,
CardMinimal,
]
}

Step 5: Configure and Run the Storybook

If your storybook is in a subdirectory (like a workspace), create a holt.toml at your project root:

[book]
path = "path/to/your/storybook"

[serve]
port = 3000
open = true

Then start the development server:

holt serve

Without a config file, Holt runs in the current directory on port 8080. You can also override settings via flags:

holt serve --port 3000 --open

Open your browser to the configured port. You'll see your Card component with all its variants in the sidebar.

Step 6: Iterate

With the server running, edit your component or stories. Changes appear automatically thanks to hot reloading.

Try adding a new variant:

#[story]
pub fn CardHighlighted() -> impl IntoView {
view! {
<Card class="border-primary">
<CardHeader>
<CardTitle>"Highlighted Card"</CardTitle>
</CardHeader>
<CardContent>
<p>"This card has a highlighted border."</p>
</CardContent>
</Card>
}
}

The new story appears in the sidebar immediately.

Next Steps