Skip to content
Blume is now publicly available.
Blume
Esc
navigateopen⌘Jpreview
On this page

Components

The built-in JSX components — cards, columns, steps, tabs, accordions, badges, code groups, frames, trees, tooltips, type tables, live component previews, diffs, and a GitHub card — usable in any MDX page.

Blume ships an accessible, themeable component set available in any .mdx page with no imports. Each one is shown below with a live preview and its source. Components are vanilla and React-free; React only switches on if you add your own island.

Card and CardGroup

Cards link to a destination with an icon, title, and short blurb. Group them with CardGroup for a responsive grid. Reach for them on landing pages, section indexes, and “next steps” — anywhere you’re guiding the reader onward.

<CardGroup cols={2}>
  <Card title="Quickstart" href="/docs/quickstart" icon="rocket">
    Install Blume and ship your first page.
  </Card>
  <Card title="Components" href="/docs/content/components" icon="folder">
    Browse the component library.
  </Card>
</CardGroup>

Card takes title, an optional href (omit it for a non-clickable card), and an icon from Blume’s built-in icon set. CardGroup takes cols (default 2).

Steps

A numbered vertical sequence for ordered instructions — installs, setup flows, and tutorials where the order matters. Each Step takes a title.

Install Blume

Add the package to your project.

Write a page

Drop an .mdx file into your content folder.

Ship it

Run blume build and deploy dist/.
<Steps>
  <Step title="Install Blume">Add the package to your project.</Step>
  <Step title="Write a page">
    Drop an `.mdx` file into your content folder.
  </Step>
  <Step title="Ship it">Run `blume build` and deploy `dist/`.</Step>
</Steps>

Tabs

Switch between equivalent content in place — language variants, OS-specific commands, or alternative approaches — without stacking everything on the page. Each Tab takes a title.

Use Homebrew to install the toolchain.
Use winget to install the toolchain.
<Tabs>
  <Tab title="macOS">Use Homebrew to install the toolchain.</Tab>
  <Tab title="Windows">Use winget to install the toolchain.</Tab>
</Tabs>

Badge

A small inline label for status or metadata — version tags, “new” or “beta” markers, stability levels. The variant tunes the color to the meaning.

Default

Neutral metadata with no particular emphasis.

Stable
<Badge>Stable</Badge>

Accent

Draws the eye using your theme accent — good for “new” or featured markers.

New
<Badge variant="accent">New</Badge>

Success

A positive or passing state.

Passing
<Badge variant="success">Passing</Badge>

Warning

Something to use with caution, such as an experimental feature.

Beta
<Badge variant="warning">Beta</Badge>

Danger

A negative or breaking state, such as a deprecation.

Deprecated
<Badge variant="danger">Deprecated</Badge>

Icon

Render an icon by name — the same icon prop powers cards, steps, tabs, and sidebar entries. Names come from Lucide, lowercase and kebab-cased (rocket, gauge, book-open).

<Icon icon="rocket" size={20} />

Blume is Lucide-only — a bare name resolves against Lucide, and you can prefix a name with lucide: (lucide:rocket) for symmetry with other icon inputs. size sets the pixel size (default 16) and color tints it (any CSS color; defaults to currentColor). Pass a raw <svg> string, an image URL, or a local image path in place of a name to render your own art, and add a label to expose it to assistive tech — without one, the icon is decorative.

Icons resolve at build time and inline as zero-JS SVG — nothing is fetched at runtime.

File tree

Illustrate a project or folder layout. Wrap a normal Markdown list and Blume styles it as a tree — handy for explaining structure in setup and config guides.

  • docs/
    • index.mdx
    • guides/
      • configuration.mdx
  • blume.config.ts
<FileTree>

- docs/
  - index.mdx
  - guides/
    - configuration.mdx
- blume.config.ts

</FileTree>

Accordion

Stack related collapsibles in a single bordered container with dividers between them — FAQs, optional steps, or long examples. Each child is an AccordionItem (title, optional icon, description, defaultOpen). For a single standalone disclosure, use Expandable.

Does it support MDX?

