The Nightly Build.
Expert Tools

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

NotificationSwitch.tsxJAVASCRIPT
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); }
Fig. 1 · The application owns the label text and visible styling; Base UI supplies checked state and the native form control.2

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.

The default stack for new custom React work, with the condition that changes each choice.1911
JobDefaultChoose something else when
Visual startshadcn/uiA styled suite's existing visual language is already the product language.
BehaviorBase UIReact Aria's collections and internationalization fit the product, or Radix already works in production.
ConstraintsTailwind theme variablesThe team has an established token compiler or styling system.
State catalogStorybookAnother workshop already captures isolated states and runs component checks.
Complex motionMotionCSS can express the transition without layout, gesture, exit, or interruption logic.
Explorationv0The 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.

Verdict

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

  1. Base UI · About Base UI
  2. Base UI · Switch
  3. Base UI · SwitchRoot implementation
  4. Base UI · Composition
  5. Adam Wathan · CSS Utility Classes and “Separation of Concerns”
  6. Base UI · Accessibility
  7. Base UI · v1.8.0 release notes
  8. Base UI issue 5659 · Focusable element inside aria-hidden Toast.Root
  9. Storybook · Documentation
  10. Storybook · Accessibility testing
  11. shadcn/ui · Base UI is now the default
  12. Tailwind CSS · Theme variables
  13. Motion · React animation documentation
  14. Adobe · React Aria
  15. Vercel · Working with Figma and custom design systems in v0
  16. Zhang et al. · Accessibility of LLM-generated user interfaces