Skip to content

Conversation

@tjementum
Copy link
Member

Summary & Motivation

Replace the React Aria Components-based UI library with ShadCN 2.0 built on BaseUI primitives.

Why ShadCN:

  • Community and adoption: ShadCN has become the dominant component library for new React projects, with a significantly larger community than React Aria Components
  • BaseUI is the future: ShadCN 2.0 replaces Radix UI with BaseUI (@base-ui/react) as its headless primitive layer, making this the right time to adopt the new foundation
  • Rich ecosystem: Beyond basic form controls, ShadCN offers a large catalog of ready-to-use components and blocks -- charts (area, bar, line, pie, radar, radial), dashboards, sidebars, navigation menus, drawers, sheets, carousels, resizable panels, command palettes, data tables with sorting/filtering, and pre-built page blocks for login, signup, OTP verification, calendars with time pickers, and more
  • On distribution for AI: ShadCN is heavily represented in AI training data, making it "on distribution" for models like Claude and GPT -- similar to how Python is on distribution while COBOL is not. AI tools like Claude Code produce significantly better UI code when targeting ShadCN patterns, generating correct and visually polished components on the first attempt

This migration also delivers better accessibility through Apple HIG-compliant 44px touch targets, a macOS-inspired dark mode theme using OKLCH color space, and consistent focus ring styling across all interactive elements.

  • Migrate 30+ components (Button, Input, Select, Dialog, Menu, Table, Calendar, etc.) from React Aria to ShadCN 2.0 with BaseUI headless primitives
  • Replace tailwind-variants with class-variance-authority (cva) and remove react-aria-components and tailwindcss-react-aria-components dependencies
  • Replace custom Toast system with Sonner and custom ThemeMode provider with next-themes, adding a dark mode flash-prevention script in index.html
  • Replace custom OneTimeCodeInput with input-otp library and custom Calendar/DatePicker with react-day-picker
  • Introduce CSS variable-based control heights (--control-height: 44px, --control-height-sm: 36px, --control-height-xs: 28px) for Apple HIG touch target compliance
  • Update color theme to OKLCH color space with a neutral dark mode (achromatic grays) inspired by macOS system UI
  • Standardize focus rings using outline-ring focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 instead of ShadCN's default ring utilities
  • Remove ReactAriaRouterProvider and simplify the root component tree
  • Add @rsbuild/plugin-source-build with proper source field exports for shared-webapp monorepo packages
  • Delete 45 unused React Aria components (Accordion, ComboBox, GridList, Meter, MultiSelect, Slider, Switch, Tabs, etc.)
  • Update E2E tests for the new component selectors and stabilize Firefox tests using dispatchEvent for dropdown interactions
  • Customize stock ShadCN components: Dialog with DialogBody for mobile full-screen scrolling, Select with below-trigger positioning and larger touch targets, DropdownMenu with auto width, Button destructive variant with solid background for WCAG contrast, AlertDialog with vertically centered icons
  • Standardize heading elements with global Tailwind styles and remove the custom Heading/Text components
  • Standardize icon sizing to size-N utility and replace deprecated social media icons
  • Fix TenantSelector logo animation during sidebar collapse, support dialog mobile menu integration, and keyboard navigation order (skip link priority)

Downstream projects

This is a breaking change. The entire UI component library has been replaced, affecting every component import and prop API across self-contained systems. This is a significant migration effort.

Recommended migration approach:

  1. Create a new branch in the downstream project
  2. Cherry-pick each commit from this branch one by one, starting from the first
  3. The first few commits are meta/configuration changes (ShadCN setup, rule updates, dependency changes) -- apply these first
  4. After that, each commit migrates a single component (Button, Input, Label, etc.). For each component commit:
    • Cherry-pick the commit
    • Fix all compile errors in the downstream self-contained system caused by the new component API
    • Update any E2E tests that reference the migrated component
    • Build, run all tests, and run all E2E tests -- everything must pass before moving to the next component
  5. Continue one component at a time until all ~28 component migration commits are applied
  6. The later commits (after "Remove remaining React Aria Component dependencies") are visual polish, accessibility improvements, and customizations -- these can be applied more freely

The recommended approach is to use Claude Code with the /ralph-loop command for each component migration step. The loop will iteratively fix compile errors, update tests, and verify E2E tests pass until the system is fully working with the new component.

Key breaking changes (listed in commit order):

