[]previous and next page

ARIA attribute values

Attribute names are typed from the WAI-ARIA 1.2 vocabulary: an unknown aria-* name is a type error, and role accepts only real roles.

For agentsUpdated

Attribute names are typed from the WAI-ARIA 1.2 vocabulary: an unknown aria-* name is a type error, and role accepts only real roles. The runtime then writes values with these rules (from docs/contracts/dom-runtime.md):

You write aria-*, data-*, draggable, spellcheck, contenteditable any other attribute
true "true" present with an empty value
false "false" removed
null, undefined removed removed
a string or number String(value) String(value)

The first column is why aria-expanded={open()} works: false is a meaningful state ("false") and must not remove the attribute. The second column is why hidden={!open()} works: false means "not hidden", which is the absence of the attribute. Boolean attributes such as disabled, hidden, checked and open are set as DOM properties on HTML elements, which reflect to attributes.

src/disclosure.tsxtsx
import { useId, useStore } from "@shadow-js/core";

export function Disclosure() {
  const [open, setOpen] = useStore(false);
  const panel = useId();
  return (
    <div>
      <button
        type="button"
        aria-expanded={open()}
        aria-controls={panel}
        onClick={() => setOpen((value) => !value)}
      >
        Details
      </button>
      <div id={panel} role="region" aria-label="More information" hidden={!open()}>
        Everything there is to know.
      </div>
    </div>
  );
}

Prefer the visible, native state over ARIA when one exists: disabled over aria-disabled, checked over aria-checked, <details> over a hand-made disclosure. ARIA that contradicts the native state is worse than none.