JavaScript
gsx supports JavaScript-valued attributes, script interpolation, and JSON data islands. Use explicit JavaScript literals in attributes so code is distinguishable from ordinary text.
JavaScript-valued attributes
Use a js`...` literal for a handler or JavaScript expression:
package views
component Menu() {
<button @click=js`openMenu()`>Open</button>
}Renders:
<button @click="openMenu()">Open</button>Inside the literal, @{ expr } inserts a Go value at a JavaScript value, string, or regular-expression position. Use js"..." when the JavaScript itself contains backticks. See Attributes for literal syntax and Escaping for the trust boundary.
A hole in a JavaScript binding position — an assignment target, or a declaration or member name — must have type gsx.RawJS; it is spliced verbatim. Any other type there is a compile error, since a value would be JSON-quoted and break the code. Wrap the trusted expression in gsx.RawJS(...) to assign to a dynamic path:
component Bind(path string) {
<input @change=js`@{gsx.RawJS(path)} = $event.target.value;`/>
}Keep a js or css literal with @{} holes on the native element that consumes it. A wrapper should accept ordinary parameters and build the contextual literal at that destination:
component SaveButton(id string) {
<button @click=js`save(@{id})`>Save</button>
}
<SaveButton id={id}/>On a component tag, an unbraced, hole-free contextual literal may fall through as authored text. The same unbraced form is rejected when it contains holes.
Contextual literals as Go values
In a Go expression, a js literal has type gsx.RawJS and a css literal has type gsx.RawCSS. Store them in a local variable when a contextual value must be assembled before it reaches the native element:
component Choice(id int, color string) {
{{
behavior := js`select(@{id})`
styles := css`color:@{color}`
}}
<button @click={behavior} style={styles}>Select</button>
}Each hole follows the JavaScript or CSS rules for its position before the typed value is created. Trusted gsx.RawJS and gsx.RawCSS holes retain their documented passthrough; see Escaping. Do not render the literal directly as visible body text.
A component may explicitly accept the trusted type. Use braces so the literal binds the declared parameter as a Go expression:
component Widget(Handler gsx.RawJS, Rule gsx.RawCSS) {
<button @click={Handler} style={Rule}>Go</button>
}
<Widget Handler={js`open(@{id})`} Rule={css`width:@{width}px`}/>Because the literal is a Go value, its holes cannot use an error-returning pipeline or renderer. Top-level declarations also cannot use a filter or renderer that takes ctx; component expressions and {{ }} blocks can.
Alpine and htmx directives
Alpine directive values are JavaScript expressions, so mark x-data, x-model, x-for, x-text, @click, and :key values with js. The same form works for htmx attributes that contain JavaScript.
package views
component AlpineSearch(maxWidth int) {
<div
x-data=js`{
search: '',
items: ['foo', 'bar', 'baz'],
get filteredItems() {
return this.items.filter(i => i.startsWith(this.search))
}
}`
style={css`max-width:@{maxWidth}px`}
>
<input x-model=js`search` placeholder="Search..."/>
<ul>
<template x-for=js`item in filteredItems` :key=js`item`>
<li x-text=js`item`></li>
</template>
</ul>
</div>
}Renders:
<div x-data="{
search: '',
items: ['foo', 'bar', 'baz'],
get filteredItems() {
return this.items.filter(i => i.startsWith(this.search))
}
}" style="max-width:320px"><input x-model="search" placeholder="Search..."><ul><template x-for="item in filteredItems" :key="item"><li x-text="item"></li></template></ul></div>JSON attribute values
JSON is a subset of JavaScript, so attributes such as htmx's hx-vals use the same literal:
package views
component EntityFilter(entityType string, opts map[string]string) {
<input
type="checkbox"
hx-post="/filter"
hx-vals=js`{"entity_type": @{entityType}, "opts": @{opts}}`
/>
}Renders:
<input type="checkbox" hx-post="/filter" hx-vals="{"entity_type": "opportunity", "opts": {"page":"1"}}">Go strings, numbers, structs, maps, and slices in value-position holes are encoded as JSON values. The rendered attribute is then HTML-escaped; the browser restores the JSON text before the consumer reads it. Name keys in the literal and place dynamic holes in value positions.
When the entire attribute value is a single hole — data-labels=js@{labels} — gsx JSON-encodes the whole Go value, so chart `data-*` attributes and a whole-value `hx-vals` need no hand-built JSON. A hole that is *not* the whole value and sits before a token (`js`@{x} = 1, js@{x} + 2``) is a binding/operand position and accepts only gsx.RawJS. This applies to js attributes; a plain { … } attribute still takes only a string/number/bool.
<script> interpolation
Inside <script>, @{ expr } inserts a Go value in the surrounding JavaScript context:
package views
type AppState struct {
Tab string
Open bool
}
component Shell(state AppState) {
<script>
const app = @{ state };
</script>
}Renders:
<script>
const app = {"Tab":"settings","Open":true};
</script>Value-position interpolation produces JSON notation. String and regular-expression positions receive their matching escapes, including escapes that prevent input from ending the <script> element. gsx.RawJS bypasses those protections and is only for JavaScript you trust; see Escaping.
JSON data islands
Use <script type="application/json"> to expose server data without executing it. Interpolation encodes the Go value as JSON, and client code can read the element's text content and pass it to JSON.parse.
package views
type Config struct {
Env string
Beta bool
}
component Widget(cfg Config) {
<div>
<button @click=js`toggle()`>Toggle</button>
<script type="application/json" id="cfg">@{ cfg }</script>
</div>
}Renders:
<div><button @click="toggle()">Toggle</button><script type="application/json" id="cfg">{"Env":"prod","Beta":true}</script></div>