feat(html): add aria-current helper
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-03 18:25:09 +00:00
parent a5e02f6472
commit ec18122233
4 changed files with 32 additions and 1 deletions

View file

@ -38,6 +38,7 @@ Accessibility-oriented helpers are also provided for common attribute patterns:
- `AriaDescribedBy(node, ids...)`
- `AriaLabelledBy(node, ids...)`
- `AriaControls(node, ids...)`
- `AriaCurrent(node, current)`
- `Role(node, role)`
- `Lang(node, locale)`
- `Dir(node, direction)`

View file

@ -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`), plus accessibility helpers such as `AriaLabel()`, `AriaControls()`, `AriaHidden()`, and `TabIndex()`.
**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`), plus accessibility helpers such as `AriaLabel()`, `AriaControls()`, `AriaCurrent()`, `AriaHidden()`, and `TabIndex()`.
**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`.

10
node.go
View file

@ -204,6 +204,16 @@ func AriaControls(n Node, ids ...string) Node {
return Attr(n, "aria-controls", strings.Join(ids, " "))
}
// node.go: AriaCurrent sets the aria-current attribute on an element node.
// Example: AriaCurrent(El("a"), "page").
// An empty value leaves the node unchanged so callers can opt out cleanly.
func AriaCurrent(n Node, current string) Node {
if current == "" {
return n
}
return Attr(n, "aria-current", current)
}
// node.go: Role sets the role attribute on an element node.
// Example: Role(El("aside"), "complementary").
func Role(n Node, role string) Node {

View file

@ -285,6 +285,26 @@ func TestAriaControlsHelper(t *testing.T) {
}
}
func TestAriaCurrentHelper(t *testing.T) {
ctx := NewContext()
node := AriaCurrent(El("a", Raw("Home")), "page")
got := node.Render(ctx)
want := `<a aria-current="page">Home</a>`
if got != want {
t.Errorf("AriaCurrent() = %q, want %q", got, want)
}
}
func TestAriaCurrentHelper_Empty(t *testing.T) {
ctx := NewContext()
node := AriaCurrent(El("a", Raw("Home")), "")
got := node.Render(ctx)
want := `<a>Home</a>`
if got != want {
t.Errorf("AriaCurrent(\"\") = %q, want %q", got, want)
}
}
func TestRoleHelper(t *testing.T) {
ctx := NewContext()
node := Role(El("button", Raw("menu")), "navigation")