Skip to content

templ and html/template ​

structpages only needs a component with Render(context.Context, io.Writer) error, so any renderer works. Every pattern in gsx Patterns — Props, partials, cross-page rendering, testing with PageContext — applies as is; only the markup differs. The url, id and target filters are gsx-only: other renderers call URLFor, ID and IDTarget themselves.

templ ​

templ.Component works unchanged.

Syntax mapping ​

gsxtempl
component (p Index) Page(props indexProps) { … }templ (p Index) Page(props indexProps) { … }
<Layout><p.Content props={props}/></Layout>@Layout() { @p.Content(props) }
children gsx.Node + { children }{ children... }
href={Detail{} |> url(params)}href={ structpages.URLFor(ctx, Detail{}, params) }
id={Index.List |> id}id={ structpages.ID(ctx, Index.List) }
hx-target={Index.List |> target}hx-target={ structpages.IDTarget(ctx, Index.List) }
RenderComponent(<Widget n={n}/>)RenderComponent(Widget(n))

templ has no pipeline filters, so call URLFor, ID and IDTarget directly. Attribute expressions accept (string, error) and return the error from Render — the same hoisting gsx does — so no must() helper and no urlFor/idFor wrapper is needed. href takes a plain string; templ.SafeURL is not needed for URLs from URLFor.

templ
templ (p index) Content(props IndexProps) {
    <form hx-post={ structpages.URLFor(ctx, addTodo{}) }
          hx-target={ structpages.IDTarget(ctx, index.TodoList) }>
        <input name="text"/>
    </form>
    <div id={ structpages.ID(ctx, index.TodoList) }>
        @p.TodoList(props.Todos)
    </div>
}

Places that need a plain string ​

templ.Attributes map values and script arguments can't take (string, error). Resolve the value where an error can be returned instead of panicking:

  • compute it in Props (or a Go helper returning (T, error)) and pass it in the props struct;
  • or give the child component an explicit parameter for the URL and set the attribute on the element inside it, where { structpages.URLFor(...) } is allowed.

html/template ​

A page component returns any value with a Render method, so a small adapter type puts a template behind it. examples/html-template parses one template set per page and returns the entry to execute:

go
type tpl struct {
    page  string // which parsed set
    entry string // which template in it
    data  any
}

func (p tpl) Render(_ context.Context, w io.Writer) error {
    t, ok := pageTmpls[p.page]
    if !ok {
        return fmt.Errorf("unknown page %q", p.page)
    }
    return t.ExecuteTemplate(w, p.entry, p.data)
}

type post struct{}

func (post) Page(props postProps) tpl     { return tpl{page: "post", entry: "layout/public", data: props} }
func (post) Comments(props postProps) tpl { return tpl{page: "post", entry: "post/comments-list", data: props.Comments} }

Partials work as with gsx: each method returning a tpl is a component that HX-Target can select, once the mount has an htmx target selector (this example serves htmx 4, so HTMXv4RenderTarget).

Templates have no render ctx, so give them URLs through a template function. The example binds one to the mounted *StructPages once, at parse time, resolving pages by Ref:

go
sp, err := structpages.Mount(mux, index{}, "/", "Index",
    structpages.WithTargetSelector(structpages.HTMXv4RenderTarget),
)
if err != nil {
    log.Fatalf("mount: %v", err)
}
funcs := template.FuncMap{
    "urlFor": func(name string, args ...any) (string, error) {
        return sp.URLFor(structpages.Ref(name), args...)
    },
}
html
<a hx-get="{{ urlFor "index" }}" hx-target="main" hx-push-url="true">Home</a>

A template function returning (string, error) stops execution with that error, so a bad name fails the render. sp.URLFor has no request context, so params aren't filled from the current URL: pass every path param explicitly. The Ref strings are data to the compiler, and the linter doesn't read .html templates, so a boot-time URL inventory (Testing) is what catches a renamed page here.

Lint ​

structpages-lint checks URLFor/ID/IDTarget/Ref calls in templ's generated Go like any other Go, and its url-attr category scans .templ files for hard-coded internal URLs. It doesn't read html/template files. Suppress with the Go-style directive, which is stripped from the output (see Lint).