Directives
Opt-inRegister :::name / ::name / :name directive syntax that maps to your own JSX components. Zero names are registered by default — you supply the vocabulary.
The directives feature populates the Core Directives registry from your zfb.config.ts. It maps CommonMark Directives syntax — container (:::name), leaf (::name), and text (:name) — to JSX component calls in compiled MDX.
Zero directive names are registered by default. You choose the vocabulary; zfb emits the JSX.
Enable
import { defineConfig } from "zfb/config";
export default defineConfig({
markdown: {
features: {
directives: {
note: "Note",
tip: "Tip",
warning: "Warning",
},
},
},
});The value is a map from directive name → component. Each entry can be a short-form string or a full-form DirectiveSpec object (see below).
Short form — bare component name
The simplest entry maps a directive name to a component identifier string. The directive is registered as a container with titleFromLabel: true — the bracketed [label] becomes a title="…" attribute on the emitted JSX element.
export default defineConfig({
markdown: {
features: {
directives: {
callout: "Callout",
},
},
},
});Author writes:
:::callout[Heads up]
Body text.
:::Pipeline emits:
<Callout title="Heads up">
<p>Body text.</p>
</Callout>Full form — DirectiveSpec object
Use the full form to control the directive shape (container, leaf, or text) and whether the bracketed label becomes a title attribute.
export default defineConfig({
markdown: {
features: {
directives: {
// container (default kind), titleFromLabel on
note: "Note",
// leaf directive — self-closing, no body
youtube: {
component: "Youtube",
kind: "leaf",
titleFromLabel: true,
},
// text (inline) directive
kbd: {
component: "Kbd",
kind: "text",
titleFromLabel: false,
},
},
},
},
});DirectiveSpec fields:
| Field | Type | Required | Default |
|---|---|---|---|
component | string | yes | — |
kind | "container" | "leaf" | "text" | no | "container" |
titleFromLabel | boolean | no | true |
Directive shapes
Container —
:::name[label]…:::wraps a multi-paragraph body into a JSX component. Most commonly used for callout-style blocks.Leaf —
::name[label]{attrs}produces a self-closing component with no children. Used for embeds (video, code playgrounds, etc.).Text —
:name[label]{attrs}is an inline component, mixed into prose.
Blank-line requirement
Each fence line (:::name and the closing :::) must be separated from surrounding content by blank lines so the Markdown parser treats them as separate paragraphs. Without blank lines the content is not recognised as a directive and a warning diagnostic is emitted at build time.
Registering a directive does not provide a component
directives is framework-agnostic and ships no JSX components. Registering a directive only tells the pipeline to emit <ComponentName> in compiled MDX. You must:
Author the component yourself.
Add it to your project's MDX components map.
Style it with your own CSS.
Casing and verbatim emit
The component identifier you supply ("Note", "Kbd", "Youtube") is emitted verbatim — there is no auto-PascalCasing and no validation. note: "Note" emits <Note>; note: "my-note" emits <my-note> (a DOM element, likely not what you want).
The directive-name key follows the directive grammar [A-Za-z_][A-Za-z0-9_-]*. A key that does not match this pattern will simply never match any :::name in source.
See also
Directives registry — the underlying Core primitive (always active, handles parsing).
Recipe: Admonitions — a worked example registering
note,tip,info,warning,danger,caution, anddetailswith minimal component stubs and CSS.Custom Directives — register directives via the Rust pipeline API.