Base UI keeps custom React components changeable
Use Base UI for interaction, own the visual source, and test the assembled component where automation stops.
Base UI separates behavior from appearance
A React team choosing a component library is making three decisions at once. The skin is the color, type, spacing, and motion. The skeleton is the DOM, parts, and composition. The behavior is everything a switch, menu, dialog, or combobox must do with a keyboard, pointer, form, focus order, and accessibility tree. “Custom” means being able to change the first two without quietly reimplementing the third.
Base UI is the strongest default for that job in a new, highly custom React interface. It ships no CSS, works with any styling system, exposes the rendered parts, and permits parts to be added, removed, or wrapped. It supports React 17 and newer and targets browsers classed as Baseline Widely available.1 Those properties leave the product team in charge of the visual grammar while the dependency retains the interaction machinery.
This boundary is more useful than a ranking by screenshots. A styled suite owns a visual language and gives the fastest route to that language. A source-distributed catalog such as shadcn/ui gives the application a polished set of files it can edit. Base UI owns difficult behavior without requiring either visual system. Storybook and tokens answer a later question: whether fifty independently edited components still behave and look like one product.
Base UI preserves switch behavior under new styling
Base UI documents a switch as a root and thumb, with an associated field label and a hidden checkbox for form participation. Checked state appears as a data attribute, so the application can replace the visual treatment without copying the state machine.2
import { Field } from '@base-ui/react/field';
import { Switch } from '@base-ui/react/switch';
export function NotificationSwitch() {
return (
<Field.Root className="setting">
<Field.Label>Release alerts</Field.Label>
<Switch.Root className="switch">
<Switch.Thumb className="switchThumb" />
</Switch.Root>
</Field.Root>
);
}
/* Product-owned visual grammar */
.switch { background: var(--surface-muted); }
.switch[data-checked] { background: var(--accent); }
.switchThumb { transform: translateX(0); }
.switch[data-checked] .switchThumb { transform: translateX(1.25rem); }
The implementation below that API does substantially more. SwitchRoot uses shared button
behavior, sets the switch role and checked state, carries field relationships, and delegates changes to the
hidden checkbox.3
Base UI's render prop can replace an element or component, and a render function can change markup
from internal state. A replacement component must forward its ref and spread the incoming props.4
Drop either, and the markup may still look right while its focus or event behavior is gone.
That is the practical test for an abstraction. Adam Wathan, the creator of Tailwind CSS, put the pressure on scope: “The more a component does, or the more specific a component is, the harder it is to reuse.”5 Base UI keeps the dependency's responsibility narrow enough that a product can build a very specific visual component on top.
Base UI still needs product-level accessibility tests
An accessible primitive does not produce an accessible finished component by itself. Base UI says it handles ARIA roles and attributes, pointer and keyboard behavior, and focus management. It assigns visible focus treatment, contrast, and required names or labels to the implementer.6 A visually inventive switch that hides its focus indicator is still defective. A composed trigger that discards Base UI's props may be worse.
Base UI 1.8.0 shipped on September 4, 2026 with fixes involving label association, roving focus, hover and focus behavior, refs, props, animation, and trigger performance.7 The same concentration of fixes says that upgrades should run against real component states.
A September 10 issue gives the qualification teeth. The reporter found that a high-priority toast in 1.8.0
could be keyboard-focusable while marked aria-hidden, which axe reported as
aria-hidden-focus. The message was still announced through sibling live regions, and the issue
was awaiting maintainer review when checked.8
One unconfirmed report does not establish a library-wide failure. It does establish the weakness of the
phrase “accessible by default.”
Storybook is the right place to turn that responsibility into routine work. A story records one isolated state, including the states that a happy-path page makes inconvenient to reach.9 Build stories for disabled, invalid, focus-visible, long-copy, narrow-screen, right-to-left, and reduced-motion variants. Its accessibility addon can run axe-core in the story and fail CI, but Storybook's own results include an “Incomplete” class for checks that require manual confirmation.10 Keyboard-path and assistive-technology checks remain part of shipping the assembled component.
Base UI fits inside a layered component stack
Each dependency in a custom component stack should own one kind of change. A visual starter supplies editable source for the first screen. A primitive library carries the behavior that is easiest to get subtly wrong. Tokens keep product-owned CSS from becoming a pile of nearby values. The workshop holds states still long enough to review and test them.
| Job | Default | Choose something else when |
|---|---|---|
| Visual start | shadcn/ui | A styled suite's existing visual language is already the product language. |
| Behavior | Base UI | React Aria's collections and internationalization fit the product, or Radix already works in production. |
| Constraints | Tailwind theme variables | The team has an established token compiler or styling system. |
| State catalog | Storybook | Another workshop already captures isolated states and runs component checks. |
| Complex motion | Motion | CSS can express the transition without layout, gesture, exit, or interruption logic. |
| Exploration | v0 | The team cannot supply components, tokens, and examples that constrain the output. |
shadcn/ui's July 2026 decision clarifies both the start and the bill. New projects now use Base UI by default, while Radix remains supported. Because shadcn distributes source, customized components and behavior differences make automatic migration unsafe. Its creator's advice is blunt: “the worst thing you can do for your production app is switch component libraries”.11 Source ownership gives a team editability, plus responsibility for reviewing upstream changes. It does not justify moving a working Radix system.
Tailwind's theme variables can generate utilities and ordinary CSS variables from shared design tokens.12 They constrain the skin but supply no focus, keyboard, form, or screen-reader behavior. Motion covers layout animation, gestures, scrolling, springs, and interruptible keyframes, using browser animation APIs where possible and JavaScript where required.13 Bring that runtime in for behavior CSS cannot express cleanly. Leave simple hover, focus, and opacity changes in CSS.
React Aria wins on collections and internationalization
React Aria is the better default when the product is built around rich collections, dates, localization, or unusual assistive-technology interactions. Adobe documents more than 50 style-free components, plus lower-level hooks, drag and drop, keyboard multi-selection, validation, table resizing, more than 30 languages, 13 calendar systems, five numbering systems, and right-to-left support.14 That breadth matters more than Base UI's smaller conceptual surface in a global, data-dense application.
AI generation changes the exploration loop, not this selection rule. Vercel's own guidance for v0 recommends breaking a Figma design into smaller components, iterating on each, and supplying a design system; v0 uses shadcn/ui by default and accepts custom Tailwind configuration and public packages including React Aria.15 A comparative preprint on GPT-4o and Qwen-generated interfaces found persistent difficulty with complex ARIA and dynamic interaction. An accessibility-feedback loop improved results more than zero-shot, few-shot, or self-criticism prompting.16 Generated components belong in the same Storybook, keyboard, axe, and assistive-technology loop as code written by hand.
For a new React product with a distinctive visual system, start from shadcn/ui source when it saves time, put difficult interaction on Base UI, express design constraints as tokens, and make Storybook states the unit of review. Choose React Aria when its collection and internationalization surface matches the product. Keep Radix where it already works. Add Motion for transitions CSS cannot carry, and use v0 only when it can inherit the system's components and tokens.11131415
Sources
- Base UI · About Base UI
- Base UI · Switch
- Base UI · SwitchRoot implementation
- Base UI · Composition
- Adam Wathan · CSS Utility Classes and “Separation of Concerns”
- Base UI · Accessibility
- Base UI · v1.8.0 release notes
- Base UI issue 5659 · Focusable element inside aria-hidden Toast.Root
- Storybook · Documentation
- Storybook · Accessibility testing
- shadcn/ui · Base UI is now the default
- Tailwind CSS · Theme variables
- Motion · React animation documentation
- Adobe · React Aria
- Vercel · Working with Figma and custom design systems in v0
- Zhang et al. · Accessibility of LLM-generated user interfaces