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

  1. 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.
  2. 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.
  3. 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-data and x-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.