Skip to content

Rendering elements

alacris.Element implements templ.Component, so it goes anywhere a component goes and picks up children from the block around it.

@alacris.E("user-card").Prop("name", "Ada") {
<h3 slot="title">Ada Lovelace</h3>
}

Builder methods return the element, so they chain. They collect errors rather than panicking, and Render reports the first one. A bad tag name or an unencodable prop fails the request instead of producing quietly broken HTML.

Prop takes the JavaScript prop name from define(). The library converts it to the attribute the element observes.

return alacris.E("user-card").
Prop("name", "Ada").
Prop("age", 36).
Prop("tags", []string{"math", "code"})
renders verified by go test
<user-card name="Ada" age="36" tags="[&#34;math&#34;,&#34;code&#34;]"></user-card>
which the browser turns into the real runtime, upgrading that markup

Props are kebab-cased and encoded by their Go type.

A nil value leaves the attribute off, so the component keeps the default it declared:

// A nil value leaves the attribute off entirely, so the component keeps
// the default it declared in define().
return alacris.E("x-demo").
Prop("name", nil).
Prop("tags", []string(nil)).
Prop("kept", "written")
renders verified by go test
<x-demo kept="written"></x-demo>
which the browser turns into the real runtime, upgrading that markup

A nil prop leaves the component's own default alone.

PropRaw sets an already-encoded value when you need to bypass the encoding rules, and Props takes a map, applied in sorted key order for stable output.

Attr follows HTML rules rather than alacris’: a true bool renders bare and a false one disappears. That is the opposite of how props behave, on purpose: hidden and disabled are HTML, not component API.

return alacris.E("user-card").
ID("ada").
Class("card", "card--wide").
Attr("hidden", false).
Attr("aria-label", "A person").
Attr("data-testid", "user-card")
renders verified by go test
<user-card
class="card card--wide"
id="ada"
aria-label="A person"
data-testid="user-card"
></user-card>
which the browser turns into the real runtime, upgrading that markup

Ordinary attributes follow HTML rules: a false bool disappears.

ID, Class and ClassIf are shorthands. Attrs spreads a templ.Attributes map.

alacris.E("user-card").
ID("ada").
ClassIf(selected, "is-selected").
Attrs(templ.Attributes{"data-index": 3})

Attr("class", …) appends to the class list rather than replacing it. Attr("style", …) is refused, because Style and Var sanitize their input and a raw string cannot be checked usefully.

Var sets a CSS custom property, which is how a themed component is configured from outside its shadow root:

chip := alacris.Vars("--chip-bg", "--chip-fg", "--chip-radius")
return alacris.E("ala-chip").
Apply(chip, map[string]string{
"--chip-bg": "#ffe9a8",
"--chip-radius": "999px",
}).
Text("new")
renders verified by go test
<ala-chip style="--chip-bg:#ffe9a8;--chip-radius:999px">new</ala-chip>
which the browser turns into the real runtime, upgrading that markup
new

Custom properties reach through the shadow boundary.

Both the property and the value are checked. Anything that could end the declaration, end the attribute, or open a comment is an error rather than a silent substitution:

alacris.E("x-y").Var("bg", "red;position:fixed")
// render fails: alacris: character ';' is not allowed in an inline CSS value

See Theming for VarSet, which turns a component’s set of custom properties into something Go can check.

Slot content is light DOM. It is in the document, in the first paint, and visible to a crawler. Put content that matters when JavaScript has not arrived yet here.

Three ways to fill a slot, in order of preference:

{/* 1. On your own markup. No wrapper element, full control. */}
@components.UserCard(props) {
<h3 slot={ components.UserCardSlotTitle }>Ada Lovelace</h3>
}
// 2. Slot, when the content comes from Go. Wraps it in a <div slot="...">.
alacris.E("info-card").Slot("title", heading())
// 3. SlotAs, when the wrapper element matters to layout.
alacris.E("info-card").SlotAs("h3", "title", heading())
return alacris.E("info-card").
Prop("tone", "warn").
Slot("title", text("Ada Lovelace")).
SlotAs("p", "body", text("Wrote the first algorithm."))
renders verified by go test
<info-card tone="warn">
<div slot="title">Ada Lovelace</div>
<p slot="body">Wrote the first algorithm.</p>
</info-card>
which the browser turns into the real runtime, upgrading that markup
Ada Lovelace

Wrote the first algorithm.

Slot content is light DOM, so it is in the first paint.

Children sets children programmatically instead of taking them from the enclosing block, and Text sets a single escaped text child.

A custom element that is in the document but not yet upgraded is empty and has no size. The page paints, then reflows when the module lands.

<head>
@alacris.Pending{Tags: ui.Tags}
@alacris.Scripts(cfg)
</head>
return alacris.Pending{Tags: []string{"user-card", "ala-chip"}}
renders verified by go test
<style>
user-card:not(:defined),ala-chip:not(:defined){visibility:hidden}
</style>

Hides elements until their module has defined them.

ui.Tags is generated, so the list cannot fall behind the components. For Alacris UI, ui.Pending() (from github.com/bmartel/alacris-go/ui) is the same list for every design-system tag. The declaration defaults to visibility: hidden; pass Style to choose another.

alacris.Pending{
Tags: ui.Tags,
Style: map[string]string{"opacity": "0", "transition": "opacity .15s"},
}

Every builder method records problems instead of panicking:

el := alacris.E("div") // no hyphen: not a custom element
el.Prop("id", int64(1)<<60) // beyond JavaScript's safe range
if err := el.Err(); err != nil {
// ... or just let Render return it
}

This is why a typo surfaces as a failed request rather than an element the browser silently ignores.