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:
{siteDir}/blocks/callout.html— site-local override (highest priority){themeDir}/blocks/callout.html— theme-provided block- 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; }
}));
});