Attributes
Use quoted strings for static values, braces for Go expressions, and typed literals when an attribute contains interpolated text, JavaScript, or CSS.
<input name="email" value={value} required={required}/>
<a href=f`/users/@{id}`>Profile</a>
<button @click=js`save(@{id})`>Save</button>
<div style=css`color:@{color}`>...</div>Expression attributes
Use name={expr} to bind a Go value.
package views
component Link(url string, label string, count int) {
<a href={url} data-count={count}>{ label }</a>
}Renders:
<a href="/p?q=a&b" data-count="42">Docs</a>data-count={count} formats a numeric value as attribute text. Quoted values are literal: title="Item @{id}" does not scan for @{} holes.
Boolean attributes
A bool renders as presence — true writes a bare attribute, false omits it — on every name except the handful whose HTML values are the strings "true" and "false".
<div data-open={open} hidden={done} aria-expanded={open}><div data-open hidden aria-expanded="true">The exceptions are the aria-* states plus contenteditable, spellcheck, draggable, writingsuggestions, SVG focusable and MathML displaystyle. There the string carries meaning absence cannot: a screen reader announces aria-expanded="false" as collapsed and says nothing at all when the attribute is missing, and contenteditable, spellcheck and displaystyle inherit, so only ="false" opts a subtree out.
Everything else is presence — including title={b} and library names like x-show/hx-boost, which you write as strings or js literals anyway (x-show=js`open` , hx-boost="false"). Static and string values are never touched by this rule: required="foo" and hx-boost="false" render verbatim. style={b} is its own case: it goes through the class/style merge, which always writes a value.
A bare attribute (<input required>) is always present, wherever it travels — including through a component's attrs bag.
- Want the string from a Go bool? Say so:
data-x={ strconv.FormatBool(b) }. - Want presence on an
aria-*name?gsx.Toggle(b).
Attributes where absence is the permissive state
sandbox={untrusted} and crossorigin={cors} omit the attribute when the bool is false — and for sandbox, no attribute means no sandbox at all. Make sure absence is the state you want, or write the value: sandbox="allow-scripts".
package views
component Field(on bool) {
<input type="text" class="form-control" required disabled={on}/>
}Renders:
<input type="text" class="form-control" required disabled>Conditional attributes
Use { if cond { ... } } inside an opening tag when a condition contributes one or more attributes.
package views
component Badge(featured bool) {
<span
{ if featured {
class="featured"
} }
>
content
</span>
}Renders:
<span class="featured">content</span>An else branch works too: { if active { class="active" } else { class="idle" } }.
{ switch … } is the switch counterpart, for when one of several mutually exclusive attribute sets applies:
<div
{ switch status {
case "ok":
data-state="ok"
case "warn", "error":
data-state="bad" aria-live="polite"
default:
data-state="unknown"
} }
>It takes the same shapes Go's switch does — a tag expression, multi-value case lists, a tagless switch { case cond: … }, and default — and evaluates its tag exactly once. fallthrough is not supported: an attribute list is not a statement list, so falling through would emit two arms' attributes and duplicate names. List the shared attributes in each case instead.
An arm with no matching case contributes nothing, exactly like an if with no else.
Spread { x… } — ordered
Spread a gsx.Attrs bag with { bag... }; entries render in slice order.
package views
import "github.com/gsxhq/gsx"
component Card(extra gsx.Attrs) {
<div { extra... }>content</div>
}Renders:
<div id="box" data-active="true">content</div>- Source order is kept.
- A
booltakes the same rule as an element attribute: presence, except on the names whose HTML values are"true"/"false"(aria-*,contenteditable,spellcheck,draggable).gsx.Toggle(b)forces presence on any name. - Spread values are escaped for the destination attribute; URL and
srcsetdestinations also run scheme sanitization. gsx.AttrMap.ToAttrs()sorts map keys.
Treat keys as trusted code. Build gsx.Attr and gsx.Attrs keys only from developer-controlled strings; never use user input as a key. Values are still escaped or URL-sanitized for their destination. For dynamic JavaScript or CSS, use a typed literal on the final native element; see JavaScript and CSS contexts.
Ordered-attrs literal {{ "k": v }}
Use {{ "k": v }} as the value of a component's attrs-bag parameter when the call site must declare the bag in source order.
package views
import "github.com/gsxhq/gsx"
component Counter(signals gsx.Attrs, children gsx.Node) {
<button { signals... }>{ children }</button>
}
component Page() {
<Counter signals={{ "data-signals": "{count:0}", "data-text": "$count", "data-on-click": "$count++" }}>Count</Counter>
}Renders:
<button data-signals="{count:0}" data-text="$count" data-on-click="$count++">Count</button>- The literal is valid only as a component attribute value.
- Keys must be quoted strings.
- A
booltakes the same rule as an element attribute: presence, except on the names whose HTML values are"true"/"false"(aria-*,contenteditable,spellcheck,draggable).gsx.Toggle(b)forces presence on any name. - The last scalar value for a key wins.
- All
classandstylevalues compose.
Contributing to the declared attrs input
<Panel id="profile" attrs={computed} { defaults... } attrs={{ "role": "region" }}/>- Ordinary unmatched attributes, conditional attributes, spreads,
attrs={expr}, andattrs={{...}}compose in authored order. - Names inside a conditional-attribute group are bag contributors; they do not conditionally fill ordinary component parameters.
attrs={expr}accepts a computed attrs-bag value.- Repeated explicit
attrscontributors are allowed; they do not fill an ordinary parameter slot. - The component must declare the reserved
attrsparameter.
Contextual escaping
Ordinary values are attribute-escaped. Dynamic URL expressions, interpolated URL literals, and forwarded URL values also reject dangerous schemes. A quoted URL authored directly on a native element is trusted author text and is emitted without scheme validation.
package views
component Link(href string, label string) {
<a href={href}>{ label }</a>
}Renders:
<a href="about:invalid#gsx">click me</a>Use typed literals for JavaScript- and CSS-valued attributes:
<button @click=js`save(@{id})`>Save</button>
<div style=css`color:@{color}`>...</div>See Escaping and JavaScript for the complete context rules.
Interpolating attribute literals
Use f literals to combine static text with typed @{expr} holes.
package views
component Row(id string, n int) {
<div data-key=f`row-@{id}-@{n}` title=f`Item @{id |> upper}`>Row</div>
}Renders:
<div data-key="row-a&b-5" title="Item A&B">Row</div>- Use
f"..."when the content contains a backtick. - Inside a backtick-delimited literal,
\`writes a literal backtick. \@{writes a literal@{instead of opening a hole.
On a component tag
<PageHeader title="Tickets" subtitle=f`@{count} tickets`/>A matching string parameter receives the assembled string. A matching gsx.Node parameter receives an escaped text node. An unmatched name falls through to the component's attrs bag.
URL attributes sanitize the whole value
Static text and holes in an interpolated URL literal are assembled before the URL scheme check.
package views
component Link(x string) {
<a href=f`javascript:@{x}`>go</a>
}Renders:
<a href="about:invalid#gsx">go</a>data:image literals
<img src=f`data:image/png;base64,@{b64}`/>
<img src={imageBytes |> dataURL("image/png")}/>
<img src={"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E"}/>Image sinks accept both base64 and strictly validated plain-text payloads. A data: literal is rejected on strict navigation sinks such as href, and a constant value that its sink always blocks warns at generate time. See Escaping for image and navigation sink rules.
class and style are merge targets
Interpolated class and style values merge with a forwarded attrs bag.
package views
import "github.com/gsxhq/gsx"
component Badge(variant string, attrs gsx.Attrs) {
<span class=f`badge-@{variant}` { attrs... }>Hi</span>
}Renders:
<span class="badge-x hl" id="a">Hi</span>See Styling for precedence.