Format Definitions

Format definitions are the core of bserver's reusability. They define how a name should render as HTML, using the ^ prefix.

Basic Format

^muted:
  tag: div
  params:
    class: text-muted small
  content: '$*'

This creates a format named muted that renders as a <div> with the specified CSS classes. The $* in content: means "pass through whatever content is provided."

Usage:

footer:
  muted: This is footer text

Renders:

<footer>
  <div class="text-muted small">This is footer text</div>
</footer>

The ^ Prefix

The caret (^) prefix is required for format definitions. Without it, the key is treated as a content definition, not a format:

# CORRECT: Format definition - registered in the formats registry
^muted:
  tag: div
  params:
    class: text-muted small

# WRONG: Content definition - stored as data, NOT rendered as a format
muted:
  tag: div
  params:
    class: text-muted small

This is a common gotcha. If your format isn't working, check for the ^ prefix first.

Format Fields

tag

The HTML tag to render:

^card:
  tag: div
  params:
    class: card

Usage: card: "Card content" renders <div class="card">Card content</div>

params

HTML attributes for the tag. Can use static values or variable substitution:

# Static params
^container:
  tag: div
  params:
    class: container

# Variable params
^link:
  tag: a
  params:
    href: '$url'
  content: '$contents'

content (singular)

Defines how inner content is rendered:

# Pass-through: content rendered inside the tag directly
^muted:
  tag: div
  params:
    class: text-muted
  content: '$*'

# Named variable: specific field used as content
^link:
  tag: a
  params:
    href: '$url'
  content: '$contents'

# Structural wrapper: all content wrapped in a sub-element
^card:
  tag: div
  params:
    class: card
  content:
    card-body: '$*'

contents (plural)

The plural form contents: works like content: for string values, but for structural wrappers it wraps each item individually rather than wrapping all content as a single block:

# Singular: ONE <li> wrapping ALL items together
^single-wrap:
  tag: ul
  content:
    li: '$*'

# Plural: one <li> PER ITEM
^ulist:
  tag: ul
  contents:
    li: '$*'

With ulist: and content ["apple", "banana", "cherry"]:

<!-- contents: (plural) - each item wrapped individually -->
<ul>
  <li>apple</li>
  <li>banana</li>
  <li>cherry</li>
</ul>

This distinction is crucial for list rendering. The built-in ^ulist uses contents: (plural) so each list item gets its own <li>.

params: '$*' (Wildcard Params)

When params is the string $*, each content entry's key-value pairs become HTML attributes directly:

^headlink:
  tag: link
  params: '$*'

With content:

headlink:
  - rel: stylesheet
    href: /styles.css
    crossorigin: anonymous

Renders:

<link rel="stylesheet" href="/styles.css" crossorigin="anonymous">

This is how <meta> and <link> tags are generated from structured YAML data.

Variable Substitution

$key and $value

For iterating over map entries where each entry produces its own HTML element:

^meta:
  tag: meta
  params:
    name: '$key'
    content: '$value'

With:

meta:
  viewport: width=device-width, initial-scale=1
  description: My site description
  author: Jane Doe

Renders:

<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="My site description">
<meta name="author" content="Jane Doe">

Each map entry produces its own <meta> tag.

Named Variables ($url, $contents, etc.)

Format params and content can reference named variables that come from the content map's keys:

^link:
  tag: a
  params:
    href: '$url'
  content: '$contents'

Usage with a map providing the variable values:

- link:
    url: /about
    contents: About Us

Renders: <a href="/about">About Us</a>

$* (Pass-through)

$* in content: means "render the provided content as-is":

^muted:
  tag: div
  params:
    class: text-muted small
  content: '$*'

Usage: muted: Hello world renders <div class="text-muted small">Hello world</div>

Unreplaced Variables

If a variable in params can't be resolved (no matching key in the content data), the entire attribute is omitted. This prevents broken HTML output from partially-resolved templates.

The Container Pattern

When a map has a formatted key whose format uses $var in content (like ^link), and there are sibling entries in the map, the siblings become children of that container element:

- link: /about
  text: Read more about us

Here, ^link defines content: '$contents'. Since there's no contents key but there is a text sibling, bserver renders the text as a child of the link:

<a href="/about">Read more about us</a>

This works because text is recognized as literal content to place inside the container element.

Iteration with Lists of Maps

When content is a list of maps and the format uses named variables, each map in the list produces its own element:

^navitem:
  tag: a
  params:
    href: '$url'
    class: nav-link
  content: '$label'

With:

navitem:
  - url: /
    label: Home
  - url: /about
    label: About
  - url: /contact
    label: Contact

Renders:

<a href="/" class="nav-link">Home</a>
<a href="/about" class="nav-link">About</a>
<a href="/contact" class="nav-link">Contact</a>

Combining Formats

Formats compose naturally. A ^ulist wrapping links:

^ulist:
  tag: ul
  contents:
    li: '$*'

^links:
  tag: a
  params:
    href: '$key'
  contents: '$value'

With:

ulist:
  links:
    /about: About
    /contact: Contact

Renders:

<ul>
  <li><a href="/about">About</a></li>
  <li><a href="/contact">Contact</a></li>
</ul>

The contents: (plural) on both formats ensures each link gets its own <li> wrapper.

Page-Level Format Overrides

If your page YAML file defines a ^format, it takes precedence over formats loaded from parent directories. This allows individual pages to customize rendering behavior:

# In your page file:
^card:
  tag: div
  params:
    class: card shadow-lg

main:
  - card: "This card has a shadow"

Next Steps