Custom components

Say your docs need a callout box on 40 pages. Pasting its HTML and CSS 40 times works until the day you change it. In chimera you write the callout once as a class that gets the same methods, type checking and theming as the built-in components.

A component

Implementing Component lets your class go anywhere content goes, and Container lets it hold children. tag names the HTML tag it renders as:

class Callout implements Component, Container
    function tag(): string
        return "aside"
    end
    function __init(self, title: string?, content: Content?)
        self:add(Strong(title), content)
    end
    function default_styles(self): { Rule }
        const callout = self:style_selector()
        return {
            Rule(callout, {
                display = "block",
                padding = "0.75rem 1rem",
                border_left = `4px solid {token("primary")}`,
                background = token("surface"),
                border_radius = token("radius"),
            }),
            Rule(`{callout} > p`, { margin = "0.25rem 0 0" }),
        }
    end
end


const warning = Callout("Heads up", Paragraph("Saves from version 1 don't load in version 2."))

default_styles returns the CSS the component needs, and every page that uses the component includes those rules once. style_selector() gives the selector for the class's elements. Rules built on it only ever match your component.

Parsing it back

Implementing Component records the class under its name as soon as the class is defined. Its elements render with data-element="Callout", and the parser uses that attribute to rebuild them as Callouts (see Parsing HTML). A class that should own a plain tag, so every <meter> parses as it, implements HtmlElement as well.

Arranging children

Implement Layout instead of Container and your component also gets row(), grid(), gap() and the rest of the methods Layout describes.

Scripts and stylesheets

When a component needs a script or a stylesheet in the page's <head>, return it from head_elements. A page gets each one once, however many of the components it holds:

function head_elements(self): { Metadata }?
    return { Script(nil, "https://cdn.example.com/charts.js"):defer() }
end

For a small handler, on("click", script) is enough. It writes the code into the element's onclick attribute.

Rendering your own HTML

By default a component renders its tag, its attributes and its children. For anything else, override render and use render_open to write the opening tag:

function render(self, out: Writer)
    self:render_open(out)
    out:write(`<span class="count">{#self.children}</span>`)
    for _, child in self.children do
        child:render(out)
    end
    out:write("</aside>")
end

The Writer knows about the page being rendered, including its path, the site's pages and the themes on offer. out:href_to(path) turns a path from the site's root into a link that works from the current page.

Printing in the terminal

print(warning) shows the element in the terminal as a tree rather than as HTML. Override outline to change the one-line summary each node gets.