Yes — every page can be .md or .mdx.

Is the theme customizable?

Yes, via Tailwind v4 tokens and your own theme.css.

<Accordion>
  <AccordionItem title="Does it support MDX?">
    Yes — every page can be `.md` or `.mdx`.
  </AccordionItem>
  <AccordionItem title="Is the theme customizable?">
    Yes, via Tailwind v4 tokens and your own `theme.css`.
  </AccordionItem>
</Accordion>

Expandable

A lightweight inline disclosure for nested detail — expanding a field’s sub-properties or an optional aside. title labels the toggle (defaults to “Show more”); set defaultOpen to start expanded.

Show advanced options

These settings are optional and rarely need changing.

<Expandable title="Show advanced options">
  These settings are optional and rarely need changing.
</Expandable>

Columns

Lay cards or blocks out in a responsive grid of equal columns that reflows on mobile. Columns takes cols; wrap each cell in a Column.

Fast

Built on Astro and Vite.

Themeable

Tailwind v4 design tokens.

<Columns cols={2}>
  <Column>
    <Card title="Fast" icon="rocket">
      Built on Astro and Vite.
    </Card>
  </Column>
  <Column>
    <Card title="Themeable" icon="sun">
      Tailwind v4 design tokens.
    </Card>
  </Column>
</Columns>

CodeGroup

Group several code blocks into one tabbed switcher — a tab per language or file. The tab label is each block’s title (the text after the language). Add dropdown to switch with a menu instead of a tab bar.

export const greet = (name: string) => `Hello, ${name}`;
def greet(name: str) -> str:
    return f"Hello, {name}"
fn greet(name: &str) -> String {
    format!("Hello, {name}")
}
<CodeGroup>

```ts TypeScript
export const greet = (name: string) => `Hello, ${name}`;
```

```python Python
def greet(name: str) -> str:
    return f"Hello, {name}"
```

```rust Rust
fn greet(name: &str) -> String {
    format!("Hello, {name}")
}
```

</CodeGroup>

Frame

Wrap an image or any visual in a centered, bordered frame with an optional caption (rendered as Markdown) and hint.

Frames center and caption visuals.

A framed illustration.
<Frame
  caption="A **framed** illustration."
  hint="Frames center and caption visuals."
>
  <img src="/screenshot.png" alt="Product screenshot" />
</Frame>

YouTube

Embed a YouTube video in a responsive, privacy-friendly (youtube-nocookie.com) 16 frame that ships no client JavaScript. Pass a video id or a full url, plus an optional title (for accessibility) and a start time in seconds.

<YouTube id="aqz-KE-bpKQ" title="Big Buck Bunny" />
<YouTube url="https://youtu.be/aqz-KE-bpKQ" start={30} />

Color

Show color swatches with copyable hex values — useful for documenting a palette or brand colors. Use variant="compact" for a swatch list, or variant="table" with Color.Row to group them. Each Color.Item takes a name and a value (a hex string, or { light, dark } for theme-aware colors).

<Color variant="compact">
  <Color.Item name="blue-500" value="#3B82F6" />
  <Color.Item name="green-500" value="#16A34A" />
  <Color.Item name="background" value={{ light: "#FFFFFF", dark: "#0A0A0A" }} />
</Color>

Tree

Render a hierarchical file/folder structure with expandable folders. (For a quick, list-driven version see File tree; Tree gives per-folder control.) Use Tree.Folder (name, optional defaultOpen, openable) and Tree.File (name).

src
index.ts
components
Button.tsx
blume.config.ts
<Tree>
  <Tree.Folder name="src" defaultOpen>
    <Tree.File name="index.ts" />
    <Tree.Folder name="components">
      <Tree.File name="Button.tsx" />
    </Tree.Folder>
  </Tree.Folder>
  <Tree.File name="blume.config.ts" />
</Tree>

Panel

A titled container for supplementary, set-aside content. title is optional.

<Panel title="Good to know">
  Panels hold supporting detail without interrupting the main flow.
</Panel>

Tooltip

