Native elements first
artui builds on native HTML elements wherever the platform provides one, delegating keyboard behaviour, focus management, and semantics to the browser rather than reimplementing them.
The rule is simple. If the HTML spec or a browser UA stylesheet already implements the keyboard contract, use that element. Reach for ARIA only when the platform genuinely lacks a primitive.
Why it matters
Every ARIA widget you build from <div> makes you reimplement something the browser already does:
- Focus management.
<dialog>confines focus to its content automatically through the top layer. A<div role="dialog">needs a manual focus trap. - Keyboard behaviour.
<details>and<summary>respond toEnterandSpacenatively.<div role="button" tabIndex={0}>does not. - Semantics.
<dialog>setsrole="dialog"andaria-modalimplicitly in the accessibility tree when it is opened withshowModal(). You do not write those attributes; the browser inserts them. - State.
<details open>reflects the expanded state in the DOM, and screen readers read it without any JavaScript.
More code means more surface area to get wrong. Each reimplemented behaviour is another thing you have to test, maintain, and keep correct across every browser and AT combination.
What artui uses
Accordion is built on <details> and <summary>. The browser handles Enter and Space toggling, the open attribute, and the disclosure triangle. artui adds only what the native element lacks: coordinated single-expand through the name attribute, the APG-specified Arrow/Home/End navigation across headers, and a polite live-region announcement when a panel expands.
// From accordion.tsx: each item is a native <details>
<details name={groupName} className="artui-accordion-item">
<summary aria-controls={panelId} aria-expanded={isOpen}>
{summaryChildren}
</summary>
{/* Panel renders outside <summary> as a sibling */}
</details>Dialog is built on the native <dialog> element, opened with showModal(). The browser enforces the top-layer modal context, traps focus inside the dialog, and exposes Escape through the native cancel event. artui intercepts cancel to control the close sequence, manages focus restoration, and locks body scroll, none of which the native element provides on its own.
// From dialog.tsx: native <dialog> opened via showModal()
<dialog
ref={dialogRef}
aria-modal="true"
aria-labelledby={labelledBy}
>
{children}
</dialog>Note that aria-modal="true" is set explicitly in addition to using showModal(), because some screen reader and browser combinations do not infer aria-modal from the top-layer state alone.
Datepicker uses <input type="date">. The browser renders a native date picker with full keyboard support and locale-aware formatting at no cost.
When ARIA is the right answer
Not every interactive pattern has a native element. Combobox, listbox, tree, slider, and menu have no HTML equivalent. <select> is not a composable listbox, and <input type="range"> does not expose the role="slider" and aria-value* attributes that assistive technologies read. For these, artui implements the WAI-ARIA APG pattern directly, with ARIA roles, states, and keyboard behaviour as specified.
The principle is not "never use ARIA." It is "don't use ARIA to replace something the browser already provides."
Enforcement hierarchy
How artui makes accessibility violations impossible, then a compile-time error, then a visible dev overlay, in that order of preference.
Verbatim distribution
artui ships component source directly into your repository through a CLI, so accessibility guards travel with the code and type-checking happens in your own build.