Skip to main content

Using JavaScript with templ

Script tags

Use standard <script> tags, and standard HTML attributes to run JavaScript on the client.

templ body() {
<script>
function handleClick(event) {
alert(event + ' clicked');
}
</script>
<button onclick="handleClick(this)">Click me</button>
}
tip

To ensure that a <script> tag within a templ component is only rendered once per HTTP response (or context), use a templ.OnceHandle.

Using a templ.OnceHandle allows a component to define global client-side scripts that it needs to run without including the scripts multiple times in the response.

Pass Go data to JavaScript

Pass Go data to a JavaScript event handler

Use templ.JSFuncCall to pass server-side data to client-side scripts by calling a JavaScript function.

input.templ
templ Component(data CustomType) {
<button onclick={ templ.JSFuncCall("alert", data.Message) }>Show alert</button>
}

The data passed to the alert function is JSON encoded, so if data.Message was the string value of Hello, from the JSFuncCall data, the output would be:

output.html
<button onclick="alert('Hello, from the JSFuncCall data')">Show alert</button>

Pass event objects to an Event Handler

HTML element on* attributes pass an event object to the function. To pass the event object to a function, use templ.JSExpression.

warning

templ.JSExpression bypasses JSON encoding, so the string value is output directly to the HTML - this can be a security risk if the data is not trusted, e.g. the data is user input, not a compile-time constant.

input.templ
<script>
function clickHandler(event, message) {
alert(message);
event.preventDefault();
}
</script>
<button onclick={ templ.JSFuncCall("clickHandler", templ.JSExpression("event"), "message from Go") }>Show event</button>

The output would be:

output.html
<script>
function clickHandler(event, message) {
alert(message);
event.preventDefault();
}
</script>
<button onclick="clickHandler(event, 'message from Go')">Show event</button>

Call client side functions with server side data

Use templ.JSFuncCall to call a client-side function with server-side data.

templ.JSFuncCall takes a function name and a variadic list of arguments. The arguments are JSON encoded and passed to the function.

In the case that the function name is invalid (e.g. contains </script> or is a JavaScript expression, not a function name), the function name will be sanitized to __templ_invalid_function_name.

components.templ
templ InitializeClientSideScripts(data CustomType) {
@templ.JSFuncCall("functionToCall", data.Name, data.Age)
}

This will output a <script> tag that calls the functionToCall function with the Name and Age properties of the data object.

output.html
<script>
functionToCall("John", 42);
</script>
tip

If you want to write out an arbitrary string containing JavaScript, and are sure it is safe, you can use templ.JSUnsafeFuncCall to bypass script sanitization.

Whatever string you pass to templ.JSUnsafeFuncCall will be output directly to the HTML, so be sure to validate the input.

Pass server-side data to the client in a HTML attribute

A common approach used by libraries like alpine.js is to pass data to the client in a HTML attribute.

To pass server-side data to the client in a HTML attribute, use templ.JSONString to encode the data as a JSON string.

input.templ
templ body(data any) {
<button id="alerter" alert-data={ templ.JSONString(data) }>Show alert</button>
}
output.html
<button id="alerter" alert-data="{&quot;msg&quot;:&quot;Hello, from the attribute data&quot;}">Show alert</button>

The data in the attribute can then be accessed from client-side JavaScript.

const button = document.getElementById('alerter');
const data = JSON.parse(button.getAttribute('alert-data'));

alpine.js uses x-* attributes to pass data to the client:

templ DataDisplay(data DataType) {
<div x-data={ templ.JSONString(data) }>
...
</div>
}

Pass server-side data to the client in a script element

In addition to passing data in HTML attributes, you can also pass data to the client in a <script> element.

input.templ
templ body(data any) {
@templ.JSONScript("id", data)
}
output.html
<script id="id" type="application/json">{"msg":"Hello, from the script data"}</script>

The data in the script tag can then be accessed from client-side JavaScript.

const data = JSON.parse(document.getElementById('id').textContent);

Avoiding inline event handlers

According to Mozilla, inline event handlers are considered bad practice.

