Skip to content

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:

gsx
package views

component Menu() {
	<button @click=js`openMenu()`>Open</button>
}

Renders:

html
<button @click="openMenu()">Open</button>

▶ Open in Playground

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:

gsx
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:

gsx
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.

gsx
<button onclick={label}>…</button>    // label = "hi" → onclick="&#34;hi&#34;"
<button onclick={handler}>…</button>  // handler is gsx.RawJS → verbatim
<button onclick="save()">…</button>   // quoted text is code, verbatim

Write 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:

gsx
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:

gsx
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.

gsx
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:

html
<div x-data="{
search: &#39;&#39;,
items: [&#39;foo&#39;, &#39;bar&#39;, &#39;baz&#39;],
get filteredItems() {
return this.items.filter(i =&gt; 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>

▶ Open in Playground

JSON attribute values ​

JSON is a subset of JavaScript, so attributes such as htmx's hx-vals use the same literal:

gsx
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:

html
<input type="checkbox" hx-post="/filter" hx-vals="{&#34;entity_type&#34;: &#34;opportunity&#34;, &#34;opts&#34;: {&#34;page&#34;:&#34;1&#34;}}">

▶ Open in Playground

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:

gsx
package views

type AppState struct {
	Tab  string
	Open bool
}

component Shell(state AppState) {
	<script>
		const app = @{ state };
	</script>
}

Renders:

html
<script>
		const app = {"Tab":"settings","Open":true};
	</script>

▶ Open in Playground

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.

gsx
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:

html
<div><button @click="toggle()">Toggle</button><script type="application/json" id="cfg">{"Env":"prod","Beta":true}</script></div>

▶ Open in Playground