Skip to main content

Component Option Audit

Last updated: 2026-09-21 (Looma 0.3)

Looma compared the configuration options of each baseline component against the equivalent components in other UI libraries. This page records the decisions. Looma 0.3 publishes the decisions; most additions ship after 0.3.

Method

  • Libraries: React Aria Components, Radix, Headless UI, Material UI, Chakra UI v3 / Ark, Vuetify 3, PrimeVue 4, Quasar 2, Web Awesome (Shoelace's successor) and Spectrum Web Components. Layout adds Every Layout and Mantine. The editor is compared with Tiptap, BlockNote, Plate, Lexical and Novel. The WAI-ARIA Authoring Practices are the behaviour reference. Only official docs and official source repositories were used.
  • Common means that most of the libraries with an equivalent component offer the option. Being common only makes an option a candidate; Looma adds it only if it has real value.
  • Looma's rules:
    • Props are HTML attributes, and every value has a text form.
    • Function-valued options become attributes, child markup, slots or events.
    • Booleans default to false.
    • Authors never have to write data-* attributes.
  • Uncommon options that Looma already had were kept only where they have real value. The others are listed under "Declined" or "Drop".

The per-family evidence, with counts and sources, is in the repository: layout, form controls, combobox, actions and display, overlays, navigation and collections and editor. Where this page and the evidence disagree, this page wins.

Changed in 0.3

  • Consumer attributes land on the native root. class, id, type, name, form, aria-pressed and other native attributes written on ui-button, ui-icon-button, ui-input, ui-textarea, ui-select and ui-dialog reach the native element. The audit's conditional type, name, value, form and pressed props are therefore not needed.
  • Tokens set on an ancestor apply. Components read their public --ui-* tokens with fallbacks instead of redeclaring them, and a repository check enforces this.
  • Structured values are attributes. Props with list, record or object types are declared with HTML Next's type system; in HTML their attribute text is JSON, parsed against the declared shape. This covers editor menu items and anchor-rect, table geometry, table menu actions (which replace the nine undeclared can-* booleans) and combobox items.
  • Combobox drops function hooks, option descriptions and option metadata. value (single) and items (multiple) are separate props.
  • Cluster always wraps and has no wrap or justify props.
  • Container replaces ui-center: a centred column with a maximum width and gutters.
  • Sidebar is rebuilt as the sidebar panel only, with the options in the Layout table below.
  • Tabs gain stretch, and the tab list scrolls when it overflows.
  • Tree Item expand no longer bubbles. It fires only on the item that was toggled. Expanding or collapsing never changes an ancestor or sibling.
  • Dialog has a header with a title and a close button, a scrolling body, and an actions footer.
  • Toast Region gains auto and duration, and pauses its timers on hover and focus.
  • Tooltip gains inverse for a dark surface.
  • Editable gains hint and actions, and keeps the same size while editing.

Bugs found

Fix these first. They are defects, not new options.

ComponentDefect
Checkbox, SwitchNo name, so they never submit with a form.
Radio GroupArrow keys work on one axis only. The APG and native radios use all four.
DialogEscape closes the dialog only when dismissible is set. Escape must always close a modal dialog.
Search ShellVerify that Escape does not depend on dismissible.
Popover, Tooltip, Menuplacement ignores left, right and centred values.
TreeReordering has no keyboard or single-pointer alternative (WCAG 2.2 SC 2.5.7).
Form FieldThe error slot is announced as a description even while the field is valid.
EditableAn empty value renders an invisible click target with no accessible text.
Editor ToolbarToggle buttons expose their state only through data-active, not aria-pressed.
Insert Table Gridheader-row is ignored, and the header checkbox always starts checked.
Slash Menu, Mention MenuThey overwrite an author's aria-label, and their header and hint text is hard-coded English.
SeparatorVerify that a slotted label is exposed to assistive technology. The separator role hides its children.
Toast Region, Affordance ScopeAuthors still write data-* markers. Toast Region's stories use data-ui-toast and data-ui-toast-dismiss. Affordance Scope's controller finds participants other than Looma controls through [data-ui-affordance]. Replace both with declared attributes or elements.

Layout

ComponentAdoptDeclined
ui-stacknoneDirection switch (that is a Cluster or Switcher), divider prop (write <ui-separator>), split-after (author CSS)
ui-clusternonejustify: spreading items apart is not a cluster, and a justified row would be a separate flex primitive. Non-wrapping mode, separate row and column gaps, grow
ui-gridcolumns: number, a maximum column count that keeps the grid intrinsicBreakpoint columns, spans and offsets (they belong to the 12-column paradigm)
ui-containerRenamed from ui-center in 0.3. It holds content in a centred column with a maximum width, such as settings, activity and legal pages; center suggested centring on both axes, and Container is what most libraries call this. Make the measure steps cover the column widths apps usefluid (just don't use the wrapper), center-content (no use case yet)
ui-switcherlimit: number, the most items allowed in the horizontal statenone
ui-sidebarThe sidebar panel only; the main content is not part of it. Props: collapsed, side, width, min-width, max-width, resizable, resize-step, resize-label and breakpoint. Below the breakpoint it becomes an off-canvas drawer. Events resize and toggle. Later: persist. The page's own layout places the sidebar next to the main contentThe current two-pane layout (side plus main), including under another name. A small item with trailing content is a Cluster. Also declined: rail or mini mode, expand on hover, swipe gestures, keyboard shortcut, snapping
ui-reelnoneArrow controls (build a carousel instead), hidden scrollbar, height

Form controls

ComponentAdoptDeclined
ui-inputsize: "sm" | "md". New ui-input-group with start and end slots for icons, units and buttonsVariant, built-in label, hint or error (Form Field owns these), clearable, counter, mask, debounce
ui-textareaautosize (field-sizing: content, with rows as the minimum)Size, variant, counter, resize prop (CSS)
ui-selectsize: "sm" | "md". With multiple, the selection is values: list(string), the selected option values (written in HTML as values='["red","blue"]'); value stays a single string. The options stay <option> childrenPlaceholder prop (use a disabled first <option>), clearable, variant
ui-checkboxname (bug), invalidSize (a token), checkbox group (use <fieldset>), readonly, label placement
ui-radio-groupinvalid. change detail becomes { value, previousValue, trigger }. Drop the duplicate select event. label defaults to "". orientation defaults to verticalSize, hint, readonly
ui-switchname (bug)Size (tokens), on/off track labels, readonly, invalid
ui-form-fieldShow and describe the error slot only while the field is invalid. Fall back to the control's native validationMessage. Add a CSS required markerKeyed per-validity messages (for now), orientation
ui-editablesave-label, cancel-label, placeholder, required and maxlength (invalid text does not commit). label defaults to ""Multiline (for now), arbitrary edit content, activation and submit modes
ui-comboboxloading with an empty slot, filter: "contains" | "none" (none for server-side filtering), open-on-focus, invalidFunction hooks, option descriptions, debounce, minimum characters, virtualization, custom match functions

Validation stays declarative everywhere. Validation functions are replaced by native constraint attributes, setCustomValidity(), the invalid prop and Form Field's error slot.

Actions and display

ComponentAdoptDeclined
ui-buttonpending: keeps focus and the label, sets aria-disabled and shows a spinnertype, name, value and form (native attributes already reach the button), a separate tone axis, icon slots. Deferred: rendering as a link
ui-icon-buttonpendingpressed (write native aria-pressed), tone
ui-badgeoutline variantSize (badges follow the font size), icon, pill, anchored count badge (a separate component if ever needed)
ui-calloutRename tone note to neutral to match BadgeIcon slot (a per-tone icon is built in), variant, size, dismissible (a candidate if banners need it), live region (write native role)
ui-avatarsize, shape: "circle" | "square", a built-in person glyph when there is no image or name, decorativeStatus event, lazy loading, background colour
ui-avatar-grouptotal (the true count when the markup is truncated), overflow-label (template text with {count}), size (together with Avatar)Spacing (a token)
ui-separatornone (see the label bug)Thickness, colour, decorative

Overlays

ComponentAdoptDeclined
ui-dialogclosedby: "any" | "closerequest" | "none" (native vocabulary) replaces dismissible. modeless replaces modal, so modal is the default. alert (role="alertdialog", no close button). size: "sm" | "md" | "lg". An open eventA fullscreen presentation, initial or return focus props (native autofocus and <dialog> already do this), description, draggable
ui-popoverThe full placement set as a typed keyword. A --ui-popover-offset tokenArrow, modal or focus trap (use Dialog), dismiss toggles
ui-tooltipThe full placement set, defaulting to top. Instant display for the next tooltip after one has showndisabled, trigger modes, arrow toggle. The evidence suggested dropping inverse; Looma keeps it for dark tooltips
ui-menu, ui-menu-item<hr> separators. ui-menu-group with a label. Typeahead and Home/End. href link items. A shortcut slot. type: "action" | "checkbox" | "radio" with checked. Later: submenusA close-on-select option, an icon slot, a destructive tone
ui-context-menuEverything Menu gains. Long-press opens it on touchdisabled, global, modal
ui-toast-regionA declarative <ui-toast> child with tone, duration and an action slot. placement on the region. Drop message and openMerging auto into duration (Looma keeps the auto switch), maximum visible, swipe, promise toasts, hotkey
ComponentAdoptDeclined
ui-disclosurename for exclusive groups (like <details name>), a summary slot, heading-level, closed content that find-in-page can revealAn accordion wrapper, lazy mounting, variants
ui-tabsactivation: "auto" | "manual". An optional ui-tab-panel child with disabled and a tab slot for rich labels. label defaults to ""Lazy panels, link tabs, closable tabs, variants
ui-tree, ui-tree-itemselection: "none" | "single" | "multiple", a select event, lazy items, keyboard reordering (bug), typeahead, a cancelable reorder. Drop hover-expand-delay. Drop drop-depth and subtree-depth if they can be derivedPropagation modes, filtering, expand-all, a separate checkbox option
ui-top-barRender a <header>Sticky and elevate-on-scroll props (documented CSS recipes instead), hide on scroll
ui-search-shellBuilt-in keyboard navigation from the input to the result rows. dismissible governs backdrop clicks onlyBuilt-in filtering, hotkey
ui-affordance-scopeReveal on focus and on coarse pointersnone

Editor

ComponentAdoptDeclined
ui-editor-toolbarDeclare floating. Roving focus between controls. aria-pressed on toggles (bug). Use ui-separator for groupsAutomatic overflow, label
ui-editor-slash-menuChild item markup with keywords, groups, filtering inside the component, an empty slot, a footer slot for key hintsplacement, loading, shortcut badges
ui-editor-mention-menuChild item markup with an avatar start slot, an empty slot, a header slotGrouping, filtering inside the component
ui-editor-insert-table-gridHonour header-row (bug). Keyboard grid navigation. A click inserts immediatelyA separate insert button (unless touch testing needs it)
ui-editor-table-toolbar, ui-editor-table-context-menuHeader row and column toggles. scope: "cell" | "row" | "column" on the context menu. Move row and move column actions. Theme tokens for cell backgrounds. The toolbar stops duplicating the context menuVertical alignment, borders, text colour, sort, duplicate, freeze, striping
ui-editor-table-overlayRow and column handles open a scoped menuDrag-to-reorder (deferred; the move actions come first), a selection overlay

Earlier audit

The March 2026 family-level audit, which decided which component families Looma owns, is in docs/component-library-audit.md.