Directives registry
The Core primitive that maps :::name / ::name / :name directive syntax to JSX components.
DirectiveRegistry is the Core primitive that maps CommonMark Directives syntax — container (:::name), leaf (::name[label]), and text (:name[label]) — to JSX component calls in the compiled output.
The primitive exists in Core, but the registry visitor is not added to the normal pipeline by default. It runs only when markdown.features.directives is supplied, or when Rust code inserts a DirectiveRegistry manually. You interact with it in two ways:
From config — use the opt-in
directivesfeature to register directive names without writing Rust. Supplyingdirectives: {}still wires an empty registry.From Rust — populate a
DirectiveRegistrydirectly and insert it into the pipeline. See Custom Directives.
Directive shapes
The registry handles three directive shapes:
Container —
:::name[label]…:::wraps multi-paragraph body content into a JSX component.Leaf —
::name[label]{attrs}produces a self-closing component with no children.Text —
:name[label]{attrs}is an inline component.
Zero defaults
The registry ships with no directive names pre-registered. Every :::name you use must be explicitly registered — either via the directives feature in zfb.config.ts or by populating the registry in Rust. When the registry is active, an unrecognised directive name emits a warning diagnostic and leaves the source paragraph unchanged.
Typed attribute schemas (from #584)
The registry accepts typed attribute schemas for each registered directive. Unknown attributes emit a build-time warning (the attr still passes through unchanged). Type-coercion failures (e.g. a non-boolean value for a Boolean attr) emit hard errors. Each attribute is declared as an AttrSchema:
| Field | Description |
|---|---|
name | Attribute name as written in the MDX source (e.g. "tone", "data-foo"). |
ty | Expected type — see AttrType below. |
default | Value applied when the attr is absent and required is false. None means no default. |
required | When true and the attr is absent with no default, a diagnostic is emitted. |
AttrType has four variants:
| Variant | Accepts | Notes |
|---|---|---|
String | Any string value, passed through verbatim. | No validation. |
Enum(Vec<String>) | One of the declared allowed values (case-sensitive). | A value outside the list emits a diagnostic. |
Boolean | "true" / "false" (case-insensitive). | A bare attribute with no =value also counts as true. Emits as the string "true"/"false" in JSX (v1 only supports string-literal attributes). |
Number | Any value parseable via f64::from_str. | The original source string is stored (not the parsed number) so the JSX emitter passes it through unchanged; a non-parseable value emits a diagnostic. |
The schema is declared alongside the register call:
registry.register(
DirectiveDef::text("badge", "Badge")
.with_attrs(vec![
AttrSchema { name: "tone".into(), ty: AttrType::String, default: None, required: false },
AttrSchema {
name: "size".into(),
ty: AttrType::Enum(vec!["sm".into(), "md".into(), "lg".into()]),
default: Some("md".into()),
required: false,
},
]),
);Custom directives
To register additional directives or override built-ins, see Custom Directives — the author-facing path that does not require writing Rust.
See also
directivesfeature — opt-in config feature that populates the registry fromzfb.config.ts.Custom Directives — register new
:::name/::name/:namedirective names via the Rust API.Extending the Markdown Pipeline — engine-side extension surface.