Custom Blocks

Braised supports Pandoc-style fenced divs for structured content components. Blocks are rendered through Go templates and can use Alpine.js for interactivity.

Syntax

A block opens with ::: followed by {.block-name} and closes with :::. The block name is the first CSS class and determines which template is used.

:::{.block-name}
Content goes here.
:::

Additional attributes are passed as props:

:::{.callout type="warning" title="Watch out"}
This will cause problems.
:::

Structural blocks (those that contain child blocks) use a named close instead of a bare ::::

:::{.tabs}
:::{.tab label="First"}
Content of first tab.
:::{/tab}
:::{.tab label="Second"}
Content of second tab.
:::{/tab}
:::{/tabs}

Structural built-in blocks: tabs, tab, code-group, pillstrip, when, dl, item, table, row, cell, spotlight. All require their named close (:::{/name}). Leaf blocks (callout, details, synopsis, option, rawhtml, pull-quote) use a bare ::: close.

Built-in blocks

callout

Highlights important information. The type prop controls the semantic category.

Props

Prop Values Default
type note, tip, warning, caution note
title Any string (none)

Example

:::{.callout}
A plain note with no title.
:::

A plain note with no title.

:::{.callout type="tip" title="Pro tip"}
Use `nav_title` in frontmatter to shorten long page titles in the sidebar.
:::
Pro tip

Use nav_title in frontmatter to shorten long page titles in the sidebar.

:::{.callout type="warning" title="Draft pages are skipped"}
Pages with `draft: true` in their frontmatter are not written to `dist/`.
They do not appear in the build output at all.
:::
Draft pages are skipped

Pages with draft: true in their frontmatter are not written to dist/. They do not appear in the build output at all.

:::{.callout type="caution" title="Destructive operation"}
Running `braised build` will overwrite everything in the `dist/` directory.
:::
Destructive operation

Running braised build will overwrite everything in the dist/ directory.


details

A collapsible section backed by the native <details> element. No JavaScript required.

Props

Prop Values Default
summary Any string Details

Example

:::{.details summary="Show the full error message"}

rendering "docs/guide.md": executing template: template: page.html:14:3: executing "content" at <.Page.Content>: ...

:::
Show the full error message
rendering "docs/guide.md": executing template: template: page.html:14:3:
executing "content" at <.Page.Content>: ...

pull-quote

Typographic emphasis for a text excerpt. Renders a centered, italic block with optional attribution. Excluded from the RAG content manifest by default โ€” the content is typically repeated from surrounding body prose. Set rag="include" to index it (useful for epigraphs or standalone quotations that are not pulled from body text).

Props

Prop Values Default
attribution Any string (none)
rag "include" (excluded from manifest)

Example

:::{.pull-quote}
Good documentation doesn't explain what the code does. It explains why.
:::

Good documentation doesn't explain what the code does. It explains why.

:::{.pull-quote attribution="Jane Smith, *Designing for Readers*"}
A reader who has to re-read a sentence has already lost the thread.
:::

A reader who has to re-read a sentence has already lost the thread.

Jane Smith, *Designing for Readers*

spotlight

A breakout container that visually elevates any content โ€” prose, images, callouts, or a nested pull-quote โ€” beyond the normal column padding. The level prop controls how far the block expands and how much visual decoration is applied.

spotlight is a structural block: it may contain other blocks as direct children and must be closed with :::{/spotlight}.

Props

Prop Values Default
level "1", "2", "3" "2"

Example

:::{.spotlight}
![Key architecture diagram](arch.png)
:::{/spotlight}
:::{.spotlight level="3"}
This paragraph receives the strongest visual lift โ€” widest breakout, deepest shadow.
:::{/spotlight}

Composing with pull-quote

spotlight (structural) wraps pull-quote (leaf) cleanly. The inner ::: closes pull-quote; :::{/spotlight} closes the outer block:

:::{.spotlight level="2"}
:::{.pull-quote attribution="Internal design review"}
Every API should be obvious to someone who has never used it.
:::
:::{/spotlight}

Every API should be obvious to someone who has never used it.

Internal design review

The pull-quote's RAG exclusion applies regardless of what wraps it. To index the quoted text, set rag="include" on the pull-quote, not on spotlight.


tabs

A tabbed panel group. Each direct child should be a tab block. Alpine.js handles the active state.

Child block: tab

Prop Values Default
label Any string Tab N (1-indexed, e.g. Tab 1, Tab 2)

Example

:::{.tabs}
:::{.tab label="Go"}
```go
package main

import "fmt"

func main() {
    fmt.Println("hello")
}
```
:::{/tab}
:::{.tab label="Python"}
```python
def main():
    print("hello")

if __name__ == "__main__":
    main()
```
:::{/tab}
:::{/tabs}
package main

import "fmt"

func main() {
    fmt.Println("hello")
}
def main():
    print("hello")

if __name__ == "__main__":
    main()

dl

A definition list rendered as <dl>/<dt>/<dd>. Use item child blocks for each entry. Particularly useful for configuration references, glossaries, and command flag documentation. Both dl and item are structural โ€” close them with :::{/dl} and :::{/item}. Item bodies may contain nested blocks such as callouts.

