TEMPLATES

Beanstalk has unique, superpowered templating strings. Templates use [] exclusively - never confuse with collections {}.

These are the only way to create mutable strings, as double quotes are only used for string slices.

The main purpose of a template is to generate either complex text content, markdown, HTML or just regular formatted strings.

Templates have two sections separated by a colon:

This example creates the string "head body"


    ["head": body]
    

Templates are scoped using square brackets and can be passed special types called Styles to change their behavior, content and styling.

Templates are either folded to strings at compile time, or become functions that return strings at runtime.

Structure

Templates unlock the full power of Beanstalk's HTML / CSS generation capabilities.

Styles

Templates can be used to build complex UI components. They can use slots to insert content from other templates and have style metadata attached to them.

A template's style is defined in the template head using $ directives.

$ introduces compiler-handled directives (so they don't collide with normal variables and can be extended in the future), such as formatter-like built-ins, precedence controls, and default child templates that are automatically applied to direct child templates.

Directive availability is frontend-registry based:


    -- Define a template style
    [$markdown, $children([: All children start with this prefix! ]):
        # Hello
        This template is parsed as markdown.

        @example.com (Here is a link!) using this custom markdown flavour.

        [$todo: write some more info!]

        [: This child is prefixed!]
    ]
    

Frontend Built-in Style Directives

HTML Project Directives

Formatting directives do not automatically flow into nested child templates. If a child template should keep using a formatter such as $markdown, redeclare it in that child template's head.

$fresh is per-child and only affects wrapper application from the immediate parent. Siblings without $fresh still receive the parent wrappers:


    # list = [$children([:<li>[$slot]</li>]):
        <ul>
            [$slot]
        </ul>
    ]

    [list:
        [: one ]
        [$fresh: [: two ]]
    ]
    

In this example, `one` is wrapped with `<li>...</li>`, while `two` opts out and is rendered without the parent $children(..) wrapper.

Slots

Template slots let one template receive content from another template. The default slot is written as and marks where the main body content should be inserted.

Named slots can also be declared with . These allow helper templates to insert content into a specific part of another template using $insert("name").

Positional slots can be declared using positive integers, such as , , etc. Loose contributions (those not explicitly targetting a named slot) are assigned to positional slots first, in ascending numeric order. Any remaining loose contributions go to the default slot if it exists.


    img = [:
        <img src="[$slot(1)]" alt="[$slot]">
    ]

    [img, "logo.png": Site logo]
    

In this example, "logo.png" fills the first positional slot , and "Site logo" fills the default $slot.

Named inserts:


    title = [:
        <h1 style="[$slot("style")]">
            [$slot]
        </h1>
    ]

    blue = [$insert("style"): color: blue;]

    [title, blue:
        Hello world
    ]
    

In this example, `blue` inserts color: blue; into the $slot("style") slot of `title`, while `Hello world` is inserted into the default $slot.

If a template has named or positional slots but no default slot, any loose body content that cannot be assigned to a positional slot is an error. If a slot receives no content, it expands to an empty string. Repeated slots, such as two occurrences of , will replay the same content in both places.

Because $children(..) only applies to direct child templates, nested helpers can scope row and cell wrappers independently.

A child template is treated as an opaque contribution after it has been selected as a direct child. The parent $children(..) wrapper must not descend into that child and wrap its nested templates. If nested templates need their own wrappers, the child template should declare its own $children(..) directive.


    # table = [$children([:<tr>[$slot]</tr>]):
        <table style="[$slot("style")]">
            [$slot]
        </table>
    ]

    # row = [$children([:<td>[$slot]</td>]):
        [$slot]
    ]

    [styled_table:
        [row:
            [: Type]
            [: Description with [codesnippet:inline helper] content]
        ]

        [row:
            [: Float]
            [: 64-bit floating point number]
        ]
    ]
    

In that example, table wraps each direct row child in . Then row wraps each direct cell child in . The nested codesnippet helper inside a cell remains inline content inside that cell; it does not become another table cell.

Template control flow examples


    [head_of_element: content of element that renders as text (markdown / metadata / alt tag / code ..etc)]

    [ loop text_array |text|:
        [text]
    ]

    [if not text.isBlank():
        [ text: plus some extra text in the same element as the variable text ]
    ]
    

Errors

If an error is added to a template at runtime, it will be displayed in the console, rather than injected into the template.