Skip to main content
All articles
Migration February 10, 2026 8 min read

Migrating from shadcn/ui to Ninna UI: A Practical Guide

A step-by-step walkthrough of moving a real React app off copy-pasted shadcn/ui components onto versioned Ninna UI packages — without losing Radix accessibility.

By Ninna UI Team


shadcn/ui popularised an idea that genuinely changed front-end development: instead of installing a component library, you copy its source into your repo and own it. It's a great model — until you've copied 40 components, customised half of them, and every `npx shadcn add` turns into a merge conflict. This guide walks through moving a real app to Ninna UI, which keeps the same Radix + Tailwind foundation but ships as versioned npm packages.

You don't have to migrate everything at once. Ninna UI and shadcn/ui components are both just React + Tailwind, so they coexist happily while you migrate page by page.

1. Install the packages you need

Where shadcn/ui drops files into components/ui, Ninna UI gives you scoped packages. Install the core theme plus whichever component packages you use:

npm install @ninna-ui/core @ninna-ui/primitives @ninna-ui/overlays @ninna-ui/forms

2. Replace one CSS import for all theming

Delete your hand-managed HSL variables and components.json. Ninna UI theming is a single CSS import with oklch perceptual colors:

@import "tailwindcss";
@import "@ninna-ui/core/theme/presets/default.css";

Dark mode now works through CSS — there's no color-mode script or provider to wire up.

3. Swap imports component by component

Most components map one-to-one. Replace the copy-pasted import path with the package import:

// Before (shadcn/ui)
import { Button } from "@/components/ui/button";

// After (Ninna UI)
import { Button } from "@ninna-ui/primitives";

Overlays gotcha: Modal, Drawer, Popover, Tooltip, and DropdownMenu live in @ninna-ui/overlays — not @ninna-ui/primitives. This trips up both humans and AI agents.

4. Drop the Radix peer dependencies

shadcn/ui has you install @radix-ui/* packages directly. Ninna UI wraps Radix internally through @ninna-ui/react-internal, so you can remove those peer deps — their types never leak into your code.

5. Customise through data-slot CSS, not source edits

This is the biggest mental shift. You no longer own the source, so you customise through CSS. Ninna UI exposes 98 data-slot targets plus the cn() helper:

<Button className="data-[slot=button]:rounded-full">Rounded</Button>

What you gain

  • Bug fixes and a11y patches arrive via npm update — no manual re-copy or merge conflicts.
  • A cleaner dependency tree with no Radix peer deps to manage.
  • oklch perceptual colors instead of hand-tuned HSL variables.
  • Smaller surface area to maintain in your own repo.

What you give up

Honesty matters: you lose direct source ownership. If editing a component's internals line-by-line is core to your workflow, shadcn/ui may still suit you better. For most teams, trading source ownership for zero-maintenance updates is a clear win.


Build it with Ninna UI

Accessible React components, CSS-only theming, Tailwind v4 native.

Get Started

Keep reading