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.
Event handlers
An event-handler attribute is any name starting with on — after a data- or svg:-style prefix — as in html/template. A Go value there ({expr}, or a bare `…` raw string) is encoded as a JavaScript value: a string becomes a quoted string literal, other values their JSON, and a gsx.RawJS passes through as code.
<button onclick={label}>…</button> // label = "hi" → onclick=""hi""
<button onclick={handler}>…</button> // handler is gsx.RawJS → verbatim
<button onclick="save()">…</button> // quoted text is code, verbatimWrite handler code as quoted text, js`…` or gsx.RawJS. A spread gsx.Attrs key is encoded the same way; an f or css literal on a handler is an error. Framework attributes such as @click and x-on:click are not event handlers: mark their JavaScript with js.
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.
Both libraries evaluate these strings at runtime, which a strict Content Security Policy blocks; see CSP nonces.
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.
A <script> is JavaScript when it has no type, type="module", or a JavaScript MIME type — legacy spellings such as application/x-javascript included, ;parameters ignored. Any other type is a data block: its body must be a single @{ } value.
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>