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

Customization

Override components, add interactive islands, mount custom pages, install registry components, or eject.

Component overrides

Add a components.ts (or components.tsx) to your project root and export defineComponents. The mdx map either replaces a built-in component or adds a new one — available in every .mdx page with no import.

import { defineComponents } from "blume";
import Callout from "./components/Callout.astro";
import Pricing from "./components/Pricing.astro";

export default defineComponents({
  mdx: {
    Callout, // replace the built-in Callout
    Pricing, // add a new <Pricing /> component
  },
});

Keys are the names you write in MDX (<Callout>, <Pricing>). Use the .tsx filename when you import React components.

Reference form

Every override — in mdx, layout, or islands — accepts three forms:

import { defineComponents } from "blume";
import Callout from "./components/Callout.astro";

export default defineComponents({
  mdx: {
    Callout, // 1. an imported component
    Note: "./components/Note.astro", // 2. a path string (resolved from the project root)
    Chart: { component: "./components/Chart.tsx", client: "load" }, // 3. a descriptor
  },
});

The descriptor form adds a hydration mode so an interactive React/Vue/Svelte component ships its JavaScript and comes alive on the client. Without a client mode a framework component renders as static HTML — Blume prints a build warning when it spots one, since that’s usually a mistake.

client Hydrates
"load" Immediately on page load
"idle" When the main thread is idle
"visible" When scrolled into view
"media" When a media query matches (add media: "(min-width: 40rem)")
"only" Client only, never server-rendered

For interactive components you use across many pages, the islands group is a shorthand for the descriptor form with client: "visible".

Typing an override

When you replace a built-in, import its prop type from blume/components so your component matches the contract — the types are derived from the components themselves, so they never drift:

import type { CalloutProps } from "blume/components";

export default function Callout(props: CalloutProps) {
  // …your own callout, same props as the built-in
}

Prop types are exported for the content components (CalloutProps, CardProps, TabsProps, StepsProps, BadgeProps, and more).

Layout slots

The layout map replaces a piece of Blume’s chrome with your own component. Each override receives the same props as the built-in it replaces, so you can wrap the default or start from scratch.

import { defineComponents } from "blume";
import Footer from "./components/Footer.astro";
import Logo from "./components/Logo.astro";

export default defineComponents({
  layout: {
    Logo, // brand mark + title in the header
    Footer, // site-wide footer (no built-in — renders only when set)
  },
});

Wired slots:

Slot Replaces Props
Layout The entire page shell (RootLayout) Everything the built-in layout receives, plus the layout map
Header The top navigation bar site, logo, navigation, route, searchEnabled, …
Logo The brand link (mark + title) in the header site, logo
Search The header search trigger + modal navigation, strings, locale, askEnabled
Sidebar The primary navigation tree items, currentRoute
MobileNav The nav inside the mobile drawer (defaults to Sidebar) items, currentRoute
Breadcrumbs The breadcrumb trail crumbs
TableOfContents The on-this-page outline headings, title, variant
Pagination The prev/next footer links prev, next, strings
PageHeader An injection point above the article (no built-in) page, headings, route
PageFooter An injection point below the article (no built-in) page, headings, route
Footer A site-wide footer after the content grid (no built-in) site, navigation, ui

PageHeader, PageFooter, and Footer have no built-in component — they render nothing until you set them, which makes them handy injection points for a promo banner, a “last updated” note, or a marketing footer.

Layout slots accept the same three reference forms as MDX overrides, so a slot can be a path string or a hydrated descriptor ({ component, client }) when you want an interactive header or footer.

Interactive islands

For interactive UI (React, Vue, or Svelte), drop a component into an islands/ folder and use it in any MDX page — Blume hydrates it for you, no wrapper or registration needed:

import { useState } from "react";

export default function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>Clicked {n}</button>;
}
Use it anywhere: <Counter />

See Islands for hydration strategies and framework setup.

Custom pages

Add .astro files under your pages/ folder to mount fully custom routes alongside your docs — a landing page, a pricing page, or a hand-built index. They keep their location, so relative imports and getStaticPaths work as usual, and they can read your config, navigation, and routes from the blume:data module.

See Custom Pages for the full guide.

Registry

blume add copies a Blume-maintained component into your project as source — you own it and can edit it freely. Run it with no arguments to list what’s available:

blume add

Install a layout slot (header, sidebar, breadcrumbs, table of contents, pagination, or feedback) or any content component (callout, card, tabs, steps, accordion, and more):

blume add callout
blume add pagination

The copy imports the rest of the framework from blume/*, so it renders exactly like the built-in until you change it. blume add prints the defineComponents snippet to register it — content components under mdx, layout pieces under layout.

Eject

When you want full control, eject the generated runtime into a standalone Astro project:

blume eject --yes

Eject is a one-way step: the hidden .blume/ runtime becomes a normal Astro app you own and can modify directly. The blume package stays importable, so you keep its components, theme, and Markdown processors.

Was this page helpful?