Reveal a definition or hint on hover for an inline term. tip is the hover text; add an optional headline and a cta + href for a follow-up link.

Hover the APIAPIA set of protocols software uses to communicate.Read the guide term to learn more.

Hover the <Tooltip tip="A set of protocols software uses to communicate." headline="API" cta="Read the guide" href="/docs/quickstart">API</Tooltip> term.

Tile

A clickable preview that leads with a visual — an icon or image — above a title and description. Good for galleries and showcases. Takes title, description, and href; the child is the visual.

Quickstart

Ship your first page in minutes.

<Tile
  title="Quickstart"
  description="Ship your first page in minutes."
  href="/docs/quickstart"
>
  <Icon icon="rocket" size={28} />
</Tile>

Prompt

A single row with a label and a copy button. The description (Markdown) is the visible label; the body is the prompt itself — hidden, and copied to the clipboard when the Copy prompt button is pressed. actions controls the buttons (e.g. ["copy", "cursor"]).

Ask the model to document an endpoint.

<Prompt
  description="Ask the model to **document** an endpoint."
  actions={["copy"]}
>
  Write reference docs for the POST /v1/pets endpoint.
</Prompt>

Visibility

Show or hide content by audience. for="web" renders only on the site; for="agents" targets the generated Markdown (llms.txt) that AI agents read.

This note appears on the website but is omitted from the agent-facing Markdown.

<Visibility for="web">Shown on the site only.</Visibility>
<Visibility for="agents">Shown only in the generated Markdown.</Visibility>

Type tables

Tables for documenting an object’s properties — its props, types, and defaults. Write the rows by hand with TypeTable, or generate them straight from a TypeScript interface or type alias with AutoTypeTable.

Type table

A Prop / Type grid where each row expands to reveal its description and details. Pass a type map keyed by property name; each entry takes a type, plus an optional description, default, required flag, typeDescription, and typeDescriptionLink. Optional props (required unset) show a ? after the name.

PropType
labelstring

The button's visible label.

Typestring
variant?"primary" | "ghost"

Visual style.

Type"primary" | "ghost"
Default"primary"
disabled?boolean
Typeboolean
<TypeTable
  type={{
    label: {
      type: "string",
      required: true,
      description: "The button's visible label.",
    },
    variant: {
      type: '"primary" | "ghost"',
      default: '"primary"',
      description: "Visual style.",
    },
    disabled: { type: "boolean" },
  }}
/>

Auto type table

Generate a type table from a TypeScript type so the docs stay in sync with the source. Point AutoTypeTable at a file with path (resolved from your project root) and a type name. Descriptions come from JSDoc comments, defaults from @default tags, and optional properties (?) are marked accordingly.

<AutoTypeTable path="./src/button.ts" name="ButtonProps" />

You can also pass the type inline with type instead of a path — handy for small examples:

PropType
labelstring

The button's visible label.

Typestring
variant?"primary" | "ghost"

Visual style.

Type"primary" | "ghost"
Default"primary"
disabled?boolean

Disable interaction.

Typeboolean
<AutoTypeTable
  name="ButtonProps"
  type={`
export interface ButtonProps {
  /** The button's visible label. */
  label: string;
  /**
   * Visual style.
   * @default "primary"
   */
  variant?: "primary" | "ghost";
  /** Disable interaction. */
  disabled?: boolean;
}
`}
/>

GitHub info

A card linking to a GitHub repository with its live star and fork counts. Counts are fetched at build time — no client JavaScript — and the card still renders if the API is unreachable. Pass owner and repo, or omit them to use the repository from your blume.config. Set a GITHUB_TOKEN environment variable to lift the API rate limit.

withastro/astroThe web framework for content-driven websites. ⭐️ Star to support our work!60.8K3.6K
<!-- Uses the repo from blume.config -->
<GithubInfo />

<!-- Or point it at any repository -->
<GithubInfo owner="withastro" repo="astro" />

Component