Child block: item

Prop Values Default
term Any string (empty)

Example

:::{.dl}
:::{.item term="--port"}
TCP port to listen on. Defaults to `4321`.
:::{/item}
:::{.item term="--host"}
Address to bind to. Defaults to `localhost`.
:::{/item}
:::{/dl}
--port

TCP port to listen on. Defaults to 4321.

--host

Address to bind to. Defaults to localhost.


synopsis

A styled block for command syntax, BNF grammar, or function signatures. Write the content as a fenced code block inside the block. Italic text (*param*) renders as a replaceable parameter.

Props

None.

Example

:::{.synopsis}
```
braised build [--drafts] [site-dir]
braised serve [--port PORT] [site-dir]
```
:::
braised build [--drafts] [site-dir]
braised serve [--port PORT] [site-dir]

code-group

A variant of tabs intended specifically for code blocks. Uses the same Alpine.js component (braisedTabs) but with styling hooks that assume each panel contains exactly one fenced code block.

Props

Same as tabs. Child labels default to Tab 1, Tab 2, ... if not set.

Example

:::{.code-group}
:::{.tab label="braised.yaml"}
```yaml
site:
  title: My Docs
  url: https://docs.example.com
output: ./dist
```
:::{/tab}
:::{.tab label="nav.yaml"}
```yaml
- Introduction: index.md
- Guide:
  - Getting Started: guide/start.md
```
:::{/tab}
:::{/code-group}
site:
  title: My Docs
  url: https://docs.example.com
output: ./dist
- Introduction: index.md
- Guide:
  - Getting Started: guide/start.md

table

A semantic HTML table with support for header rows, multiline cell content, and merged cells via colspan/rowspan. Use row and cell child blocks to define structure.

Child block: row

Prop Values Default
header "true" (none โ€” body row)

Child block: cell

Prop Values Default
colspan Positive integer (none)
rowspan Positive integer (none)

table props

Prop Values Default
caption Any string (none)

Indentation: Row and cell fence lines may be indented with spaces or tabs for readability โ€” braised strips leading whitespace before classifying fence lines. Keep cell content at the natural document depth; 4+ spaces on content lines (not fence lines) would make them CommonMark code blocks.

Cell content: Use separate open, content, and close lines for each cell. Inline syntax :::{.cell}content:::{/cell} is not supported. All three levels (table, row, cell) are structural and require named closes (:::{/table}, :::{/row}, :::{/cell}). Cell bodies may contain nested blocks such as callouts.

Example

:::{.table caption="PostgreSQL numeric types"}
  :::{.row header="true"}
  :::{.cell}
  Name
  :::{/cell}
  :::{.cell}
  Size
  :::{/cell}
  :::{.cell}
  Range
  :::{/cell}
  :::{/row}
  :::{.row}
  :::{.cell}
  smallint
  :::{/cell}
  :::{.cell}
  2 bytes
  :::{/cell}
  :::{.cell}
  โˆ’32768 to 32767
  :::{/cell}
  :::{/row}
  :::{.row}
  :::{.cell}
  integer
  :::{/cell}
  :::{.cell}
  4 bytes
  :::{/cell}
  :::{.cell}
  โˆ’2147483648 to 2147483647
  :::{/cell}
  :::{/row}
:::{/table}

Multiline cell content works naturally โ€” add as many lines as needed between the open and close fences:

  :::{.cell}
First paragraph of content.

Second paragraph of content.
  :::{/cell}

Merged cells use colspan and rowspan on the cell block:

  :::{.cell colspan="2"}
  Spans two columns
  :::{/cell}

rawhtml

Passes content through to the page as raw HTML, bypassing Braised's Markdown processing and goldmark's HTML sanitiser. Use this for third-party embeds: form widgets, script-injected components, iframes, or any HTML that cannot be expressed as Markdown or a block.

Props

None.

Example

:::{.rawhtml}
<div id="typeform-embed">
  <!-- third-party widget bootstrapped by script below -->
</div>
<script src="https://embed.typeform.com/next/embed.js"></script>
:::

Content inside rawhtml is emitted verbatim. Block-level HTML elements (<div>, <script>, <iframe>, etc.) pass through unchanged. Braised blocks are not supported inside rawhtml.

For inline <script> tags in <head> (e.g. analytics), use the layout extra_head override instead.


pillstrip + when

A page-level content filter. The pillstrip block renders a row of pill buttons; the companion when block wraps content that should show or hide based on the active pill. Selecting a pill updates a global page-filter state โ€” all when blocks across the page respond simultaneously.

pillstrip child block: option

Prop Values Default
label Display text for the pill (required)
key Filter key matched by when blocks value of label

when props

Prop Values Default
key Filter key that activates this block (required)

Example

:::{.pillstrip}
:::{.option label="Linux" key="linux"}
:::
:::{.option label="macOS" key="macos"}
:::
:::{.option label="Windows" key="windows"}
:::
:::{/pillstrip}

:::{.when key="linux"}
Install with `apt install braised`.
:::{/when}

:::{.when key="macos"}
Install with `brew install braised`.
:::{/when}

