Skip to content

Latest commit

 

History

History
176 lines (158 loc) · 20.9 KB

component-libraries.md

File metadata and controls

176 lines (158 loc) · 20.9 KB

Which components of popular libraries would be a CSS class on a built-in element if CSS could set ARIA (and focusability)? Each component links to its source; rows link to the matching use case where one exists.

Web Awesome

Web Awesome (branch next, October 2026) has 73 components. It also ships native styles for button, details, dialog, hr, checkboxes, radios, text fields, select, input[type=range], input[type=color] and progress, so for many components the styling half of the class already exists in the same package; the component exists for the other half.

Count
A class on a native element today, no ARIA needed 10
A class once CSS can set ARIA 32
A class once CSS can set ARIA and focusgroup 7
Stays a component 24

Columns: the native element the class would go on, what CSS already covers, what ARIA in CSS adds (the blocker), and what JS would remain, if any.

A class today

Nothing stands in the way; the component wraps presentation.

Component Element Notes
badge span
callout div
card article/div header, media and footer are slots → children
tag span + button the remove button is markup; the wa-remove event is the author's listener either way
button button / a native styles already cover both; aria-busy from [loading] is the one ARIA write (Loading buttons)
button-group div the JS toggles hover/focus classes on neighbours for border collapsing, which is :has() today
popup any anchor positioning covers placement, flip, shift, arrows, anchor-size(); virtual elements stay JS
animated-image img image-animation: controlled is the CSS version, with UA-assigned semantics
scroller div overflow + scroll-driven animations for the shadows; focused scroll containers already handle Home/End. The role="region" is the scrollable regions case
textarea textarea native styles exist; resize is CSS; autosize is field-sizing: content; the character count is a small output + JS

A class once CSS can set ARIA

Component Element CSS already covers ARIA in CSS adds Residue
skeleton div everything aria-busy: true (Skeleton)
spinner div everything role: progressbar; aria-label: "Loading" (Spinner)
avatar span/img shape, initials, icon role: img; aria-label: attr(label) (Avatar)
divider hr native styles, orientation, label via ::before aria-orientation from [orientation] (orientation); a labelled divider needs aria-label
icon span mask-image aria-hidden unless [label], then role: img; aria-label: attr(label) (decorative elements) fetching SVG by name stays JS; a mask-based icon set does not
checkbox input[type=checkbox] native styles aria-describedby: --hint via ref-scope (relationships); :user-invalid { aria-invalid } (form validation) setCustomValidity() is native
switch input[type=checkbox switch] native styles role: switch where the UA does not (polyfills); aria-describedby
radio input[type=radio] native styles aria-describedby
radio-group fieldset native styles aria-orientation from [orientation]; aria-describedby
checkbox-group fieldset native styles same
input input native styles, decorations via ::before/::after or siblings aria-describedby: --hint; :user-invalid; the password toggle's aria-pressed clearing and type flipping are one line of JS each
number-input input[type=number] native styles aria-describedby; :user-invalid custom stepper icons
select (single) select customizable select, ::picker() aria-describedby; :user-invalid; ::picker() roles where the picker is a grid multiple-with-tags and lazy loading stay JS
option option customizable select nothing, unless relabelled (select listboxes)
details details native styles, ::details-content transitions, icon placement ::details-content { role: region; aria-labelledby: --summary } (accordion panels)
accordion, accordion-item details[name] exclusive accordion, heading wrapper is markup same as details expand/collapse all is the author's loop either way
dialog dialog native styles, closedby for light dismiss, commandfor to open aria-labelledby: --title via ref-scope (dialog labels); dialog:modal { aria-modal: true } is UA autofocus covers initial focus
drawer dialog dialog + placement, transitions same as dialog
popover [popover] popover + anchor positioning + commandfor; the UA sets aria-expanded on the invoker nothing on the popover itself; roles when it holds a widget
tooltip ::tooltip / [popover] interest invokers + anchor positioning role: tooltip (UA for ::tooltip); the shared description list manual show/hide is showPopover()
progress-bar progress or div native styles; width: var(--value) aria-valuenow: var(--value) (values that draw the UI)
progress-ring progress or div conic-gradient from attr(value) / var(--value) same
breadcrumb, breadcrumb-item nav > ol > li > a separators via ::before, dropdown items are markup li:last-child > a { aria-current: page } (breadcrumbs)
step, stepper nav > ol > li variants, orientation, icons .active { aria-current: step } on host or button; .loading { aria-busy: true } (stepper) step-change announcements (one-shot, ariaNotify())
pagination nav > ol > li > a compact, summary via sibling-index()/sibling-count() aria-current: page; aria-disabled on boundary controls (pagination) generating the list from total/page-size is templating or the server
rating fieldset > input[type=radio] the classic CSS-only widget: :checked, :hover and :has(~ :hover) for preview, extra radios for precision aria-readonly for read-only; radios are already a correctly announced value (rating) wa-hover event
slider (single) input[type=range] native styles, datalist markers aria-valuetext: control-value() "%" (slider) the value tooltip needs control-value() in content; range (two thumbs) stays JS
carousel, carousel-item div with scroll snap ::scroll-marker, ::scroll-button(), scroll-snap, slides per page via scroll-snap-type and sizing (CSS carousels) aria-label: sibling-index() " of " sibling-count() (carousel slides); :snapped { aria-current }; aria-hidden/interactivity: inert on off-screen slides (scroll-snap) loop, autoplay and mouse dragging stay JS

