extending
Extending Leaf
A change can belong to one page, a reusable package, or Leaf's shared kernel. Put it where its behavior should apply; most new reusable widgets belong in a package. This guide sets those boundaries, then builds the smallest useful package.
Choose the extension boundary
-
One page. Semantic HTML, existing Leaf elements, a
page-local
<style>, and inline SVG shape one document. Keep presentation here when no other page needs it. - A package. A reusable element, theme, browser behavior, or author guidance belongs in a package. A project-local package, an installed package, and a package bundled with Leaf all use the same composition format.
- Leaf's kernel. Runtime, chrome, and protocol behavior shared by every page belongs in Leaf itself. A package consumes those capabilities; it does not add a parallel runtime path.
Choose a bundled package
The core product gallery now stays with Leaf’s default vocabulary. Each optional package has a focused working page, so its controls, data, and reader task can be judged without searching an omnibus gallery.
Playground also powers the structured data explorer and the built-output A/B comparison. Those pages keep their task-specific state, schema, and behavior while reusing the same package mechanics.
Build a package with two files
Create a package in the project, then check the empty composition:
$ leaf package init packages/callout
$ leaf package check packages/callout
The widget in this tutorial needs only these files:
packages/callout/ registry.json theme.css
Every package beyond the bundled default joins a page with
--package. In the commands below, PAGE is the page
directory to create or update:
$ leaf page init --package packages/callout PAGE
The complete package contract covers installed and bundled packages, contribution directories, composition order, and replacement rules.
package init also creates empty contribution directories for
guidance, browser modules, widgets, and vendor files. The CSS-only widget here
leaves them untouched.
To start with behavior instead, name the first element while creating the package:
$ leaf package init packages/risk-notes --widget lf-risk-note
Leaf writes a valid markup-widget element declaration and its one-shot browser
module, then checks the complete candidate before changing the package. The
tutorial below stays with the smaller CSS-only form so each contract remains
visible. A behavior module imports Leaf's package API from
../runtime/widget-api.js; it registers each interactive
capability once with commands(). The same live command rows feed
keyboard dispatch, help, accessibility shortcuts, and—when
decision names the action—the controls shown inline for an Ask.
State the look
A package's theme.css joins Leaf's existing cascade. This package
only styles the element it introduces:
lf-callout {
display: block;
padding: var(--sp-3);
border: 1px solid var(--rule);
border-radius: var(--r);
background: var(--field);
--lf-block-frame: 1;
}
--lf-block-frame: 1 tells the shared layout that this rule draws
the widget's inset. The opening
theme token block
lists values such as --accent that a package may override for the
whole page. Presentation used by only one page stays in that version's own
<style>.
Declare the element
The page will author the callout as ordinary readable markup:
<lf-callout id="migration-warning">
<strong>Migration window</strong>
Writes pause for about two minutes.
</lf-callout>
Its complete registry.json entry validates the attributes and
tells Leaf that the body is prose. x-example is the specimen an
author queries with the entry:
{
"lf-callout": {
"description": "A short warning or constraint that belongs in the reading flow.",
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$"
}
},
"required": ["id"],
"additionalProperties": false,
"x-content": "markup",
"x-upgrade": false,
"x-example": "<lf-callout id=\"migration-warning\"><strong>Migration window</strong> Writes pause for about two minutes.</lf-callout>"
}
}
x-content and x-upgrade are the two Leaf
declarations every widget requires. The ordinary JSON Schema fields validate
its attributes; description and x-example make the
merged registry useful to an author.
The registry-key reference lists every
x-* declaration. A CSS-only widget stops here.
Compose and check the page
$ leaf package check packages/callout
$ leaf page init --package packages/callout PAGE
$ jq '.["lf-callout"], .["$keys"]' PAGE/registry.json
$ leaf version check PAGE --render
Re-running page init is the explicit update for an existing page.
Pages that are not re-initialized keep their vendored layer unchanged.
leaf package install packages/callout copies the checked package
into ~/.local/state/leaf/packages/, after which
--package callout reaches it by name from any project on this
machine.
- Registry keys
-
The generated reference for declarations such as
x-dataandx-request. - Package contract
- Composition, guidance, browser modules, external data, requests, and validation.
- Bundled default package
- A complete package containing the widgets used across Leaf pages.