feat(html): add accessibility attribute helpers
All checks were successful
Security Scan / security (push) Successful in 9s
Test / test (push) Successful in 55s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-31 19:28:53 +00:00
parent 65c0dd3e27
commit 4ae93ce36f
5 changed files with 36 additions and 4 deletions

View file

@ -4,7 +4,7 @@
# go-html
HLCRF DOM compositor with grammar pipeline integration for server-side HTML generation and optional WASM client rendering. Provides a type-safe node tree (El, Text, Raw, If, Each, Switch, Entitled), a five-slot Header/Left/Content/Right/Footer layout compositor with deterministic `data-block` path IDs and ARIA roles, a responsive multi-variant wrapper, a server-side grammar pipeline (StripTags, GrammarImprint via go-i18n reversal, CompareVariants), a build-time Web Component codegen CLI, and a WASM module (2.90 MB raw, 842 KB gzip) exposing `renderToString()`.
HLCRF DOM compositor with grammar pipeline integration for server-side HTML generation and optional WASM client rendering. Provides a type-safe node tree (El, Text, Raw, If, Each, Switch, Entitled, AriaLabel, AltText), a five-slot Header/Left/Content/Right/Footer layout compositor with deterministic `data-block` path IDs and ARIA roles, a responsive multi-variant wrapper, a server-side grammar pipeline (StripTags, GrammarImprint via go-i18n reversal, CompareVariants), a build-time Web Component codegen CLI, and a WASM module (2.90 MB raw, 842 KB gzip) exposing `renderToString()`.
**Module**: `forge.lthn.ai/core/go-html`
**Licence**: EUPL-1.2

View file

@ -17,12 +17,14 @@ type Node interface {
}
```
All concrete node types are unexported structs with exported constructor functions. The public API surface consists of nine constructors plus the `Attr()` and `Render()` helpers:
All concrete node types are unexported structs with exported constructor functions. The public API surface consists of nine node constructors, two accessibility helpers, plus the `Attr()` and `Render()` helpers:
| Constructor | Behaviour |
|-------------|-----------|
| `El(tag, ...Node)` | HTML element with children. Void elements (`br`, `img`, `input`, etc.) never emit a closing tag. |
| `Attr(Node, key, value)` | Sets an attribute on an `El` node. Traverses through `If`, `Unless`, and `Entitled` wrappers. Returns the node for chaining. |
| `AriaLabel(Node, label)` | Convenience helper that sets `aria-label` on an element node. |
| `AltText(Node, text)` | Convenience helper that sets `alt` on an element node. |
| `Text(key, ...any)` | Translated text via the active context translator. Server builds fall back to global `go-i18n`; JS builds fall back to the key. Output is always HTML-escaped. |
| `Raw(content)` | Unescaped trusted content. Explicit escape hatch. |
| `If(cond, Node)` | Renders the child only when the condition function returns true. |

View file

@ -39,7 +39,7 @@ This builds a Header-Content-Footer layout with semantic HTML elements (`<header
| Path | Purpose |
|------|---------|
| `node.go` | `Node` interface and all node types: `El`, `Text`, `Raw`, `If`, `Unless`, `Each`, `EachSeq`, `Switch`, `Entitled` |
| `node.go` | `Node` interface and all node types: `El`, `Text`, `Raw`, `If`, `Unless`, `Each`, `EachSeq`, `Switch`, `Entitled`, plus `AriaLabel` and `AltText` helpers |
| `layout.go` | HLCRF compositor with semantic HTML elements and ARIA roles |
| `responsive.go` | Multi-variant breakpoint wrapper (`data-variant` containers) |
| `context.go` | Rendering context: identity, locale, entitlements, i18n service |
@ -52,7 +52,7 @@ This builds a Header-Content-Footer layout with semantic HTML elements (`<header
## Key Concepts
**Node tree** -- All renderable units implement `Node`, a single-method interface: `Render(ctx *Context) string`. The library composes nodes into trees using `El()` for elements, `Text()` for translated text, and control-flow constructors (`If`, `Unless`, `Each`, `Switch`, `Entitled`).
**Node tree** -- All renderable units implement `Node`, a single-method interface: `Render(ctx *Context) string`. The library composes nodes into trees using `El()` for elements, `Text()` for translated text, control-flow constructors (`If`, `Unless`, `Each`, `Switch`, `Entitled`), and accessibility helpers (`AriaLabel`, `AltText`).
**HLCRF Layout** -- A five-slot compositor that maps to semantic HTML: `<header>` (H), `<aside>` (L/R), `<main>` (C), `<footer>` (F). The variant string controls which slots render: `"HLCRF"` for all five, `"HCF"` for three, `"C"` for content only. Layouts nest: placing a `Layout` inside another layout's slot produces hierarchical `data-block` paths like `L-0-C-0`.

12
node.go
View file

@ -105,6 +105,18 @@ func Attr(n Node, key, value string) Node {
return n
}
// AriaLabel sets an aria-label attribute on an element node.
// Usage example: AriaLabel(El("button", Text("save")), "Save changes")
func AriaLabel(n Node, label string) Node {
return Attr(n, "aria-label", label)
}
// AltText sets an alt attribute on an element node.
// Usage example: AltText(El("img"), "Profile photo")
func AltText(n Node, text string) Node {
return Attr(n, "alt", text)
}
func (n *elNode) Render(ctx *Context) string {
if n == nil {
return ""

View file

@ -175,6 +175,24 @@ func TestElNode_AttrEscaping_Good(t *testing.T) {
}
}
func TestAriaLabel_Good(t *testing.T) {
node := AriaLabel(El("button", Raw("save")), "Save changes")
got := node.Render(NewContext())
want := `<button aria-label="Save changes">save</button>`
if got != want {
t.Errorf("AriaLabel() = %q, want %q", got, want)
}
}
func TestAltText_Good(t *testing.T) {
node := AltText(El("img"), "Profile photo")
got := node.Render(NewContext())
want := `<img alt="Profile photo">`
if got != want {
t.Errorf("AltText() = %q, want %q", got, want)
}
}
func TestElNode_MultipleAttrs_Good(t *testing.T) {
ctx := NewContext()
node := Attr(Attr(El("a", Raw("link")), "href", "/home"), "class", "nav")