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.

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.

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.

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