Component renders an example file from your project’s examples/ directory as a live preview alongside its highlighted source, in tabs. Point it at a file with path — its location under examples/, without the extension (so examples/counter.tsx is path="counter"). React, Vue, Svelte, and Astro examples are all supported; framework examples hydrate, Astro ones render statically. It keeps the preview and the code in sync from a single file.

The preview renders in an isolated frame that the docs styles never reach — no prose margins, typography, or theme chrome bleed into your component. The frame gets Tailwind (preflight + utilities scanned from your example files and anything they import), Blume’s design tokens so classes like bg-background follow the site palette by default, and it follows the site’s light/dark toggle live.

To style previews with your own design system — say, shadcn variables — point examples.css at a stylesheet. It’s injected into every preview frame after Blume’s defaults, so your tokens win. Don’t @import "tailwindcss" in it; the frame already provides Tailwind. Both .dark and [data-theme="dark"] work for dark-mode overrides:

// blume.config.ts
export default defineConfig({
  examples: { css: "examples/theme.css" },
});
/* examples/theme.css */
:root {
  --primary: oklch(0.6 0.2 260);
}

.dark {
  --primary: oklch(0.75 0.15 260);
}

@theme inline {
  --color-primary: var(--primary);
}

The directory is configurable too — set source (or use the string shorthand, examples: "...") when your examples live elsewhere (e.g. a registry layout). path is always relative to it:

// blume.config.ts
export default defineConfig({
  examples: "registry/files-sdk",
});
<!-- registry/files-sdk/file-list/basic.tsx -->
<Component path="file-list/basic" />

examples can also be a glob (anything with *, ?, [], {}, or !). Only matching files are discovered, and path is relative to the glob’s static prefix (the part before the first wildcard). This is for a registry that colocates each component’s source with its example — point at just the examples so the sources, which have no default export to preview, aren’t swept in:

// blume.config.ts
export default defineConfig({
  // registry/files-sdk/file-list/file-list.tsx — source, left out
  // registry/files-sdk/file-list/examples/basic.tsx — discovered
  examples: "registry/files-sdk/**/examples/*",
});
<!-- keyed relative to registry/files-sdk -->
<Component path="file-list/examples/basic" />
import { useState } from "react";

const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <button
      className="rounded-blume border border-border bg-background px-4 py-2 font-medium text-foreground text-sm transition-colors hover:bg-muted"
      onClick={() => setCount((value) => value + 1)}
      type="button"
    >
      Clicked {count} {count === 1 ? "time" : "times"}
    </button>
  );
};

export default Counter;
<!-- examples/counter.tsx -->
<Component path="counter" />

An Astro example renders live with no client JavaScript:

---
interface Props {
  title?: string;
}

const { title = "Hello from Astro" } = Astro.props;
---

<div class="rounded-blume border border-border bg-muted/30 px-5 py-4">
  <p class="m-0 font-semibold text-foreground text-sm">{title}</p>
  <p class="m-0 mt-1 text-muted-foreground text-sm">
    A static, server-rendered example — no client JavaScript ships.
  </p>
</div>

CodeBlock

CodeBlock highlights a code string with the same Shiki theme and transformers as your fenced code — including the light/dark swap — for places a fence can’t go, like a landing page or a custom component. Pass code and a lang:

export const greet = (name: string): string =>
`Hello, ${name}!`;
---
import CodeBlock from "blume/components/content/CodeBlock.astro";
---

<CodeBlock lang="ts" code={source} icons={false} />

To highlight to an HTML string yourself (e.g. inside your own component), import the underlying helper from blume/markdown:

import { highlightCode } from "blume/markdown";

const html = await highlightCode(source, "ts");

Diff

Diff renders a git-style diff, highlighted with the same Shiki theme as your code blocks and produced entirely at build time — no client JavaScript. Give it two inline strings (old / new), two file paths (before / after), or a unified patch (an inline patch string or a src file).

<Diff
  lang="ts"
  old={`export function greet(name) {
  return "Hi, " + name;
}`}
  new={`export function greet(name: string): string {
  return "Hi, " + name + "!";
}`}
/>

Diff two files in your project, relative to its root:

Or render a unified patch from a file:

Was this page helpful?