This example demonstrates how to add client-side behaviour to a component using a script tag.

The example uses a templ.OnceHandle to define global client-side scripts that are required, without rendering the scripts multiple times in the response.

component.templ
package main

import "net/http"

var helloHandle = templ.NewOnceHandle()

templ hello(label, name string) {
// This script is only rendered once per HTTP request.
@helloHandle.Once() {
<script>
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
}
<div>
<input type="button" value={ label } data-name={ name }/>
<script>
// To prevent the variables from leaking into the global scope,
// this script is wrapped in an IIFE (Immediately Invoked Function Expression).
(() => {
let scriptElement = document.currentScript;
let parent = scriptElement.closest('div');
let nearestButtonWithName = parent.querySelector('input[data-name]');
nearestButtonWithName.addEventListener('click', function() {
let name = nearestButtonWithName.getAttribute('data-name');
hello(name);
})
})()
</script>
</div>
}

templ page() {
@hello("Hello User", "user")
@hello("Hello World", "world")
}

func main() {
http.Handle("/", templ.Handler(page()))
http.ListenAndServe("127.0.0.1:8080", nil)
}
tip

You might find libraries like surreal useful for reducing boilerplate.

var helloHandle = templ.NewOnceHandle()
var surrealHandle = templ.NewOnceHandle()

templ hello(label, name string) {
@helloHandle.Once() {
<script>
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
}
@surrealHandle.Once() {
<script src="https://cdn.jsdelivr.net/gh/gnat/surreal@3b4572dd0938ce975225ee598a1e7381cb64ffd8/surreal.js"></script>
}
<div>
<input type="button" value={ label } data-name={ name }/>
<script>
// me("-") returns the previous sibling element.
me("-").addEventListener('click', function() {
let name = this.getAttribute('data-name');
hello(name);
})
</script>
</div>
}

Importing scripts

Use standard <script> tags to load JavaScript from a URL.

templ head() {
<head>
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
</head>
}

And use the imported JavaScript directly in templ via <script> tags.

templ body() {
<script>
const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
const lineSeries = chart.addLineSeries();
lineSeries.setData([
{ time: '2019-04-11', value: 80.01 },
{ time: '2019-04-12', value: 96.63 },
{ time: '2019-04-13', value: 76.64 },
{ time: '2019-04-14', value: 81.89 },
{ time: '2019-04-15', value: 74.43 },
{ time: '2019-04-16', value: 80.01 },
{ time: '2019-04-17', value: 96.63 },
{ time: '2019-04-18', value: 76.64 },
{ time: '2019-04-19', value: 81.89 },
{ time: '2019-04-20', value: 74.43 },
]);
</script>
}
tip

You can use a CDN to serve 3rd party scripts, or serve your own and 3rd party scripts from your server using a http.FileServer.

mux := http.NewServeMux()
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets"))))
http.ListenAndServe("localhost:8080", mux)

Working with NPM projects

https://github.com/a-h/templ/tree/main/examples/typescript contains a TypeScript example that uses esbuild to transpile TypeScript into plain JavaScript, along with any required npm modules.

After transpilation and bundling, the output JavaScript code can be used in a web page by including a <script> tag.

Creating a TypeScript project

Create a new TypeScript project with npm, and install TypeScript and esbuild as development dependencies.

mkdir ts
cd ts
npm init
npm install --save-dev typescript esbuild

Create a src directory to hold the TypeScript code.

mkdir src

And add a TypeScript file to the src directory.

ts/src/index.ts
function hello() {
console.log('Hello, from TypeScript');
}

Bundling TypeScript code

Add a script to build the TypeScript code in index.ts and copy it to an output directory (in this case ./assets/js/index.js).

ts/package.json
{
"name": "ts",
"version": "1.0.0",
"scripts": {
"build": "esbuild --bundle --minify --outfile=../assets/js/index.js ./src/index.ts"
},
"devDependencies": {
"esbuild": "0.21.3",
"typescript": "^5.4.5"
}
}

