hypra

A11Y-014: clickable element that isn't a control

Error: the page doesn't build until this is fixed.

A div or span with onclick, Alpine's "@click" or an htmx request can be clicked with a mouse, but a keyboard can't reach it, and a screen reader doesn't say it can be pressed. A button can do all of that by itself. (WCAG 2.1.1)

Fails

page(title: "Shoes", description: "All our running shoes, from light racers to trail shoes.") {
  h1 "Shoes"
  div(class: "btn", onclick: "buy(42)") "Buy"
  span("@click": "open = true") "Sizes"
}
error[A11Y-014]: `div` with `onclick` isn't a button: a keyboard can't reach it
 --> page.hypra:3:3
  |
3 |   div(class: "btn", onclick: "buy(42)") "Buy"
  |   ^^^
  = help: use button(type: "button") or a link; if it has to stay, add role: "button" and tabindex: 0, and handle Enter and Space
  = docs: https://hypralang.dev/rules/a11y-014/

warning[SEC-001]: inline event handler `onclick`
 --> page.hypra:3:30
  |
3 |   div(class: "btn", onclick: "buy(42)") "Buy"
  |                              ^^^^^^^^^
  = help: put scripts in head { script("/app.js") }
  = docs: https://hypralang.dev/rules/sec-001/

error[A11Y-014]: `span` with `@click` isn't a button: a keyboard can't reach it
 --> page.hypra:4:3
  |
4 |   span("@click": "open = true") "Sizes"
  |   ^^^^
  = help: use button(type: "button") or a link; if it has to stay, add role: "button" and tabindex: 0, and handle Enter and Space
  = docs: https://hypralang.dev/rules/a11y-014/

2 errors, 1 warning

Fixed

page(title: "Shoes", description: "All our running shoes, from light racers to trail shoes.") {
  h1 "Shoes"
  action("cart.add", id: 42) "Buy"
  button(type: "button", "@click": "open = true") "Sizes"
}

A click outside an element, like "@click.outside" to close a menu, isn't a click on it, and isn't reported. Neither is a list that handles the clicks on the buttons or links inside it in one place, like ul("@click": "pick($event.target)") { li { button "S" } li { button "M" } }: the buttons are what people press. An element that has to stay what it is takes role: "button" and tabindex: 0, and its script handles the Enter and Space keys too.