A class once CSS can set ARIA and focusgroup

These need roving focus or arrow-key traversal as well, which is the other half of the program.

Component Element CSS already covers ARIA + focusgroup adds Residue
tab, tab-group, tab-panel details[name] or radio group layout, overflow scrolling (::scroll-button()) focusgroup: tablist; aria-selected from [open]/:checked; role: tabpanel; interactivity: focusable (tabs) closable tabs are a button plus the author's handler; manual activation is what details does natively
dropdown, dropdown-item details + ::details-content anchor positioning, nested details for submenus, :checked for checkable items focusgroup: menu on the panel (dropdown menus); menuitemcheckbox and aria-checked on checkbox inputs, aria-haspopup on submenu summaries (menu items) typeahead, submenu hover timing
tree, tree-item nested details indent guides, icons, [open] role: tree/treeitem; aria-level from nesting; aria-expanded from [open], none on leaves; aria-selected; focusgroup (tree view) multi-selection, indeterminate parents, lazy loading

Stays a component

Behavior that is neither presentation nor semantics.

Component Why
tag-input, otp-input, time-input, known-date composite editing: tokenisation, segment focus, parsing, locale rules
color-picker the panel: 2D picking, format conversion, eyedropper. A plain swatch is input[type=color], which native styles already cover
split-panel CSS resize has no keyboard path and resizes the element rather than a divider
comparison the range value drives a sibling's clip-path; one line of JS, no CSS path
copy-button clipboard
qr-code canvas
format-bytes, format-date, format-number, relative-time Intl, timers
include, markdown, random-content fetching and parsing
animation, intersection-observer, mutation-observer, resize-observer declarative wrappers over JS APIs
toast, toast-item one-shot announcements and timers
zoomable-frame zoom on an iframe is CSS; the controls and theme sync are JS
page a layout; not ARIA-blocked, and mostly CSS already

What this says

  • The blocker is semantics, not styling. For 39 components the styling is already CSS, and for the form controls, details, dialog, progress and hr among them, Web Awesome's own native stylesheet already styles the element the class would go on. What the component adds is role, aria-* and (for seven) focus management.
  • Form controls are the biggest group. Ten wrappers around native inputs exist mainly to attach a label and a hint, to sync aria-invalid, and to style the result; ref-scope and :user-invalid cover the first two and native styles the third.
  • Native behaviour is usually already there. details, dialog, popover, scroll snap, customizable select and radio groups supply the interaction; the components re-implement it in JS so they can own the attributes. Rating is the clearest case: the radio version is more accessible than the slider the component exposes.
  • The residue is small and one-shot. Where JS remains in the "class" tiers it is clipboard, timers, announcements, or a single line that flips an attribute, none of it the state-sync code that ARIA in CSS removes.