:::{.when key="windows"}
Download the binary from the releases page.
:::{/when}

The first option is active by default. When key is omitted from an option block, the label value is used as the key โ€” useful when label and key are the same.

Content not wrapped in a when block is always visible regardless of the active pill.


Indentation

Braised strips leading whitespace from fence lines before classification, so any indentation depth works. Two spaces per level is conventional.

:::{.table}
  :::{.row}
  :::{.cell}
  Content here.
  :::{/cell}
  :::{/row}
:::{/table}

Rules:

  • Any number of leading spaces or tabs on a fence line is stripped before classification. Tabs and spaces both work at any depth โ€” use whichever makes the source most readable.
  • 4+ spaces on a content line (not a fence line) still triggers CommonMark's indented code block rule (ยง4.4) inside a block. Only fence lines (:::{...} and :::{/...}) are immune to this โ€” they are stripped by braised before goldmark processes them. Keep cell and item content at the natural indentation of the document; only indent the fences.
  • 3-space indentation on fence lines partially works (the fence opens) but produces unreliable close detection. In practice, use 0, 2, or 4+ spaces โ€” not 3.

For nested blocks like table > row > cell, a common style is to indent child fences one level (2 spaces or one tab) from their parent โ€” this is purely visual organisation and has no effect on parsing:

Blank line before outer close fences (recommended style): When an inner structural block closes immediately before an outer close fence, add a blank line between them:

:::{.row}
:::{.cell}
Content.
:::{/cell}

:::{/row}

Without the blank line, the Markdown parser can absorb the outer fence (:::{/row}) as literal text in the preceding paragraph. Braised silently strips these spurious fence lines as a safety net, but the blank line is the recommended style โ€” it is unambiguous and produces identical output at any nesting depth.


Custom blocks

Create a blocks/ directory in your site root and add an HTML file named after the block.

my-site/
โ”œโ”€โ”€ braised.yaml
โ”œโ”€โ”€ blocks/
โ”‚   โ””โ”€โ”€ badge.html      โ† renders :::{.badge}
โ””โ”€โ”€ docs/
    โ””โ”€โ”€ index.md

The template receives a BlockContext value:

Field Type Description
.Children template.HTML Rendered HTML of all child content
.ChildBlocks []ChildContext Direct child blocks with their rendered output
.Props map[string]string Attributes from the opening fence
.Page PageContext Title, URL, headings for the current page
.Site SiteContext Title and URL for the site

Each entry in .ChildBlocks has:

Field Type Description
.Name string Block name (first CSS class) of the child
.Props map[string]string Child's opening fence attributes
.Rendered template.HTML Output of the child's template

Use the default function to provide fallback values for optional props:

{{$level := index .Props "level" | default "info"}}
<span class="badge badge-{{$level}}">{{.Children}}</span>

Parent/child rendering: Child templates are data providers โ€” their .Rendered output is what the child's template emits. Parent templates own all structural markup. If your block type is always a structural child of a specific parent (like tab inside tabs), keep its template minimal:

{{.Children}}

Let the parent template read .ChildBlocks[i].Props directly and build the wrapper elements (tab buttons, <tr> rows, etc.) itself.

Local templates in blocks/ take precedence over built-in templates of the same name, so you can override any built-in block.

Cross-reference registration

Any block โ€” built-in or custom โ€” can accept an id prop. When present, it registers that block as a named cross-reference target, reachable from any page via braised:ref/{id}:

:::{.callout type="caution" id="disk-wipe-warning" title="Data loss risk"}
Running this command will erase the disk.
:::

From another page:

See the [disk-wipe warning](braised:ref/disk-wipe-warning) before proceeding.

The id value must be unique across the entire site. Custom block templates can also register refs programmatically using {{call .RegisterRef id title anchorID}} โ€” see Block Templates for details.

Leaf vs structural blocks

Leaf blocks use a bare ::: to close. All custom blocks default to leaf.

Structural blocks pass ::: through to inner child blocks and use a named close :::{/name}. The built-in structural blocks are tabs, tab, code-group, pillstrip, when, dl, item, table, row, cell, and spotlight.

To make a custom block structural, declare it in braised.yaml:

blocks:
  structural: [my-tabs, my-container]

Once declared structural, the block must be closed with :::{/name} and may contain nested leaf blocks (callouts, synopsis, etc.) without ambiguity:

:::{.my-container}
Some intro text.
:::{.callout type="note"}
A note nested inside your custom block.
:::
:::{/my-container}

Without the structural declaration, the outer block would grab the inner ::: and leave the callout unclosed.

Rich content inside blocks

All standard Markdown features work inside blocks, including GFM extensions:

:::{.callout type="note" title="Supported inside blocks"}
| Feature | Works? |
|---|---|
| Tables | โœ“ |
| ~~Strikethrough~~ | โœ“ |
| `code spans` | โœ“ |
| Fenced code blocks | โœ“ |
| Task lists | โœ“ |
| Footnotes | โœ“ |
:::
Supported inside blocks
Feature Works?
Tables โœ“
Strikethrough โœ“
code spans โœ“
Fenced code blocks โœ“
Task lists โœ“
Footnotes โœ“