zfb
GitHub repository

Type to search...

to open search from anywhere

Directives

Register :::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

zfb.config.ts
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.

zfb.config.ts
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.

zfb.config.ts
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:

FieldTypeRequiredDefault
componentstringyes
kind"container" | "leaf" | "text"no"container"
titleFromLabelbooleannotrue

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.

Fence spacing

Blank lines around container fences are supported but not required. zfb also handles the collapsed form that markdown-rs produces when an opener, body, and closer land in one paragraph:

:::note
Body text.
:::
:::tip
Another block.
:::

Both sibling directives and nested directives are rewritten when their names are registered — in the collapsed form and in the blank-line-separated form alike. Unknown directive names still emit a warning diagnostic and leave their source text unchanged.

Nesting a container directive inside another follows the CommonMark-Directives convention: the outer fence uses more colons than the inner one.

:::::note
Prose above.
:::tip
Inner body.
:::
:::::

The same convention applies when the fences are separated by blank lines.

:::::note

Prose above.

:::tip
Inner body.
:::

:::::

This holds with hardBreaks or cjkFriendly enabled, and for a collapsed directive reached through :::include. Those options re-tokenise the paragraph before the directive registry sees it, and recognition is tolerant of that shape (#2413) — up to and including a ::::: outer fence, which needs the colon-count rule to nest correctly. This holds for the blank-line-separated form as well.

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:

  1. Author the component yourself.

  2. Add it to your project's MDX components map.

  3. 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 wired when markdown.features.directives is supplied.

  • Recipe: Admonitions — a worked example registering note, tip, info, warning, danger, caution, and details with minimal component stubs and CSS.

  • Custom Directives — register directives via the Rust pipeline API.

Revision History

CreatedUpdated