08 / Components
One page each, and the arguments that span them
Forty-two components, each with its own page: the component rendered from the package, then its spec in full. This page is the map, and the few arguments that are about more than one of them.
Three levels, and what each one owes the reader
Height is not decoration here: it says what a reader has to do to get rid of something. The base level is the page. The floating level — Popover, Menu, Tooltip — sits on one shadow and dismisses on a click outside. What separates those three is what each may contain and where the focus indicator has to be drawn.
The overlay level dims the page, traps focus and dismisses on Escape. That combination is the definition: anything that does not need all three belongs on the floating level instead. Dialog is that level, and a sheet is the same component anchored to an edge — for content that is long rather than urgent. AlertDialog goes one further: it announces on open and lands focus on Cancel, because a decision that size should not be one Enter away.
Composite widgets cost one Tab
Most components are a single control. The composite patterns own a region of the keyboard instead — Toolbar, Menubar, Tree, DataGrid, Treegrid, Listbox and Combobox. Their shared rule is that the whole widget costs one Tab and arrow keys move inside it. A row of buttons in a plain div is not a toolbar, however much it looks like one.
The builder surface is composed, not invented
The product’s main screen adds nothing to the system: a sections panel, a canvas and an inspector, built from buttons, fields, choice controls and rows, on the Sidebar layout primitive. The section rows reuse the menu item’s geometry, the inspector is Field and Choice at the small control height, and the canvas blocks are the list row with a border.
The shell is a query container, so the inspector leaves at the shell’s own width rather than the window’s — which is what lets the same builder sit in a full page, a preview pane and a docs example without three sets of rules. If a screen this central needs something new, the something belongs in the system first. That is the test the whole library was built toward.
Where this leaves off
Every component here is built, audited against 448 APG requirements, and covered by the browser suite and 299 assertions ported from W3C’s own tests. Each page below carries the component rendered from the package and its spec in full — the same eleven sections, generated from the same file the package ships.
What no test supplies: nobody has yet listened to any of this with a screen reader. Every spec says so in its own Accessibility section rather than claiming otherwise, and several of the fixes already made are specifically about what gets announced.
Every component, and where to find it
core
data
- DataTableStatic table and data grid are two different products, and keeping them apart is why this one has no keyboard model at all.
- MeterBuilt as a div with role="meter" rather than a native <meter>, because the native element cannot be styled to the contract across the browser floor.
- RowsPlural on purpose. Rows is a container of row parts, the same way options holds option.
disclosure
- AccordionRadix is permitted here — Accordion is on the closed list of six.
- CollapsibleThis is one of two components that look like they need a binding and do not — the other is RadioGroup. A component moves up a tier by taking a native element, never by adding script to the core.
- TabsRadix is permitted here — Tabs is on the closed list of six.
feedback
- AlertThe close is its own part, and its own decision. data-part="close" is a button with one job, appearing only when onDismiss is passed; action is the slot that may hold an undo. Two controls, two names, and either can be left off.
- ProgressSharing Meter's anatomy was deliberate: two bars that look different for no reason is the drift a shared stylesheet prevents.
- SkeletonIt belongs to the loading pattern more than to itself, which is why the guidance that matters is on the Components page under Loading rather than here.
- SpinnerThe delay is CSS rather than a timer in the binding, so a consumer without the React binding gets it too.
- ToastRadix Toast was not used. The six patterns Radix is permitted for are a closed list, and a toast needs no focus scope, no collision detection and no portal.
forms
- Buttonmax() for the touch floor was rejected. It flattened the size scale — sm and md became the same height on a phone, which removed the reason sm exists.
- ButtonGroupOnly the joined form ships. The gapped form was already data-se-layout="cluster", which landed earlier in the same batch, so building a component for it would have been a second name for one thing.
- CalendarWeek start comes from Intl.Locale.getWeekInfo where it exists. It is not in every engine yet, so the call is guarded and the fallback is Monday. This is the one place the component asks the platform something it may not know.
- CheckboxGroupThe select-all is part of the group rather than a separate component, because its indeterminate state is a fact about the group and nothing else can compute it.
- Choice--se-choice-box, --se-choice-row and --se-switch-track were added rather than forcing the box onto --se-control-h, which would have made every checkbox 40px on the authority of a rule rather than a decision.
- Comboboxin selectionMenu, Select and Combobox are three components and one temptation. A menu fires an action; a select holds a value from a closed set; a combobox holds a value and lets you type toward it.
- CounterA text input inside the card was rejected: it turned the headline number into a form field and brought back the two-control problem Spinbutton already solved once.
- DateFieldNot <input type="date">. The native control cannot be skinned, its picker is the platform's, and its value handling varies. What it does well — a numeric keypad and a real parser — this reproduces without giving up the appearance.
- DatePickerDateField gained an adornment prop for this. It passes straight through to Field; the date field itself puts nothing there, because its message row already reports what was parsed.
- FieldThe id is required rather than generated, because a consumer who forgets it gets a visibly labelled control with no accessible name at all — the worst combination, since it looks correct.
- RadioGroupOne of two components that look like they need a binding and do not.
- SliderRadix is permitted here — Slider is on the closed list of six.
- SpinbuttonBuilding the steppers as real buttons was rejected: three tab stops for one value.
navigation
- BreadcrumbThe W3C ships only two upstream assertions for this pattern, so almost nothing external will catch a mistake here.
- LinkThe underline is on by default rather than off, which is the opposite of most systems' inline links. The reason is the one above: an inline link with no underline is identified by color alone.
- PaginationpageItems is exported from the module but not from the package barrel, which carries one name per file. It is reachable only inside the repo, where its own tests use it.
overlays
- AlertDialogThis component was flagged as possibly broken in production before the first release: it had no CSS of its own and leaned on rules that only apply to a native <dialog>, which it is not. It rendered unpositioned and undimmed with a live focus trap. Fixed, and the specimen now proves it renders.
- Dialogvariant survives the ban on that prop name, and the reasoning is in CONVENTIONS.md: the public type is modal | sheet | fullscreen, and the further value alert is set internally by AlertDialog and never passed by a caller.
- MenuThe id prefix seeds every item id from useId. A constant default meant two default-configured menus on one page emitted the same ids, and aria-activedescendant then named whichever the document happened to contain first.
- PopoverThere is no shared floating surface: floater takes --se-border and --se-radius-lg while listbox takes --se-border-strong and --se-radius-md. Four surfaces that ought to agree have already drifted, and whether they collapse into one is open in 3.5.
- TooltipEscape does not stopPropagation, so a dialog above still sees it. APG requires Escape to dismiss a tooltip even while the trigger keeps focus — a tooltip can cover the very content the reader is trying to see, and without Escape a keyboard user has no way to get rid of it short of leaving the control.
page
selection
- ComboboxMenu, Select and Combobox are three components and one temptation. A menu fires an action; a select holds a value from a closed set; a combobox holds a value and lets you type toward it.
- Listbox--se-popup-max-h bounds the height so a long list never runs off the viewport and takes its dismiss affordance with it.
structure
- CarouselIts keep-or-cut decision is on the v3 plan for week nine, alongside Feed, and it has not been made.
- DataGridThe APG draws the same line: its own layout-grids example is excluded from the port as a use of the pattern rather than the pattern.
- FeedCarousel controls had no aria-controls until the upstream port found it; the same class of defect is what aria-posinset prevents here — a structure the eye reads and the ear cannot.
- MenubarRadix is permitted here — Menubar is on the closed list of six.
- SplitterNot a Separator. If the line cannot be dragged, it has no keyboard contract and no role beyond separator, and this component's machinery is all cost.
- ToolbarForty-one of the toolbar's forty-six upstream refs test a menu button, a spinbutton and a radio group *inside* the toolbar. They are another pattern's contract, recorded as not applicable rather than ported twice.
- TreeIts items carry a branch/leaf marker. Treegrid's rows did not until the upstream port found it, which is the kind of asymmetry only a second implementation of the same idea surfaces.
- TreegridSharing data-se="grid" with DataGrid is the anatomy being shared rather than a name collision: two components, one stylesheet, and the roles stay distinct.
surfaces
- Cardmedia is new to the part vocabulary. It names the region that bleeds to a frame's edge, and nothing else in the system had one.
- EmptyStateListbox renders its own data-part="empty" — one line inside a popup. Same word, same idea, different scale; the anatomy wins on the attribute name, as it does for Rows and Menubar.
- SeparatorMenu, Toolbar and Splitter each draw their own data-part="separator". Those are internal to a component's anatomy and stay; this is the one a page reaches for.