Agent skill

Go HTML Views with Gomponents

by maragudk in maragudk/gomponents

Builds and edits HTML views in Go with gomponents, where components are plain functions that return a Node, instead of using template languages.

MITAuto-check passedFrontend & Design

Install Go HTML Views with Gomponents

skills CLI
$ npx skills add maragudk/gomponents --skill gomponents -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install maragudk/gomponents gomponents --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ 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-src

Use ~/.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/

Facts

Skill name
gomponents
GitHub stars
1.9k
Token cost
~3.6k tokens
SKILL.md length
1,496 words
Files
2 (incl. references)
Skills in repo
1
Repo updated
First seen
Licence
MIT

At a glance

Builds and edits HTML views in Go with gomponents, where components are plain functions that return a Node, instead of using template languages.

  • Writing a new page, layout or component in a Go web app
  • SKILL.md covers Mental model, Common patterns, Imports and package layout and The html package: elements and…, plus 4 more sections
  • Calls go
  • Converting an HTML template into gomponents code

What it does

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.

When your agent uses it

  • 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

Example prompts

  • “Build a navbar component in gomponents with links to Home, Pricing and Docs.”
  • “Convert this HTML signup form into gomponents.”
  • “Render the invoices slice as a table using Map.”

Requirements

  • Go, with the maragu.dev/gomponents module

What it can do on your machine

Read from SKILL.md and the folder at commit 90943aa. It shows what the files ask for, not the result of running them.

  • Tool permissions

    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.

  • Runs code

    Shell commands in SKILL.md call:

    • go

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • github.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

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.

Always · name and description, kept in context so the agent knows when to use it
~200
When it runs · the whole SKILL.md, loaded when a task matches
~3.6k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~3.7k

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.

Safety

Auto-check passed

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.

SKILL.md

The full file from maragudk/gomponents at commit 90943aa, republished under its MIT licence (© maragudk). 1,496 words, ~3,595 tokens.

Download SKILL.mdSave it as .claude/skills/gomponents/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.
name
gomponents
description
Building, rendering, or editing any HTML or UI in a Go application means using gomponents — ALWAYS invoke this skill as your FIRST action, before any Read, Write, Edit, or Bash. This fires for any view, page, layout, component, form, table, navbar, footer, or list that renders to HTML; any function returning a `Node`; anything in the `html` package; and converting HTML or templates into Go. It is a hard requirement — the user writes all HTML through gomponents, a pure-Go component library whose conventions (dot imports, `Node` composition, `Map`/`If`/`Iff`, `Group`, HTML5 documents) ordinary Go and HTML habits get wrong. Skip only when no HTML or view code is touched (pure database, handler, or business logic; plain `.css`/`.js`/`.html` files; conceptual questions with no code).
license
MIT

gomponents

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.

sh
go get maragu.dev/gomponents@latest

Mental model

Everything is a Node:

go
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.

html
<a href="/about" class="nav-link">About</a>
go
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.

FunctionWhat 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) GroupTurns 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:

