Arandu View Templates
arandu-io/arandu
Writes and changes pages, layouts, forms and HTMX fragments in an Arandu Go app using its .kyse.go templates, escaping rules and Content-Security-Policy limits.
Builds and edits HTML views in Go with gomponents, where components are plain functions that return a Node, instead of using template languages.
$ npx skills add maragudk/gomponents --skill gomponents -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install maragudk/gomponents gomponents --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/maragudk/gomponents.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/gomponents .claude/skills/gomponents && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "gomponents" agent skill from https://github.com/maragudk/gomponents/tree/main/skills/gomponents into .claude/skills/gomponents/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "gomponents", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/maragudk/gomponents/tree/main/skills/gomponentsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add maragudk/gomponents --skill gomponents -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install maragudk/gomponents gomponents --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/maragudk/gomponents.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/gomponents .agents/skills/gomponents && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "gomponents" agent skill from https://github.com/maragudk/gomponents/tree/main/skills/gomponents into .agents/skills/gomponents/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "gomponents", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add maragudk/gomponents --skill gomponents -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install maragudk/gomponents gomponents --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/maragudk/gomponents.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/gomponents .cursor/skills/gomponents && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "gomponents" agent skill from https://github.com/maragudk/gomponents/tree/main/skills/gomponents into .cursor/skills/gomponents/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "gomponents", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/maragudk/gomponents.git --path skills/gomponents--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add maragudk/gomponents --skill gomponents -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install maragudk/gomponents gomponents --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/maragudk/gomponents.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/gomponents .gemini/skills/gomponents && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "gomponents" agent skill from https://github.com/maragudk/gomponents/tree/main/skills/gomponents into .gemini/skills/gomponents/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "gomponents", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install maragudk/gomponents gomponentsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add maragudk/gomponents --skill gomponents -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/maragudk/gomponents.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/gomponents .github/skills/gomponents && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "gomponents" agent skill from https://github.com/maragudk/gomponents/tree/main/skills/gomponents into .github/skills/gomponents/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "gomponents", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add maragudk/gomponents --skill gomponents -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install maragudk/gomponents gomponents --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/maragudk/gomponents.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/gomponents .opencode/skills/gomponents && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "gomponents" agent skill from https://github.com/maragudk/gomponents/tree/main/skills/gomponents into .opencode/skills/gomponents/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "gomponents", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
gomponentsBuilds and edits HTML views in Go with gomponents, where components are plain functions that return a Node, instead of using template languages.
The skill makes gomponents the required way to write any HTML or UI in a Go application. A component is a Go function returning a Node, and a Node renders itself to an io.Writer as HTML5, with no template language, code generation or dependencies. Elements, attributes and text all implement Node and are passed as children to variadic element functions, so nested calls mirror nested tags.
It documents the core helpers: Text for escaped content, Raw for markup you wrote yourself, Map to turn slices into nodes, Group to return siblings, If and Iff for conditionals, and El and Attr for custom elements and attributes the html package lacks. It also covers conventions that ordinary Go and HTML habits get wrong, such as dot imports, and includes a linting reference. It steps aside for pure handler, database and business logic, plain css, js or html files, and conceptual questions.
Read from SKILL.md and the folder at commit 90943aa. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
goFrom the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Go HTML Views with Gomponents loads about 3.6k tokens when it runs, and up to ~3.7k if it reads all its reference files. Until then it costs about 200 tokens; SKILL.md has 1,496 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from maragudk/gomponents at commit 90943aa, republished under its MIT licence (© maragudk). 1,496 words, ~3,595 tokens.
.claude/skills/gomponents/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.gomponents is HTML components in pure Go. A component is a Go function that returns a Node, and a Node renders itself to an io.Writer as HTML5. There is no template language, no code generation, and no dependencies.
go get maragu.dev/gomponents@latestEverything is a Node:
type Node interface {
Render(w io.Writer) error
}Elements, attributes, and text all implement it, and all are passed as children to the same variadic element functions. The result is like a DSL for HTML, in valid Go: an element function takes children, an attribute function takes a value, and nesting calls mirrors nesting tags. Attribute children go in the tag and everything else goes between the tags, whatever order you pass them in; by convention, write attributes first.
<a href="/about" class="nav-link">About</a>A(Href("/about"), Class("nav-link"), Text("About"))The idiomatic way is to write it declaratively, but a component is an ordinary function and can be as imperative as it needs to be. gomponents does not check that the result is valid HTML, by design, just as writing HTML by hand doesn't.
The core package provides these functions. Everything else is in the html and components packages.
| Function | What it does |
|---|---|
Text(s) / Textf(format, args...) | HTML-escaped text. The default for all content, and the strongly recommended choice for unsanitized user content. |
Raw(s) / Rawf(format, args...) | Unescaped text. For markup you wrote yourself: inline SVG, <script> and <style> bodies. Never for unsanitized user content. |
Map(slice, func(T) Node) Group | Turns a slice of data into nodes. |
Group{...} / Group(nodes) | A []Node that renders as one node. Use it to return several siblings, or to pass a children ...Node slice on. |
If(cond, node) | node if cond, else nil, and nil children are skipped. The node argument is evaluated either way. |
Iff(cond, func() Node) | Like If, but the function only runs when cond is true. |
El(name, children...) | Any element. Use it for elements the html package lacks: custom elements, or SVG children such as El("path", Attr("d", "...")). |
Attr(name) / Attr(name, value) | Boolean or valued attribute. Use it for attributes the html package lacks: Attr("hx-get", "/items"), Attr("onclick", "..."). More than one value panics. |
Element and attribute names are written verbatim, so they must be trusted compile-time values. Attribute values, Text, and Textf are escaped. Never build a name from user input, as in Data(userKey, v) or El(tagFromRequest).
An example component using all of the above except El and Attr:
package html
import (
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/components"
. "maragu.dev/gomponents/html"
)
type NavLink struct {
Href, Text string
}
type User struct {
Name string
Unread int
}
// Navbar shows the site links and, for a logged-in user, their name and a log out link.
// user is nil for a visitor.
func Navbar(links []NavLink, currentPath string, user *User) Node {
return Nav(Class("navbar"),
Map(links, func(l NavLink) Node {
return A(Href(l.Href), Classes{"link": true, "is-active": l.Href == currentPath}, Text(l.Text))
}),
If(user == nil, A(Href("/login"), Text("Log in"))),
// user.Name must only run when user is not nil.
Iff(user != nil, func() Node {
return Group{
Span(Textf("%v (%v unread)", user.Name, user.Unread)),
A(Href("/logout"), Text("Log out")),
}
}),
Raw(`<svg viewBox="0 0 16 16" width="16" height="16"><circle cx="8" cy="8" r="7"/></svg>`),
)
}If builds its node before checking the condition. If(user != nil, Text(user.Name)) panics when user is nil, because user.Name runs first. Use Iff when the node reads through a pointer, indexes a slice, or does work worth skipping.IfElse. Use two Ifs with opposite conditions, or a function with a switch when the branches are more than one node.Map has no index. When you need one, keep a counter in the closure, or use maragu.dev/gomponents/x/slices, whose Map and Filter pass the index and return plain slices to spread with .... Packages under x/ are experimental and may change, although they probably won't.children ...Node and pass them on with Group(children). Groups are transparent, so a caller's attributes (ID, Name) are placed on the root element. A block like input(children ...Node) that only adds classes to Input needs no parameters, because callers pass Type("email"), Name("email"), or Required() as children and they land on the input.JoinAttrs. A block joins its own Class with whatever the caller passes, so card(Class("mt-4"), ...) renders one class attribute. See the components package below.nil is skipped inside the tag too: If(disabled, Disabled()), Classes{...}, Value(u.Email).Group when it has several root elements.fmt.Stringer, so fmt.Println(node) works when debugging.<script> and <style> bodies need Raw. Text would turn && into && and break the code. Keep unsanitized user data out of those bodies; pass it through Data attributes instead, which are escaped and unescaped by the browser.Class calls make two attributes, and the browser keeps only the first. Combine them into one string, use Classes, or use JoinAttrs.Img(Text("x")) renders <img> with no error.Dot-import the three main packages so components read like HTML. This is the strongly preferred style unless the project already imports them another way. Whichever style the project uses, use it in every file that mentions a Node, handler files included: write Node, not g.Node.
Put components in a package named html, unless the project already has one under another name. A package named html can dot-import maragu.dev/gomponents/html without conflict.
Inside that package, export only what another package uses, which is usually just the pages and fragments:
func LoginPage(props LoginPageProps) Node, func UserRow(u User) Node.button, input, label, a, card, container. Names with a lower-case first letter cannot collide with the dot-imported Button, Input, Label, and A. When a block does need exporting, give it a specific name (SubmitButton, not Button) rather than dropping the dot import.The http package often clashes with net/http, so consider aliasing it: ghttp "maragu.dev/gomponents/http".
html package: elements and attributesElement functions take ...Node. Attribute functions take one string value, or nothing for boolean attributes (Required(), Disabled(), Checked()). A few take ...string, for attributes that are valid both with and without a value. All values are strings, so convert numbers yourself: Width(strconv.Itoa(w)).
Names follow the HTML names with Go casing: word boundaries are capitalized (ColSpan, TabIndex, MaxLength, FieldSet, FigCaption, SrcSet, AutoComplete) and initialisms are upper-case (ID, HTML, SVG, IFrame, THead, TBody, TFoot, HGroup). A few HTML names are both an element and an attribute, so one side gets a suffix:
| HTML name | Element | Attribute |
|---|---|---|
cite | Cite | CiteAttr |
data | DataEl | Data(name, value), renders data-<name> |
form | Form | FormAttr |
label | Label | LabelAttr |
slot | SlotEl | SlotAttr |
style | StyleEl | Style |
title | TitleEl | Title |
CiteEl, DataAttr, FormEl, LabelEl, StyleAttr, and TitleAttr still compile but are deprecated.
Data("id", v) renders data-id="..." and Aria("label", v) renders aria-label="...".
When unsure whether a helper exists or how it is cased, check rather than guess:
go doc maragu.dev/gomponents/html | grep -i colspanIf nothing turns up, El and Attr produce the same output a dedicated helper would.
components packageHTML5 renders a complete document: doctype, <html>, a <head> with charset, viewport, title, and optional description, and the <body>. Attribute nodes in Body or Head are placed on that element. Most apps have one layout function like this, and their pages call it:
func page(title, description string, body ...Node) Node {
return HTML5(HTML5Props{
Title: title + " - MyApp",
Description: description,
Language: "en",
HTMLAttrs: Group{Class("h-full")},
Head: Group{
Link(Rel("stylesheet"), Href("/static/app.css")),
Script(Src("/static/app.js"), Defer()),
},
Body: Group{Class("min-h-full bg-gray-50"),
navbar(),
Main(Group(body)),
footer(),
},
})
}Classes is a map[string]bool that renders as one class attribute holding the keys whose value is true, sorted. Use it for conditional classes instead of building the string yourself. It works anywhere Class does.
Button(Classes{"btn": true, "btn-primary": primary, "opacity-50": disabled}, Text(label))JoinAttrs merges every attribute with the given name among the direct children into one attribute. It looks through groups at any depth, but not into child elements. This lets blocks build on each other, each adding its own classes, while the caller adds more:
func button(children ...Node) Node {
return Button(JoinAttrs("class", Group(children), Class("btn")))
}
func primaryButton(children ...Node) Node {
return button(Class("btn-primary"), Group(children))
}
primaryButton(Class("mt-4"), Text("Save"))
// <button class="btn-primary mt-4 btn">Save</button>http packageAdapt turns a handler that returns (Node, error) (the Handler type) into a regular http.HandlerFunc:
nil, err is common and sends only the status.StatusCode() int method, that status is sent; any other error sends 500.nil node writes nothing, so also return it after writing the response yourself, such as a redirect.type notFoundError struct{}
func (notFoundError) Error() string { return "not found" }
func (notFoundError) StatusCode() int { return http.StatusNotFound }
mux.Handle("GET /users/{id}", ghttp.Adapt(func(w http.ResponseWriter, r *http.Request) (Node, error) {
u, err := users.Get(r.Context(), r.PathValue("id"))
if errors.Is(err, ErrNotFound) {
return html.NotFoundPage(), notFoundError{}
}
if err != nil {
return html.ErrorPage(), err
}
return html.UserPage(u), nil
}))Test exported pages and components: one TestComponent function per exported component, with subtests for one happy path, the error cases, and the edge cases. Test what matters in each, for example the branch that depends on input, the value that must be escaped, or the link that appears for one kind of user and not another. An expected string for a whole page restates the component and breaks on every unrelated change, so reserve exact-string comparison for small blocks and fragments, even when converting existing HTML. Programming errors in a component, such as Attr given two values, panic when the component is built, so rendering each component in a test catches them. gomponents has no public test helpers, so render to a strings.Builder through a small helper and check with strings.Contains:
func TestNavbar(t *testing.T) {
t.Run("links to the profile only when authenticated", func(t *testing.T) {
if got := render(t, Navbar(false, "/")); strings.Contains(got, `href="/profile"`) {
t.Fatalf("unauthenticated navbar has a profile link: %s", got)
}
if got := render(t, Navbar(true, "/")); !strings.Contains(got, `<a href="/profile"`) {
t.Fatalf("authenticated navbar has no profile link: %s", got)
}
})
}
func render(t *testing.T, n Node) string {
t.Helper()
var b strings.Builder
if err := n.Render(&b); err != nil {
t.Fatal(err)
}
return b.String()
}Test handlers through Adapt with httptest.NewRecorder, and assert on the status code and a few body markers.
To test unexported blocks, use an internal test package, or re-export them for an external _test package in an export_internal_test.go file with var Card = card, according to project preference. That re-export collides with dot-imported element names just as an exported function would, so it works for card but not for button.
For linter setup, see references/linting.md.
go doc maragu.dev/gomponents, and likewise for /html, /components, and /http.© maragudk, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file (references) in skills/gomponents of maragudk/gomponents.
Open the folder on GitHubat commit 90943aa
Go HTML Views with Gomponents next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Go HTML Views with Gomponents this skillmaragudk/gomponents | 1.9k | — | ~3.6k | Automated safety check: Pass | MIT | |
| Arandu View Templatesarandu-io/arandu | 281 | — | ~1.5k | Automated safety check: Pass | MIT | |
| AO Desktop App LauncherOrchestratorInc/agent-orchestrator | 13k | — | ~2.4k | Automated safety check: Pass | Apache-2.0 | |
| Monstermq Dashboard Developervogler75/monster-mq | 143 | — | ~1.8k | Automated safety check: Pass | GPL-3.0 | |
| Frontend UIfossasia/eventyay | 1.7k | — | ~299 | Automated safety check: Pass | Apache-2.0 | |
| Abo Auditjeeftor/audiobook-organizer | 189 | — | ~322 | Automated safety check: Pass | MIT |
arandu-io/arandu
Writes and changes pages, layouts, forms and HTMX fragments in an Arandu Go app using its .kyse.go templates, escaping rules and Content-Security-Policy limits.
OrchestratorInc/agent-orchestrator
Launches, restarts and troubleshoots the real AO Electron desktop app from a checkout, with isolated or real local data and checks for stale processes.
vogler75/monster-mq
Guide for developing the MonsterMQ web dashboard. An agent skill from vogler75/monster-mq.
fossasia/eventyay
Frontend UI implementation, accessibility, and responsive checks for Django templates and Vue components
jeeftor/audiobook-organizer
Audit Audiobook Organizer Go and current web UI dependencies for vulnerabilities, outdated packages, and dependency hygiene without changing files unless explicitly asked.
ningbainb/deepseek-harness-desktop
Build a new skin for the dsh-web-ui skin collection (DSH Web GUI) and publish it into the skin-center plugin — scaffold with scripts/dsh-skin-new, author skin.json plus the apply/dispose +…
Works with
Categories
Builds and edits HTML views in Go with gomponents, where components are plain functions that return a Node, instead of using template languages. The skill makes gomponents the required way to write any HTML or UI in a Go application.Writer as HTML5, with no template language, code generation or dependencies.
Go HTML Views with Gomponents fits situations like: writing a new page, layout or component in a Go web app; converting an HTML template into gomponents code; rendering a list or table from a slice of data; reviewing gomponents code for convention and escaping mistakes.
Run `npx skills add maragudk/gomponents --skill gomponents -a claude-code`. Or copy the skill folder (skills/gomponents in maragudk/gomponents) into .claude/skills/gomponents in your project. Claude Code loads it when a task matches its description.
Run `npx skills add maragudk/gomponents --skill gomponents -a codex`. Or copy the skill folder (skills/gomponents in maragudk/gomponents) into .agents/skills/gomponents in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add maragudk/gomponents --skill gomponents -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/gomponents, .gemini/skills/gomponents, .github/skills/gomponents and .opencode/skills/gomponents in your project.
Going by SKILL.md and its folder, Go HTML Views with Gomponents needs the command-line tools its instructions call (go). Our summary lists: Go, with the maragu.dev/gomponents module.
SKILL.md names 1 domain. As links in the text: github.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Go HTML Views with Gomponents is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 3.6k tokens (SKILL.md is roughly 14k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 91 tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Go HTML Views with Gomponents: Arandu View Templates (arandu-io/arandu, 281 stars), AO Desktop App Launcher (OrchestratorInc/agent-orchestrator, 13k stars), Monstermq Dashboard Developer (vogler75/monster-mq, 143 stars) and Frontend UI (fossasia/eventyay, 1.7k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
maragudk (a GitHub organization) maintains it in maragudk/gomponents, which has 1,890 GitHub stars. The repository was last updated on October 6, 2026.
Source: maragudk/gomponents on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.