After running npm build in the ts directory, the TypeScript code is transpiled into JavaScript and copied to the output directory.

Using the output JavaScript

The output file ../assets/js/index.js can then be used in a templ project.

components/head.templ
templ head() {
<head>
<script src="/assets/js/index.js"></script>
</head>
}

You will need to configure your Go web server to serve the static content.

main.go
func main() {
mux := http.NewServeMux()
// Serve the JS bundle.
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets"))))

// Serve components.
data := map[string]any{"msg": "Hello, World!"}
h := templ.Handler(components.Page(data))
mux.Handle("/", h)

fmt.Println("Listening on http://localhost:8080")
http.ListenAndServe("localhost:8080", mux)
}

Script templates

warning

Script templates are a legacy feature and are not recommended for new projects.

Use the templ.JSFuncCall, templ.JSONString and other features of templ alongside standard <script> tags to import standalone JavaScript files, optionally created by a bundler like esbuild.

If you need to pass Go data to scripts, you can use a script template.

Here, the page HTML template includes a script element that loads a charting library, which is then used by the body element to render some data.

package main

script graph(data []TimeValue) {
const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
const lineSeries = chart.addLineSeries();
lineSeries.setData(data);
}

templ page(data []TimeValue) {
<html>
<head>
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
</head>
<body onload={ graph(data) }></body>
</html>
}

The data is loaded by the backend into the template. This example uses a constant, but it could easily have collected the []TimeValue from a database.

main.go
package main

import (
"fmt"
"log"
"net/http"
)

type TimeValue struct {
Time string `json:"time"`
Value float64 `json:"value"`
}

func main() {
mux := http.NewServeMux()

// Handle template.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data := []TimeValue{
{Time: "2019-04-11", Value: 80.01},
{Time: "2019-04-12", Value: 96.63},
{Time: "2019-04-13", Value: 76.64},
{Time: "2019-04-14", Value: 81.89},
{Time: "2019-04-15", Value: 74.43},
{Time: "2019-04-16", Value: 80.01},
{Time: "2019-04-17", Value: 96.63},
{Time: "2019-04-18", Value: 76.64},
{Time: "2019-04-19", Value: 81.89},
{Time: "2019-04-20", Value: 74.43},
}
page(data).Render(r.Context(), w)
})

// Start the server.
fmt.Println("listening on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Printf("error listening: %v", err)
}
}

script elements are templ Components, so you can also directly render the Javascript function, passing in Go data, using the @ expression:

package main

import "fmt"

script printToConsole(content string) {
console.log(content)
}

templ page(content string) {
<html>
<body>
@printToConsole(content)
@printToConsole(fmt.Sprintf("Again: %s", content))
</body>
</html>
}

The data passed into the Javascript function will be JSON encoded, which then can be used inside the function.

main.go
package main

import (
"fmt"
"log"
"net/http"
"time"
)

func main() {
mux := http.NewServeMux()

// Handle template.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Format the current time and pass it into our template
page(time.Now().String()).Render(r.Context(), w)
})

// Start the server.
fmt.Println("listening on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Printf("error listening: %v", err)
}
}

After building and running the executable, running curl http://localhost:8080/ would render:

Output
<html>
<body>
<script>function __templ_printToConsole_5a85(content){console.log(content)}</script>
<script>__templ_printToConsole_5a85("2023-11-11 01:01:40.983381358 +0000 UTC")</script>
<script>__templ_printToConsole_5a85("Again: 2023-11-11 01:01:40.983381358 +0000 UTC")</script>
</body>
</html>

The JSExpression type is used to pass arbitrary JavaScript expressions to a templ script template.

A common use case is to pass the event or this objects to an event handler.

package main

script showButtonWasClicked(event templ.JSExpression) {
const originalButtonText = event.target.innerText
event.target.innerText = "I was Clicked!"
setTimeout(() => event.target.innerText = originalButtonText, 2000)
}

templ page() {
<html>
<body>
<button type="button" onclick={ showButtonWasClicked(templ.JSExpression("event")) }>Click Me</button>
</body>
</html>
}