go
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>`),
	)
}

Common patterns

  • 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.
  • There is no 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.
  • Building blocks take 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.
  • Callers add classes through 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.
  • Dynamic attributes work like dynamic elements, because nil is skipped inside the tag too: If(disabled, Disabled()), Classes{...}, Value(u.Email).
  • A fragment is just a component returned without the layout; gomponents has no special notion of it. Return a Group when it has several root elements.
  • Print a node to see its HTML. Every built-in node implements fmt.Stringer, so fmt.Println(node) works when debugging.
  • <script> and <style> bodies need Raw. Text would turn && into &amp;&amp; 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.
  • Two Class calls make two attributes, and the browser keeps only the first. Combine them into one string, use Classes, or use JoinAttrs.
  • Void elements silently ignore non-attribute children. Img(Text("x")) renders <img> with no error.

Imports and package layout

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:

  • Exported pages and fragments are named for what they are and take a props struct: func LoginPage(props LoginPageProps) Node, func UserRow(u User) Node.
  • Unexported building blocks are camelCase with a lower-case first letter, named after the element they wrap: 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".

Show full SKILL.md (614 more words)Show less

The html package: elements and attributes

Element 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 nameElementAttribute
citeCiteCiteAttr
dataDataElData(name, value), renders data-<name>
formFormFormAttr
labelLabelLabelAttr
slotSlotElSlotAttr
styleStyleElStyle
titleTitleElTitle

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:

sh
go doc maragu.dev/gomponents/html | grep -i colspan

If nothing turns up, El and Attr produce the same output a dedicated helper would.

The components package

HTML5 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:

go
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.

go
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:

go
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>

The http package

Adapt turns a handler that returns (Node, error) (the Handler type) into a regular http.HandlerFunc:

  • The node is rendered even when there is an error. For pages, return an error page along with the error, as for a 403, 404, or 500. For fragments, nil, err is common and sends only the status.
  • If the error has a StatusCode() int method, that status is sent; any other error sends 500.
  • A nil node writes nothing, so also return it after writing the response yourself, such as a redirect.
go
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
}))

Testing components

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:

go
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.

Further reading

© maragudk, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 1 other file (references) in skills/gomponents of maragudk/gomponents.

  • SKILL.md
  • references/linting.md

Open the folder on GitHubat commit 90943aa

Compare with similar skills

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.

Go HTML Views with Gomponents compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Go HTML Views with Gomponents this skillmaragudk/gomponents1.9k—~3.6kAutomated safety check: PassMIT
Arandu View Templatesarandu-io/arandu281—~1.5kAutomated safety check: PassMIT
AO Desktop App LauncherOrchestratorInc/agent-orchestrator13k—~2.4kAutomated safety check: PassApache-2.0
Monstermq Dashboard Developervogler75/monster-mq143—~1.8kAutomated safety check: PassGPL-3.0
Frontend UIfossasia/eventyay1.7k—~299Automated safety check: PassApache-2.0
Abo Auditjeeftor/audiobook-organizer189—~322Automated safety check: PassMIT

Similar skills

  • 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.

    281 GitHub stars~1.5k tokensUpdated 4 days ago
    Frontend & DesignAuto-check passed
  • AO Desktop App Launcher

    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.

    13k GitHub stars~2.4k tokensUpdated today
    DevelopmentAuto-check passed
  • Guide for developing the MonsterMQ web dashboard. An agent skill from vogler75/monster-mq.

    143 GitHub stars~1.8k tokensUpdated yesterday
    Frontend & DesignAuto-check passed
  • Frontend UI

    fossasia/eventyay

    Frontend UI implementation, accessibility, and responsive checks for Django templates and Vue components

    1.7k GitHub stars~299 tokensUpdated today
    Frontend & DesignAuto-check passed
  • Abo Audit

    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.

    189 GitHub stars~322 tokensUpdated 27 days ago
    Media & CreativeAuto-check passed
  • Skin Developer

    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 +…

    781 GitHub stars~1.4k tokensUpdated yesterday
    Frontend & DesignAuto-check passed

Works with

Questions about Go HTML Views with Gomponents

What does Go HTML Views with Gomponents do?

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.

When should I use Go HTML Views with Gomponents?

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.

How do I install Go HTML Views with Gomponents in Claude Code?

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.

How do I install Go HTML Views with Gomponents in Codex?

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.

Can I use Go HTML Views with Gomponents in Cursor, Gemini CLI or GitHub Copilot?

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.

What does Go HTML Views with Gomponents need to run?

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.

Does Go HTML Views with Gomponents access the network?

SKILL.md names 1 domain. As links in the text: github.com. This is read from the text; nothing was executed.

Is Go HTML Views with Gomponents safe to install?

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.

What licence does Go HTML Views with Gomponents use?

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.

How many tokens does Go HTML Views with Gomponents use?

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.

What are the alternatives to Go HTML Views with Gomponents?

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.

Who maintains Go HTML Views with Gomponents?

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.