The React ecosystem has been reshaped by shadcn/ui's copy-paste model. Instead of installing a component package, you pull the source into your project and own it. A whole ecosystem has grown around that idea: registries that install through the same CLI, the headless primitives that sit underneath, and blocks that wire UI straight to a backend.
The shadcn/ui Ecosystem#
The modern React landscape is defined by these trends:
What's driving the React UI library evolution
1. shadcn/ui - The Foundation#
shadcn/ui popularised the copy-paste model. The CLI writes component files into your project, and from then on they are your code. It supports Tailwind CSS v4 and React 19, and lets you pick the primitive layer: Base UI (the default for new projects since July 2026), Radix, or React Aria.
# Set up a project (the package is `shadcn`; the old `shadcn-ui` package is deprecated)
# Docs: https://ui.shadcn.com/docs/installation
npx shadcn@latest init
# Keep Radix primitives instead of the Base UI default
npx shadcn@latest init -b radix
# Add components
npx shadcn@latest add button card dialog
# Add every component
npx shadcn@latest add --allimport { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export function ExampleComponent() {
return (
<Card>
<CardHeader>
<CardTitle>shadcn/ui Example</CardTitle>
</CardHeader>
<CardContent>
<Dialog>
{/* Base UI uses `render`; Radix projects use <DialogTrigger asChild> */}
<DialogTrigger render={<Button>Open Dialog</Button>} />
<DialogContent>
<DialogHeader>
<DialogTitle>Hello</DialogTitle>
<DialogDescription>This is a shadcn/ui dialog.</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
</CardContent>
</Card>
)
}// components/ui/button.tsx - you own this file. Edit it directly, no wrappers.
import { cva } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-white hover:bg-destructive/90",
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
// Add your own variants
gradient: "bg-linear-to-r from-blue-500 to-purple-600 text-white",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 gap-1.5 px-3",
lg: "h-10 px-6",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)Perfect for: Any project that needs complete control over components and wants to own every line of code.
2. Magic UI - The Animation Specialist#
Magic UI is a natural companion to shadcn/ui: 150+ free, MIT-licensed animated components built with React, TypeScript, Tailwind CSS and Motion. Components install through the shadcn CLI under the @magicui namespace.
// npx shadcn@latest add @magicui/text-animate @magicui/shimmer-button
// TextAnimate: https://magicui.design/docs/components/text-animate
import { TextAnimate } from "@/components/ui/text-animate"
// ShimmerButton: https://magicui.design/docs/components/shimmer-button
import { ShimmerButton } from "@/components/ui/shimmer-button"
export function HeroSection() {
return (
<section className="flex min-h-screen flex-col items-center justify-center gap-8">
<TextAnimate animation="blurInUp" by="word" as="h1" className="text-center text-4xl font-bold">
Building the future of web development
</TextAnimate>
<ShimmerButton>Get Started</ShimmerButton>
</section>
)
}// TypingAnimation: https://magicui.design/docs/components/typing-animation
import { TypingAnimation } from "@/components/ui/typing-animation"
// AuroraText: https://magicui.design/docs/components/aurora-text
import { AuroraText } from "@/components/ui/aurora-text"
export function TextEffects() {
return (
<div className="space-y-8">
<h2 className="text-3xl font-bold">
Build <AuroraText>amazing</AuroraText> interfaces
</h2>
<TypingAnimation words={["Design", "Build", "Ship"]} loop />
</div>
)
}// Marquee: https://magicui.design/docs/components/marquee
import { Marquee } from "@/components/ui/marquee"
const logos = ["Acme", "Globex", "Initech", "Umbrella"]
export function LogoWall() {
return (
<Marquee pauseOnHover className="[--duration:30s]">
{logos.map((logo) => (
<span key={logo} className="mx-8 text-xl font-semibold text-muted-foreground">
{logo}
</span>
))}
</Marquee>
)
}// BlurFade: https://magicui.design/docs/components/blur-fade
import { BlurFade } from "@/components/ui/blur-fade"
// NumberTicker: https://magicui.design/docs/components/number-ticker
import { NumberTicker } from "@/components/ui/number-ticker"
export function ScrollAnimations() {
return (
<div className="space-y-8">
<BlurFade inView delay={0.2}>
<h2 className="text-2xl font-bold">Scroll to reveal me</h2>
</BlurFade>
<p className="text-4xl font-bold">
<NumberTicker value={1200} />+ deploys
</p>
</div>
)
}Perfect for: Landing pages, portfolios, and apps that need polished motion to engage users.
3. 21st.dev - The Community Registry#
21st.dev is a community registry of React and Tailwind CSS components, templates and shadcn themes published by independent design engineers. Because every author brings their own style and API, it reads more like a marketplace than a single library: browse, preview, then install the piece you want into your repo.
# Connect the 21st.dev CLI (registry installs ask you to sign in)
npx @21st-dev/cli@latest init
# Each component page then gives you its own install command,
# or a prompt you can paste into your coding agent.A few community components worth a look:
- Single Pricing Card: a single pricing card with features and testimonials
- Animated Testimonials: a clean animated testimonials section
- Acme Hero: an animated hero section and nav
- File Viewer: a file-tree browser with Shiki-highlighted code previews
Props differ from author to author, so read each component's source after installing it.
Perfect for: Projects that want a wide choice of community-built sections and components.
4. coss ui (formerly Origin UI) - Base UI Components from Cal.com#
coss ui is Cal.com's component library, described as "a new, modern UI component library built on top of Base UI". It is the successor to Origin UI: originui.com now redirects to coss.com/ui. Like shadcn/ui, you get the source rather than a package. Components install through the shadcn CLI under the @coss namespace or can be copied by hand, and pre-assembled "particles" combine several primitives into ready-made patterns. At the time of writing, the site lists 529 particles across 50+ components.
# Get started: https://coss.com/ui/docs/get-started
# New project: all components, colour tokens, base styles and fonts
npx shadcn@latest init @coss/style
# Existing project: all UI primitives
npx shadcn@latest add @coss/ui
# Or one component at a time
npx shadcn@latest add @coss/select// Select: https://coss.com/ui/docs/components/select
import {
Select,
SelectItem,
SelectLabel,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
export function FrameworkSelect() {
return (
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
<SelectLabel>Frameworks</SelectLabel>
{items.map((item) => (
<SelectItem key={item.value} value={item}>
{item.label}
</SelectItem>
))}
</SelectPopup>
</Select>
)
}// Combobox: https://coss.com/ui/docs/components/combobox
import {
Combobox,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
ComboboxPopup,
} from "@/components/ui/combobox"
const frameworks = [
{ value: "react", label: "React" },
{ value: "vue", label: "Vue.js" },
{ value: "angular", label: "Angular" },
]
export function FrameworkCombobox() {
return (
<Combobox items={frameworks}>
<ComboboxInput placeholder="Choose a framework..." />
<ComboboxPopup>
<ComboboxEmpty>No results found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item.value} value={item}>
{item.label}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxPopup>
</Combobox>
)
}// Dialog: https://coss.com/ui/docs/components/dialog
import {
Dialog,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogPanel,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export function EditProfileDialog() {
return (
<Dialog>
<DialogTrigger>Edit profile</DialogTrigger>
<DialogPopup>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>Make changes to your profile here.</DialogDescription>
</DialogHeader>
<DialogPanel>Form fields go here</DialogPanel>
<DialogFooter>
<DialogClose>Close</DialogClose>
</DialogFooter>
</DialogPopup>
</Dialog>
)
}Perfect for: Product UIs that want shadcn/ui-style ownership on Base UI primitives, with ready-made particles for common patterns.
5. Kibo UI - The Advanced Components#
Complex components that extend shadcn/ui
Kibo UI is a free, MIT-licensed set of advanced components and blocks by Hayden Bleasel that extend shadcn/ui: Gantt charts, Kanban boards, color pickers and more. Components install with its own CLI (npx kibo-ui add <component>) or the shadcn CLI, and land in components/kibo-ui/.
Advanced Components:
- Project management: Gantt, Kanban, Roadmap
- Interactive: Color picker, Image zoom, QR code
- Collaborative: Realtime collaborative canvas (a block)
- Content: Code block, Dropzone, Marquee
// npx kibo-ui add gantt
// Gantt: https://www.kibo-ui.com/components/gantt
import {
GanttFeatureItem,
GanttFeatureList,
GanttFeatureListGroup,
GanttHeader,
GanttProvider,
GanttSidebar,
GanttSidebarGroup,
GanttSidebarItem,
GanttTimeline,
GanttToday,
} from "@/components/kibo-ui/gantt"
const status = { id: "1", name: "In Progress", color: "#F59E0B" }
const features = [
{ id: "1", name: "Project Planning", startAt: new Date("2026-01-01"), endAt: new Date("2026-01-15"), status },
{ id: "2", name: "Development", startAt: new Date("2026-01-16"), endAt: new Date("2026-03-01"), status },
]
export function ProjectTimeline() {
return (
<GanttProvider range="monthly" zoom={100} className="border">
<GanttSidebar>
<GanttSidebarGroup name="Launch">
{features.map((feature) => (
<GanttSidebarItem key={feature.id} feature={feature} />
))}
</GanttSidebarGroup>
</GanttSidebar>
<GanttTimeline>
<GanttHeader />
<GanttFeatureList>
<GanttFeatureListGroup>
{features.map((feature) => (
<div className="flex" key={feature.id}>
<GanttFeatureItem {...feature} />
</div>
))}
</GanttFeatureListGroup>
</GanttFeatureList>
<GanttToday />
</GanttTimeline>
</GanttProvider>
)
}// npx kibo-ui add kanban
// Kanban: https://www.kibo-ui.com/components/kanban
"use client"
import { useState } from "react"
import {
KanbanBoard,
KanbanCard,
KanbanCards,
KanbanHeader,
KanbanProvider,
} from "@/components/kibo-ui/kanban"
const columns = [
{ id: "todo", name: "To Do" },
{ id: "in-progress", name: "In Progress" },
{ id: "done", name: "Done" },
]
const initialTasks = [
{ id: "1", name: "Write the spec", column: "todo" },
{ id: "2", name: "Build the API", column: "in-progress" },
]
export function ProjectBoard() {
const [tasks, setTasks] = useState(initialTasks)
return (
<KanbanProvider columns={columns} data={tasks} onDataChange={setTasks}>
{(column) => (
<KanbanBoard id={column.id} key={column.id}>
<KanbanHeader>{column.name}</KanbanHeader>
<KanbanCards id={column.id}>
{(task) => (
<KanbanCard key={task.id} id={task.id} name={task.name} column={column.id} />
)}
</KanbanCards>
</KanbanBoard>
)}
</KanbanProvider>
)
}// npx kibo-ui add color-picker
// Color Picker: https://www.kibo-ui.com/components/color-picker
import {
ColorPicker,
ColorPickerAlpha,
ColorPickerEyeDropper,
ColorPickerFormat,
ColorPickerHue,
ColorPickerOutput,
ColorPickerSelection,
} from "@/components/kibo-ui/color-picker"
export function DesignTool() {
return (
<ColorPicker
defaultValue="#ff0000"
onChange={(rgba) => console.log(rgba)}
className="max-w-sm rounded-md border bg-background p-4 shadow-sm"
>
<ColorPickerSelection />
<div className="flex items-center gap-4">
<ColorPickerEyeDropper />
<div className="grid w-full gap-1">
<ColorPickerHue />
<ColorPickerAlpha />
</div>
</div>
<div className="flex items-center gap-2">
<ColorPickerOutput />
<ColorPickerFormat />
</div>
</ColorPicker>
)
}// npx kibo-ui add qr-code
// QR Code: https://www.kibo-ui.com/components/qr-code
import { QRCode } from "@/components/kibo-ui/qr-code"
export function ShareCard() {
return <QRCode data="https://example.com" robustness="M" className="size-48" />
}Perfect for: Complex applications that need project-management views, rich inputs and collaboration features.
6. Radix UI - The Headless Foundation#
Unstyled, accessible primitives behind a large share of the ecosystem
Radix UI provided the original foundation for shadcn/ui and many other component libraries, and it remains widely used in production. Radix now ships as a single radix-ui package (npm install radix-ui), replacing the per-component @radix-ui/react-* installs.
// Dialog: https://www.radix-ui.com/primitives/docs/components/dialog
import { Dialog } from "radix-ui"
import { Button } from "@/components/ui/button"
export function AccessibleModal() {
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<Button>Open Dialog</Button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50" />
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white p-6">
<Dialog.Title>Edit Profile</Dialog.Title>
<Dialog.Description>Make changes to your profile here.</Dialog.Description>
<div className="mt-4 space-y-2">
<input placeholder="Name" className="w-full rounded border p-2" />
<input placeholder="Email" className="w-full rounded border p-2" />
</div>
<div className="mt-4 flex gap-2">
<Dialog.Close asChild>
<Button variant="outline">Cancel</Button>
</Dialog.Close>
<Dialog.Close asChild>
<Button>Save changes</Button>
</Dialog.Close>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}// Select: https://www.radix-ui.com/primitives/docs/components/select
import { Select } from "radix-ui"
import { ChevronDownIcon } from "@radix-ui/react-icons"
export function CustomSelect() {
return (
<Select.Root>
<Select.Trigger className="flex w-full items-center justify-between rounded border p-2">
<Select.Value placeholder="Select a framework" />
<Select.Icon>
<ChevronDownIcon />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="rounded border bg-white shadow-lg">
<Select.Viewport className="p-1">
<Select.Item value="react" className="p-2 data-highlighted:bg-gray-100">
<Select.ItemText>React</Select.ItemText>
</Select.Item>
<Select.Item value="vue" className="p-2 data-highlighted:bg-gray-100">
<Select.ItemText>Vue.js</Select.ItemText>
</Select.Item>
<Select.Item value="angular" className="p-2 data-highlighted:bg-gray-100">
<Select.ItemText>Angular</Select.ItemText>
</Select.Item>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
)
}// Tooltip: https://www.radix-ui.com/primitives/docs/components/tooltip
import { Tooltip } from "radix-ui"
import { InfoCircledIcon } from "@radix-ui/react-icons"
export function InfoTooltip() {
return (
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button className="rounded p-2 hover:bg-gray-100" aria-label="More info">
<InfoCircledIcon />
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="rounded bg-gray-900 px-2 py-1 text-sm text-white" sideOffset={5}>
This is a helpful tooltip
<Tooltip.Arrow className="fill-gray-900" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
)
}// Accordion: https://www.radix-ui.com/primitives/docs/components/accordion
import { Accordion } from "radix-ui"
import { ChevronDownIcon } from "@radix-ui/react-icons"
export function FAQAccordion() {
return (
<Accordion.Root type="single" collapsible className="w-full">
<Accordion.Item value="item-1" className="border-b">
<Accordion.Header>
<Accordion.Trigger className="group flex w-full justify-between p-4 hover:bg-gray-50">
What is Radix UI?
<ChevronDownIcon className="transition-transform duration-200 group-data-[state=open]:rotate-180" />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content className="p-4 text-sm text-gray-600">
Radix UI provides unstyled, accessible UI primitives for building design systems.
</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="item-2" className="border-b">
<Accordion.Header>
<Accordion.Trigger className="group flex w-full justify-between p-4 hover:bg-gray-50">
Why use headless components?
<ChevronDownIcon className="transition-transform duration-200 group-data-[state=open]:rotate-180" />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content className="p-4 text-sm text-gray-600">
Headless components give you complete control over styling while maintaining accessibility.
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
)
}Why it matters:
- Accessibility first: Keyboard and screen-reader behaviour handled for you
- Unstyled: Complete control over appearance
- Composable: Mix and match primitives
- TypeScript native: Full type safety
- Mature: Years of production use across the shadcn/ui ecosystem
Perfect for: Building custom components, or maintaining existing shadcn/ui projects that already run on Radix.
7. Headless UI - The Tailwind Companion#
Unstyled components designed for Tailwind CSS
Headless UI from the Tailwind CSS team provides unstyled, accessible components that pair naturally with Tailwind. Version 2 replaced the dot-notation API (Dialog.Panel) with named exports (DialogPanel) and exposes state through data attributes such as data-open, data-focus and data-closed. The examples below use the v2 API.
// Dialog: https://headlessui.com/react/dialog
import { Description, Dialog, DialogBackdrop, DialogPanel, DialogTitle } from "@headlessui/react"
import { useState } from "react"
export function HeadlessModal() {
const [isOpen, setIsOpen] = useState(false)
return (
<>
<button onClick={() => setIsOpen(true)} className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
Open dialog
</button>
<Dialog open={isOpen} onClose={() => setIsOpen(false)} className="relative z-50">
<DialogBackdrop transition className="fixed inset-0 bg-black/25 duration-300 ease-out data-closed:opacity-0" />
<div className="fixed inset-0 flex w-screen items-center justify-center p-4">
<DialogPanel
transition
className="w-full max-w-md rounded-2xl bg-white p-6 shadow-xl duration-300 ease-out data-closed:scale-95 data-closed:opacity-0"
>
<DialogTitle className="text-lg font-medium text-gray-900">Payment successful</DialogTitle>
<Description className="mt-2 text-sm text-gray-500">Your payment has been successfully submitted.</Description>
<button
type="button"
className="mt-4 rounded-md bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200"
onClick={() => setIsOpen(false)}
>
Got it, thanks!
</button>
</DialogPanel>
</div>
</Dialog>
</>
)
}// Tabs: https://headlessui.com/react/tabs
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react"
const categories = ["Account", "Password", "Notifications"]
export function HeadlessTabs() {
return (
<TabGroup>
<TabList className="flex gap-1 rounded-xl bg-blue-900/20 p-1">
{categories.map((category) => (
<Tab
key={category}
className="w-full rounded-lg py-2.5 text-sm font-medium text-blue-100 focus:outline-none data-hover:bg-white/10 data-selected:bg-white data-selected:text-blue-700 data-focus:ring-2"
>
{category}
</Tab>
))}
</TabList>
<TabPanels className="mt-2">
{categories.map((category) => (
<TabPanel key={category} className="rounded-xl bg-white p-3">
{category} settings content
</TabPanel>
))}
</TabPanels>
</TabGroup>
)
}// Disclosure: https://headlessui.com/react/disclosure
import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/react"
import { ChevronUpIcon } from "@heroicons/react/20/solid"
const faqs = [
{
question: "What is your refund policy?",
answer: "If you're unhappy with your purchase, email us within 90 days for a full refund.",
},
{
question: "Do you offer technical support?",
answer: "Yes, via email and live chat.",
},
]
export function FAQDisclosure() {
return (
<div className="mx-auto w-full max-w-md space-y-2 rounded-2xl bg-white p-2">
{faqs.map((faq) => (
<Disclosure key={faq.question}>
<DisclosureButton className="group flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200">
<span>{faq.question}</span>
<ChevronUpIcon className="size-5 text-purple-500 group-data-open:rotate-180" />
</DisclosureButton>
<DisclosurePanel className="px-4 pt-4 pb-2 text-sm text-gray-500">{faq.answer}</DisclosurePanel>
</Disclosure>
))}
</div>
)
}// Listbox: https://headlessui.com/react/listbox
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from "@headlessui/react"
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid"
import { useState } from "react"
const people = [
{ id: 1, name: "Wade Cooper" },
{ id: 2, name: "Arlene Mccoy" },
{ id: 3, name: "Devon Webb" },
]
export function HeadlessListbox() {
const [selected, setSelected] = useState(people[0])
return (
<Listbox value={selected} onChange={setSelected}>
<ListboxButton className="relative w-72 rounded-lg bg-white py-2 pr-10 pl-3 text-left shadow-md data-focus:ring-2">
{selected.name}
<ChevronUpDownIcon className="pointer-events-none absolute top-2.5 right-2.5 size-5 text-gray-400" aria-hidden="true" />
</ListboxButton>
<ListboxOptions
anchor="bottom"
transition
className="w-(--button-width) rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 transition duration-100 ease-in data-leave:data-closed:opacity-0"
>
{people.map((person) => (
<ListboxOption
key={person.id}
value={person}
className="group flex cursor-default items-center gap-2 py-2 pr-4 pl-3 data-focus:bg-amber-100 data-focus:text-amber-900"
>
<CheckIcon className="invisible size-5 text-amber-600 group-data-selected:visible" />
{person.name}
</ListboxOption>
))}
</ListboxOptions>
</Listbox>
)
}Perfect for: Projects using Tailwind CSS that need a small set of unstyled, accessible components.
8. React Aria - The Accessibility Foundation#
Adobe's accessible UI components and hooks
React Aria provides the building blocks for accessible, internationalised interfaces. The recommended entry point is react-aria-components: unstyled components that expose their state through render props and data attributes (data-pressed, data-selected, data-focus-visible), which Tailwind can target directly. The lower-level hooks are still available from the react-aria package. shadcn/ui also offers React Aria as a primitive layer, and HeroUI v3 is built on it.
// Checkbox: https://react-aria.adobe.com/Checkbox
import type { ReactNode } from "react"
import { Checkbox } from "react-aria-components"
export function AccessibleCheckbox({ children }: { children: ReactNode }) {
return (
<Checkbox className="group flex cursor-pointer items-center gap-2">
{({ isSelected }) => (
<>
<div className="flex size-5 items-center justify-center rounded border-2 border-gray-300 group-data-focus-visible:ring-2 group-data-focus-visible:ring-blue-300 group-data-selected:border-blue-500 group-data-selected:bg-blue-500">
{isSelected && (
<svg className="size-3 text-white" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)}
</div>
<span className="text-sm font-medium">{children}</span>
</>
)}
</Checkbox>
)
}// Switch: https://react-aria.adobe.com/Switch
import type { ReactNode } from "react"
import { Switch } from "react-aria-components"
export function AccessibleSwitch({ children }: { children: ReactNode }) {
return (
<Switch className="group flex cursor-pointer items-center gap-2">
<div className="relative h-6 w-11 rounded-full bg-gray-300 transition-colors duration-200 group-data-focus-visible:ring-2 group-data-focus-visible:ring-blue-300 group-data-selected:bg-blue-500">
<span className="absolute top-0.5 left-0.5 size-5 rounded-full bg-white transition-transform duration-200 group-data-selected:translate-x-5" />
</div>
<span className="text-sm font-medium">{children}</span>
</Switch>
)
}// Slider: https://react-aria.adobe.com/Slider
import { Label, Slider, SliderOutput, SliderThumb, SliderTrack } from "react-aria-components"
export function AccessibleSlider() {
return (
<Slider defaultValue={50} minValue={0} maxValue={100} className="w-full">
<div className="flex justify-between text-sm">
<Label>Volume</Label>
<SliderOutput />
</div>
<SliderTrack className="relative mt-2 h-2 w-full rounded-full bg-gray-200">
{({ state }) => (
<>
<div className="absolute h-full rounded-full bg-blue-500" style={{ width: `${state.getThumbPercent(0) * 100}%` }} />
<SliderThumb className="top-1/2 size-5 rounded-full border-2 border-blue-500 bg-white data-focus-visible:ring-2 data-focus-visible:ring-blue-300" />
</>
)}
</SliderTrack>
</Slider>
)
}Perfect for: Building highly accessible components, especially where internationalisation and complex keyboard interaction matter.
9. Tremor - The Data Visualization Specialist#
Charts and dashboard components
Tremor provides charts and dashboard components built on Recharts and Tailwind CSS. Vercel acquired Tremor in January 2025, and all of its components and blocks are now free and open source. Tremor's current components are copy-paste (React 18.2+ and Tailwind CSS v4); the older @tremor/react npm package has not had a release since January 2025, so new projects should use the copy-paste versions.
// AreaChart: https://www.tremor.so/docs/visualizations/area-chart
"use client"
import { AreaChart } from "@/components/AreaChart"
import { Card } from "@/components/Card"
const data = [
{ month: "Jan", sales: 4000, revenue: 2400 },
{ month: "Feb", sales: 3000, revenue: 1398 },
{ month: "Mar", sales: 2000, revenue: 9800 },
{ month: "Apr", sales: 2780, revenue: 3908 },
]
export function SalesDashboard() {
return (
<Card className="max-w-4xl">
<h3 className="font-semibold">Sales Overview</h3>
<p className="text-sm text-gray-500">Monthly sales and revenue</p>
<AreaChart
className="mt-4 h-80"
data={data}
index="month"
categories={["sales", "revenue"]}
colors={["blue", "emerald"]}
valueFormatter={(number) => `$${number.toLocaleString()}`}
/>
</Card>
)
}// BarChart: https://www.tremor.so/docs/visualizations/bar-chart
"use client"
import { BarChart } from "@/components/BarChart"
import { Card } from "@/components/Card"
const data = [
{ product: "Product A", sales: 4000, profit: 2400 },
{ product: "Product B", sales: 3000, profit: 1398 },
{ product: "Product C", sales: 2000, profit: 9800 },
]
export function ProductPerformance() {
return (
<Card className="max-w-4xl">
<h3 className="font-semibold">Product Performance</h3>
<BarChart
className="mt-4 h-80"
data={data}
index="product"
categories={["sales", "profit"]}
colors={["blue", "emerald"]}
valueFormatter={(number) => `$${number.toLocaleString()}`}
/>
</Card>
)
}// LineChart: https://www.tremor.so/docs/visualizations/line-chart
"use client"
import { LineChart } from "@/components/LineChart"
import { Card } from "@/components/Card"
const data = [
{ month: "Jan", users: 1000 },
{ month: "Feb", users: 1200 },
{ month: "Mar", users: 1400 },
{ month: "Apr", users: 1600 },
]
export function GrowthTrend() {
return (
<Card className="max-w-4xl">
<h3 className="font-semibold">User Growth</h3>
<LineChart
className="mt-4 h-80"
data={data}
index="month"
categories={["users"]}
colors={["blue"]}
valueFormatter={(number) => number.toLocaleString()}
/>
</Card>
)
}// Card: https://www.tremor.so/docs/ui/card
import { Card } from "@/components/Card"
const metrics = [
{ title: "Total Sales", value: "$12,699", change: "+12.5%" },
{ title: "Active Users", value: "2,345", change: "+8.2%" },
{ title: "Conversion Rate", value: "3.2%", change: "-2.1%" },
]
export function KPIDashboard() {
return (
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
{metrics.map((metric) => (
<Card key={metric.title}>
<p className="text-sm text-gray-500">{metric.title}</p>
<p className="mt-1 text-3xl font-semibold">{metric.value}</p>
<p className="mt-4 text-sm text-gray-500">{metric.change} from last month</p>
</Card>
))}
</div>
)
}Perfect for: Dashboards and applications that need data visualization.
10. HeroUI (formerly NextUI) - The Modern Design System#
A complete, styled design system on Tailwind CSS v4
NextUI was renamed HeroUI in January 2025, and the @nextui-org/react package is deprecated in favour of @heroui/react. HeroUI v3, stable since March 2026, is built on React Aria Components and Tailwind CSS v4, requires React 19, needs no provider, and uses compound components (Card.Header, Card.Title). Unlike the copy-paste libraries above, it is an installed package with its own theme, so it suits teams that want a finished design system rather than one they assemble.
npm i @heroui/styles @heroui/react/* globals.css - import order matters */
@import "tailwindcss";
@import "@heroui/styles";// Card: https://heroui.com/docs/react/components/card
// Button: https://heroui.com/docs/react/components/button
import { Button, Card } from "@heroui/react"
export function HeroUIApp() {
return (
<Card className="max-w-[400px]">
<Card.Header>
<Card.Title>HeroUI</Card.Title>
<Card.Description>heroui.com</Card.Description>
</Card.Header>
<Card.Content>
<p>Make beautiful websites regardless of your design experience.</p>
</Card.Content>
<Card.Footer>
<Button onPress={() => console.log("Started")}>Get Started</Button>
</Card.Footer>
</Card>
)
}Perfect for: Projects that want a polished, ready-made design system on the same Tailwind and React Aria foundations.
11. Supabase Library - The Backend Integration Specialist#
Full-stack blocks that connect your UI to Supabase
The Supabase Library (launched as Supabase UI Library; the old supabase.com/ui address redirects) provides blocks that wire shadcn/ui components to Supabase Auth, Database, Storage and Realtime. Blocks install through the shadcn CLI under the @supabase namespace and are built for the Next.js App Router, with versions for other React frameworks.
# Client: https://supabase.com/library/docs/nextjs/client
npx shadcn@latest add @supabase/supabase-client-nextjs// lib/supabase/client.ts (installed by the block)
import { createBrowserClient } from "@supabase/ssr"
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
)
}# Password-based auth: https://supabase.com/library/docs/nextjs/password-based-auth
npx shadcn@latest add @supabase/password-based-auth-nextjsThe block adds login, sign-up, forgot-password and update-password pages, a protected route, and the email-confirmation route handler, all on top of the Supabase client above.
// app/auth/login/page.tsx
import { LoginForm } from "@/components/login-form"
export default function Page() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<LoginForm />
</div>
</div>
)
}// npx shadcn@latest add @supabase/infinite-query-hook
"use client"
import { useInfiniteQuery } from "@/hooks/use-infinite-query"
export function PostList() {
const { data, isFetching, hasMore, fetchNextPage } = useInfiniteQuery({
tableName: "posts",
columns: "*",
pageSize: 20,
trailingQuery: (query) => query.order("created_at", { ascending: false }),
})
return (
<div>
{data.map((post) => (
<article key={post.id}>{post.title}</article>
))}
{hasMore && (
<button onClick={fetchNextPage} disabled={isFetching}>
{isFetching ? "Loading..." : "Load more"}
</button>
)}
</div>
)
}Block categories:
- Authentication: Password-based auth, social auth, current user avatar
- Database: Infinite query hook
- Storage: Dropzone file upload
- Realtime: Chat, cursors, avatar stacks
Perfect for: Full-stack applications on Supabase that want auth, storage and realtime UI without writing the plumbing.
Bonus: Convex UI - The Real-Time Specialist#
Copy-paste components for Convex-powered applications
Convex UI is a community registry, not an official Convex project. It adapts Supabase's open-source UI library (Apache 2.0) to Convex: auth runs on Convex Auth, realtime features use Convex live queries, and uploads go to Convex storage. Blocks install through the shadcn CLI from the project's own registry URL, with clients for Next.js, React (Vite), TanStack Start and React Router v7.
npx shadcn@latest add "https://convex-ui.vercel.app/r/convex-client-nextjs"// lib/convex/provider.tsx (installed by the block, alongside a Next.js 16 proxy.ts)
"use client"
import { ConvexAuthNextjsProvider } from "@convex-dev/auth/nextjs"
import { ConvexReactClient } from "convex/react"
import { ReactNode } from "react"
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!)
export function ConvexClientProvider({ children }: { children: ReactNode }) {
return <ConvexAuthNextjsProvider client={convex}>{children}</ConvexAuthNextjsProvider>
}// npx shadcn@latest add "https://convex-ui.vercel.app/r/password-based-auth-nextjs"
"use client"
import { useQuery } from "convex/react"
import { LogoutButton } from "@/components/logout-button"
import { Card, CardContent } from "@/components/ui/card"
import { api } from "@/convex/_generated/api"
// The block ships login, sign-up and password-reset pages; this is a protected page.
export default function ProtectedPage() {
const user = useQuery(api.users.current)
return (
<Card>
<CardContent>
<p>Welcome, {user?.name}</p>
<LogoutButton variant="outline" />
</CardContent>
</Card>
)
}// npx shadcn@latest add "https://convex-ui.vercel.app/r/realtime-chat-nextjs"
import { RealtimeChat } from "@/components/realtime-chat"
export function ChatRoom() {
return <RealtimeChat roomName="general" username="user123" className="h-[600px]" />
}
// Messages sync through Convex live queries; the block includes auto-scroll and a connection indicator.// npx shadcn@latest add "https://convex-ui.vercel.app/r/dropzone-nextjs"
"use client"
import { Dropzone } from "@/components/dropzone"
import { useConvexUpload } from "@/hooks/use-convex-upload"
export function FileUploader() {
const upload = useConvexUpload({
allowedMimeTypes: ["image/*"],
maxFiles: 5,
maxFileSize: 10 * 1024 * 1024,
})
return <Dropzone upload={upload} />
}
// Files are stored in Convex storage.Component Categories:
- Authentication: Password-based auth, social (OAuth) auth
- Real-time: Chat, cursors, avatar stacks
- File Storage: Dropzone with Convex storage
- User Display: Current user avatar
Perfect for: Real-time collaborative applications built on Convex with shadcn/ui styling.
Library Comparison#
shadcn/ui
- Copy-paste components
- shadcn CLI
- The foundation
Magic UI
- Copy-paste registry
- shadcn CLI (@magicui)
- Animation and effects
21st.dev
- Community registry
- 21st.dev CLI or shadcn CLI
- Breadth of community sections
coss ui
- Copy-paste components on Base UI
- shadcn CLI (@coss)
- Product UI and particles
Kibo UI
- Copy-paste registry
- kibo-ui or shadcn CLI
- Gantt, Kanban, rich inputs
Radix UI
- Headless primitives
- npm (radix-ui)
- Existing shadcn/ui projects
Headless UI
- Headless components
- npm (@headlessui/react)
- Small Tailwind projects
React Aria
- Headless components and hooks
- npm (react-aria-components)
- Accessibility and i18n
Tremor
- Copy-paste charts
- Copy-paste
- Dashboards
HeroUI
- Styled design system
- npm (@heroui/react)
- A ready-made look
Supabase Library
- Backend blocks
- shadcn CLI (@supabase)
- Supabase apps
Convex UI (bonus)
- Backend blocks (community)
- shadcn CLI (registry URL)
- Convex apps
Building Your Perfect Stack#
Choose the right combination of libraries for your project:
- Start with shadcn/ui for core components
- Add Magic UI for animation
- Lean on the Base UI or Radix primitives shadcn/ui already installs for anything custom
- shadcn/ui as the foundation
- coss ui and Kibo UI for richer app components
- Tremor for data visualization
- Supabase Library (or Convex UI) for backend-connected blocks
- shadcn/ui for base components
- Base UI or Radix for unstyled primitives
- React Aria where accessibility and internationalisation are critical
- Custom components for brand needs
Why Copy-Paste Works#
The strength of the shadcn/ui ecosystem is that you own every component. This means:
- No vendor lock-in: Components live in your codebase
- Complete customization: Modify anything to fit your needs
- Performance control: Only include what you use
- Upgrades on your terms: Component code changes only when you pull an update (the primitives underneath still version like any npm package)