Each component migration commit changes the shared component API. For each one, fix all compile errors and E2E test failures in the downstream self-contained system before moving to the next commit.

  1. "Migrate Button component from React Aria to ShadCN 2.0" (commit 4): onPress becomes onClick throughout downstream code.

  2. Each component commit (commits 4-29): As each component is migrated, its React Aria props change to standard HTML/ShadCN equivalents -- isDisabled becomes disabled, isRequired becomes required, etc. Fix usages of that specific component in the downstream self-contained system before moving to the next commit.

  3. "Migrate Dialog and Modal components from React Aria to ShadCN 2.0" (commit 13): Replace DirtyModal with DirtyDialog (from @repo/ui/components/DirtyDialog). Dialog now uses <DialogBody> between header and footer, and <DialogClose render={<Button type="reset" />}> for cancel buttons.

  4. "Migrate Toast component from React Aria to Sonner and replace custom Theme with next-themes" (commit 19): This commit requires three changes:

    Update your-self-contained-system/WebApp/bootstrap.tsx:

    -import { GlobalToastRegion } from "@repo/ui/components/Toast";
    +import { Toaster } from "@repo/ui/components/Sonner";
    -        <GlobalToastRegion />
    +        <Toaster position="top-right" closeButton={true} style={{ zIndex: 60 }} />

    Replace useToast / toast.add(...) with Sonner's toast.success(...) / toast.error(...) throughout the self-contained system.

    Add dark mode flash-prevention script to your-self-contained-system/WebApp/public/index.html after the existing nonce script:

    <script nonce="{{cspNonce}}">
      (()=> {var t=localStorage.getItem('theme'),d=window.matchMedia('(prefers-color-scheme:dark)').matches;if(t==='dark'||(!t&&d)) { document.documentElement.classList.add('dark') }})();
    </script>
  5. "Migrate Heading and Text components from React Aria to native HTML elements" (commit 24): Replace <Heading> and <Text> with native <h1>-<h4> and <p> elements.

  6. "Remove remaining React Aria Component dependencies and complete ShadCN 2.0 migration" (commit 31): Update your-self-contained-system/WebApp/routes/__root.tsx -- remove the <ReactAriaRouterProvider> wrapper from the component tree (keep the children as-is):

    -import { ReactAriaRouterProvider } from "@repo/infrastructure/router/ReactAriaRouterProvider";
  7. "Remove custom Image component in favor of native img with localized alt text" (commit 43): Replace <Image> with native <img> with localized alt text.

  8. "Enable hot reload for shared-webapp monorepo packages with source build plugin and proper import conventions" (commit 47): Update your-self-contained-system/WebApp/rsbuild.config.ts to add the source build plugin:

    +import { pluginSourceBuild } from "@rsbuild/plugin-source-build";

    Add to rspack config:

         watchOptions: {
    -        ignored: ["**/tests/**", "**/playwright-report/**"]
    +        ignored: ["**/tests/**", "**/playwright-report/**"],
    +        followSymlinks: true
    +      },
    +      snapshot: {
    +        managedPaths: []
         }

    Add the plugin:

    +    pluginSourceBuild({
    +      sourceField: "source"
    +    }),
  9. Custom components: Any custom components built on React Aria primitives need to be rebuilt using ShadCN 2.0 patterns. This includes replacing react-aria-components imports with BaseUI/ShadCN equivalents, replacing tailwind-variants (tv()) with class-variance-authority (cva()), and replacing focusRing utility with outline-ring focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2. Look at the migrated components in shared-webapp/ui/components as reference for the new patterns.

Look at the changes in Back Office as a reference for what needs to change in downstream self-contained systems.

Checklist

  • I have added tests, or done manual regression tests
  • I have updated the documentation, if necessary

@tjementum tjementum self-assigned this Jan 23, 2026
@tjementum tjementum requested a review from a team as a code owner January 23, 2026 17:48
@tjementum tjementum added the Enhancement New feature or request label Jan 23, 2026
…e dropdown below the trigger instead of overlaying
@tjementum tjementum force-pushed the migrate-to-shadcn-2.0-and-base-ui branch 2 times, most recently from 3a34953 to f025236 Compare January 23, 2026 21:38
@tjementum tjementum force-pushed the migrate-to-shadcn-2.0-and-base-ui branch from f025236 to 57d739a Compare January 23, 2026 22:07
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants