Custom and Overriding Blocks

Block templates are HTML files that render :::{.blockname} fenced divs. They live in blocks/ inside your site root, your theme directory, or (for built-ins) embedded in the braised binary.

Resolution order

When Braised encounters :::{.callout}, it searches in this order:

  1. {siteDir}/blocks/callout.html — site-local override (highest priority)
  2. {themeDir}/blocks/callout.html — theme-provided block
  3. Built-in embedded callout.html — braised default

The first match wins. You do not need to declare anything in braised.yaml to activate a custom block — the presence of the file is sufficient.

Writing a custom block

Create blocks/mycard.html in your project root (or theme):

<div class="card {{index .Props "variant" | default "default"}}">
  {{if index .Props "title"}}
  <h3 class="card-title">{{index .Props "title"}}</h3>
  {{end}}
  <div class="card-body">{{.Children}}</div>
</div>

Use it in Markdown:

:::{.mycard title="Getting started" variant="primary"}
Follow these steps to install the CLI.
:::

The template context (.) is a BlockContext:

.Props              map[string]string  — attributes from the opening fence
.Children           template.HTML      — rendered inner content
.ChildBlocks        []ChildContext     — direct child blocks (name, props, rendered)
.Page.Title         string             — current page title
.Page.URL           string             — current page URL
.Page.Breadcrumb    []string           — nav trail labels (outermost → current page)
.Page.Headings      []Heading          — H2/H3 headings on the current page
.Page.Meta          map[string]string  — page-level frontmatter extra keys
.Site.Title         string             — site name
.Site.URL           string             — site base URL
.Site.Meta          map[string]any     — site-level meta from braised.yaml
.RegisterRef        func               — register a cross-reference target (see below)

RegisterRef

Block templates can register named cross-reference targets so other pages can link to content they generate:

{{call .RegisterRef "api:create-user" "POST /users" "create-user"}}

Arguments: id (unique site-wide ref ID), title (link text when unspecified by the author), anchorID (in-page fragment — use "" to target the page root).

The registered ID is then reachable from any page via braised:ref/api:create-user.

Structural blocks

A structural block may contain other blocks as direct children. It requires a named close (:::{/blockname}) rather than a plain :::.

Declare a custom block as structural in braised.yaml:

blocks:
  structural: [mycontainer, myrow]

Once declared, the block must be closed with :::{/name} and may contain nested leaf blocks without ambiguity. Built-in blocks (such as spotlight) are declared structural in the parser — no braised.yaml entry is needed to use them.

Block CSS conventions

Built-in block styles live in braised:theme/base. Two conventions apply when writing block CSS — either for a custom block or when overriding a built-in:

Blocks do not set max-width. The content column (article { max-width: var(--content-max) }) owns layout constraints. Blocks that need to expand beyond the normal column padding use negative margin-inline and let the column cap the overall width. This keeps ultrawide behavior consistent across all blocks without any per-block width logic.

Built-in blocks expose --blockname-* custom properties as theming handles. Override them after @import "braised:theme/base" — no template copy required:

@import "braised:theme/base";

/* Widen the pull-quote rule color to match a brand accent */
.pull-quote {
  --pull-quote-border: #e05c2a;
}

/* Reduce spotlight expansion on level 2 */
.spotlight[data-level="2"] {
  --spotlight-bleed: 1rem;
}

When writing a custom block, follow the same pattern — declare tuneable values as custom properties on the block's root element with sensible defaults:

.my-card {
  --my-card-radius: 8px;
  --my-card-padding: 1.25rem;
  border-radius: var(--my-card-radius);
  padding: var(--my-card-padding);
  background: var(--bg-surface);
  border: 1px solid var(--border);
}

Overriding a built-in block

To change how callout renders, copy it to blocks/callout.html (or {themeDir}/blocks/callout.html) and edit it. The built-in template is in internal/block/builtin/ in the braised source repository.

Block JavaScript

If a block requires client-side behaviour, place a .js file alongside the template:

blocks/
  accordion.html
  accordion.js    ← included in the blocks.js bundle automatically

The JS file is concatenated into dist/assets/blocks.js in priority order: built-in → theme → site root. A site-local blocks/accordion.js can override the theme's version of the same file, exactly like HTML templates.

Register components with Alpine:

document.addEventListener('alpine:init', () => {
  Alpine.data('accordion', () => ({
    open: false,
    toggle() { this.open = !this.open; }
  }));
});