Authoring gsx templates
SkillFiles & storageUse when writing or editing .gsx files — gsx templating components, JSX-style markup in Go, or when asked about gsx syntax (component declarations, interpolation, attributes, control flow, class/style, children/fallthrough).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Authoring gsx templates skill
What this skill tells your AI
The instructions your AI receives, as published by gsxhq/gsx in skills/gsx/SKILL.md and read by ahel’s review.
gsx is a Go templating language: templ-style component declarations with a
JSX-style markup body, compiled to plain Go (.gsx → .x.go).
Status: gsx is runnable —
gsx generatecompiles.gsx→.x.go. The language is stable but still evolving (some CLI commands andstylecomposition are in progress). Treat theinternal/corpus/testdata/cases/**/*.txtarcorpus as the canonical, current reference — each case pins the.gsxinput, generated Go, and rendered output — and prefer copying patterns from there.
Core rules
- A
.gsxfile is ordinary Go (package, imports, types, funcs) pluscomponentdeclarations. Put non-template Go (helpers, types) in the same file as normal Go. - A component body is emission — the markup is the result. No return type, no
returnkeyword. Bare Go statements are not allowed in the body; wrap them in{{ … }}. - Capitalization decides the tag's meaning: lowercase/hyphenated → HTML element
(
<div>,<el-dialog>); Capitalized/dotted → component (<Card>,<ui.Button>). - A component's authored parameter list is emitted unchanged. Markup binds ordinary
parameters by exact name; direct Go callers use the same positional signature.
For
component Card(title string, featured bool),<Card title="Hi" featured/>bindstitleandfeatured, while Go callsCard("Hi", true). - Lowercase
childrenandattrsare reserved roles only when declared as parameters.childrenreceives the markup body.attrsreceives unmatched attributes and ordered bag contributors such asattrs={bag}andattrs={{ "id": id }}. CapitalizedChildrenandAttrsare ordinary inputs. - Whitespace collapses JSX-style (
internal/wsnorm): a newline between elements is dropped (no space); a same-line run of spaces collapses to one. So inline tokens split across elements on separate lines render with no space between them — put an explicit space inside an element (<span> → </span>,<span>label: </span>) or keep them on one line. Bodies ofpre/textarea/script/styleare kept verbatim.
Forms
- Interpolation:
{ expr }(auto-escaped per context: HTML/attr/URL/CSS). Opt-outs, each bypassing one check:gsx.Raw(s)(HTML),gsx.RawURL(s)(URL scheme check),gsx.RawCSS(s)(CSS value-filter). JS/event-handler (on*) contexts are a compile error, not auto-escaped. - Error handling: a
(T, error)interpolation/attr value ({ f() },name={ f() }) auto-unwraps toT; the error propagates out of the enclosingRender. There is no?try-marker. To handle the error instead, use{ if v, err := f(); err != nil { … } }. - Attributes: static
name="lit", dynamicname={ expr }, boolean barename, type-drivendisabled={ cond }(bool → bare/omitted), spread{ expr... }, conditional{ if … }/{ for … }inside the tag. - Composable
class/style:class={ a, "cls": cond, x }(comma list;"cls": condis conditional sugar). - Control flow contributing children:
{ if/for/switch … { <markup> } }. - Go escape hatch (no output):
{{ stmt }}. Fragments:<>…</>. - Children: declare
children gsx.Nodeorchildren ...gsx.Node, then place it with{children}. A component without that parameter rejects a non-empty body. - Attribute fallthrough: declare
attrs gsx.Attrs(or another supported attrs-bag shape), then spread{ attrs... }where the component chooses. A component withoutattrsrejects unmatched attributes and attrs-bag contributors.
Coming from JSX (don't get confused)
Same shape, but the holes are Go, not JavaScript:
- Values are Go:
{ expr },name={ expr }— not JS, not template strings. class(notclassName), composable:class={ base, "on": active }.- Control flow is blocks, not operators:
{ if c { … } },{ for _, x := range xs { … } }— no&&, ternary, or.map(). - Children:
{children}in the body;<C>…</C>at the call site — noprops.children. - Event handlers are strings, not funcs:
@click=js`open = !open`(Alpine), notonClick={fn}. No output from{{ stmt }};gsx.Raw(s)is thedangerouslySetInnerHTMLopt-out. Body is emission — noreturn. - Same as JSX:
<>…</>fragments, positional markup-vs-Go, whitespace collapse.
The one subtlety: markup vs Go
Inside { }, gsx decides markup-vs-Go positionally (the Babel rule):
{ <div/> } is markup, { a < b } is a Go expression. If a comparison or generic
looks like a tag, reach for parentheses or a {{ }} block. See the
internal/corpus/testdata/cases/parser/ corpus cases.
When in doubt
Read the matching corpus directory under internal/corpus/testdata/cases/:
elements elements/, escaping interpolation/ + security/, control flow
control_flow/, components/children/slots components/ + slots/, attributes
attrs/ + class/ + style/ + jsattr/, corner cases parser/, method
components methods/, fallthrough fallthrough/. Full guide: docs/guide/syntax.md.
Signals
- GitHub stars
- 54
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
gsx- Source
- github.com/gsxhq/gsx