- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Avatar
- Badge
- Breadcrumb
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- CommandNew
- Dialog
- Empty
- Field
- Fieldset
- Form
- Frame
- Group
- Input
- Input Group
- Kbd
- Label
- Menu
- Meter
- Number Field
- Pagination
- Popover
- Preview Card
- Progress
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Toolbar
- Tooltip
Radix / shadcn Migration
A comprehensive guide for migrating from Radix UI and shadcn/ui to coss ui components.
This guide is designed for developers who already have applications built with shadcn/ui or Radix UI and want to adopt coss ui components. coss ui is fundamentally built with Base UI from the ground up—it is not an adaptation from Radix UI. Recognizing that many teams are migrating from Radix-based libraries, this page consolidates all migration instructions for a smooth transition.
Overview
coss ui components are built on Base UI, not Radix. This means the architecture and API patterns are different by design. However, we've worked to make the migration path as clear as possible by:
- Providing detailed component-by-component migration guides below
- Maintaining similar component names and structures where it makes sense
- Offering clear prop mappings and code examples
Tip: This migration guide is structured to be LLM-friendly. You can provide this page to AI coding assistants (like Claude, ChatGPT, etc.) to help automate the conversion of your Radix/shadcn components to coss ui.
General Migration Patterns
The asChild to render Pattern
The most common change across all components is replacing Radix UI's asChild prop with Base UI's render prop:
<DropdownMenuTrigger asChild>
<Button>Edit profile</Button>
</DropdownMenuTrigger><MenuTrigger render={<Button />}>Edit profile</MenuTrigger>Component Naming Conventions
Some components have updated names for clarity and consistency:
*Content→*Popupor*Panel(e.g.,DialogContent→DialogPopup)- Legacy names are often kept for backward compatibility
Component Migration Guides
Accordion
Quick Checklist:
- Replace
type="multiple"→multiple={true}onAccordion - Remove
type="single"fromAccordion - Remove
collapsiblefromAccordion - Always use arrays for
defaultValue - Use
AccordionPanelgoing forward;AccordionContentremains for legacy - If you used
asChildon parts, switch to therenderprop
Prop Mapping:
| Component | Radix UI Prop | Base UI Prop |
|---|---|---|
Accordion | type (enum, "single" or "multiple") | multiple (boolean, default: false) |
Accordion | collapsible | removed |
Comparison Example:
<Accordion type="multiple" collapsible defaultValue="item-1">
<AccordionItem value="item-1">
<AccordionTrigger>Title</AccordionTrigger>
<AccordionContent>Content</AccordionContent>
</AccordionItem>
</Accordion><Accordion multiple={true} defaultValue={["item-1"]}>
<AccordionItem value="item-1">
<AccordionTrigger>Title</AccordionTrigger>
<AccordionPanel>Content</AccordionPanel>
</AccordionItem>
</Accordion>Alert
New Variants:
We've added new colored variants for better semantic meaning:
| Variant | Description |
|---|---|
info | Displays an info alert (blue) |
success | Displays a success alert (green) |
warning | Displays a warning alert (yellow) |
error | Displays an error alert (red) |
Ensure you have the following variables imported in your CSS file:
--destructive-foreground--info--info-foreground--success--success-foreground--warning--warning-foreground
Alert Dialog
Quick Checklist:
- Replace
asChild→renderonAlertDialogTriggerand closing buttons - Replace
AlertDialogActionandAlertDialogCancel→AlertDialogClose - Prefer
AlertDialogPopup;AlertDialogContentremains for legacy - Use
AlertDialogPanelto wrap main content betweenAlertDialogHeaderandAlertDialogFooter - If you used
asChildon any other parts, switch to therenderprop
Comparison Example:
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Show Alert Dialog</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction asChild>
<Button>Continue</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog><AlertDialog>
<AlertDialogTrigger render={<Button variant="outline" />}>
Show Alert Dialog
</AlertDialogTrigger>
<AlertDialogPopup>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogPanel>Content</AlertDialogPanel>
<AlertDialogFooter>
<AlertDialogClose render={<Button variant="ghost" />}>
Cancel
</AlertDialogClose>
<AlertDialogClose render={<Button variant="destructive" />}>
Continue
</AlertDialogClose>
</AlertDialogFooter>
</AlertDialogPopup>
</AlertDialog>Avatar
Quick Checklist:
- Replace
asChild→renderonAvatar
Comparison Example:
<Avatar asChild>
<Link href="/profile">
<AvatarImage src="avatar.jpg" alt="User" />
<AvatarFallback>U</AvatarFallback>
</Link>
</Avatar><Avatar render={<Link href="/profile" />}>
<AvatarImage src="avatar.jpg" alt="User" />
<AvatarFallback>U</AvatarFallback>
</Avatar>Badge
Quick Checklist:
- Replace
asChild→renderonBadge
Size Comparison:
Compared to shadcn/ui, our Badge component includes size variants for better density control. shadcn/ui badges have a fixed size, while our component offers flexible sizing with sm, default, and lg options.
So, if you want to preserve the original shadcn/ui badge size, you should use the lg size in coss ui.
New Variants:
We've added new colored variants to the existing ones (default, destructive, outline, secondary) for better semantic meaning and visual communication:
| Variant | Description |
|---|---|
info | Blue badge for information |
success | Green badge for success states |
warning | Yellow badge for warnings |
error | Red badge for errors |
Ensure you have the following variables imported in your CSS file:
--destructive-foreground--info--info-foreground--success--success-foreground--warning--warning-foreground
Comparison Example:
<Badge asChild>
<Link href="/new">New</Link>
</Badge><Badge render={<Link href="/new" />}>New</Badge>Button
Quick Checklist:
- Replace
asChild→renderonButton
Size Comparison:
coss ui button sizes are more compact compared to shadcn/ui, making them better suited for dense applications. We also introduce new sizes (xs, xl, icon-sm, icon-lg) for more granular control:
| Size | Height (shadcn/ui) | Height (coss ui) |
|---|---|---|
xs | - | 24px |
sm | 32px | 28px |
default | 36px | 32px |
lg | 40px | 36px |
xl | - | 40px |
icon | 36px | 32px |
icon-sm | - | 28px |
icon-lg | - | 36px |
So, for example, if you were using the default size in shadcn/ui and you want to preserve the original height, you should use the lg size in coss ui.
New Variants:
We've added a new destructive-outline variant for better UX patterns:
- Primary actions: Use
destructive(solid red) for the main destructive action - Secondary triggers: Use
destructive-outline(outline red) to avoid alarming red buttons in the main interface
Comparison Example:
<Button asChild>
<Link href="/login">Login</Link>
</Button><Button render={<Link href="/login" />}>Login</Button>Card
Quick Checklist:
- Use
CardPanelgoing forward;CardContentremains for legacy
Checkbox
Quick Checklist:
- Replace
asChild→renderonCheckbox
Collapsible
Quick Checklist:
- Replace
asChild→renderonCollapsibleTrigger - Prefer
CollapsiblePanel;CollapsibleContentremains for legacy
Comparison Example:
<Collapsible>
<CollapsibleTrigger asChild>
<Button>Toggle</Button>
</CollapsibleTrigger>
<CollapsibleContent>Content here</CollapsibleContent>
</Collapsible><Collapsible>
<CollapsibleTrigger render={<Button />}>Toggle</CollapsibleTrigger>
<CollapsiblePanel>Content here</CollapsiblePanel>
</Collapsible>Command
The API is significantly different from shadcn/ui (cmdk). Please review both docs before migrating: cmdk Docs and shadcn/ui Command, and our Base UI Autocomplete docs.
Key Differences:
- No
cmdkdependency - built entirely with Base UI's Autocomplete and Dialog components - Data-driven approach - pass an
itemsarray toCommandand use render functions instead of manually composingCommandItemchildren - Use
CommandCollectionwithinCommandGroupwhen rendering grouped data with the items pattern - Use
CommandDialog,CommandDialogTrigger, andCommandDialogPopupfor dialog functionality instead of composing separate Dialog components CommandGroupuses<CommandGroupLabel>as a child instead of aheadingprop
Dialog
Quick Checklist:
- Replace
asChild→renderonDialogTriggerand closing buttons - Prefer
DialogPopup;DialogContentremains for legacy - Use
DialogPanelto wrap main content betweenDialogHeaderandDialogFooter - If you used
asChildon any other parts, switch to therenderprop
Comparison Example:
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Show Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>Dialog Description</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="ghost">Cancel</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog><Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Show Dialog
</DialogTrigger>
<DialogPopup>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>Dialog Description</DialogDescription>
</DialogHeader>
<DialogPanel>Content</DialogPanel>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
</DialogFooter>
</DialogPopup>
</Dialog>Group (Button Group)
Quick Checklist:
- Prefer
Group*component names;ButtonGroup*remain for compatibility GroupSeparatoris always required between controls, including outline buttons (unlike shadcn where separators are optional for outline buttons). This ensures consistent focus state handling and better accessibility- If you used
asChildonButtonGroupText, switch to therenderprop for custom components
Input
Compared to shadcn/ui, our Input component includes size variants for better density control. shadcn/ui inputs have a fixed height of 36px, while our component offers flexible sizing with sm (28px), default (32px), and lg (36px) options.
So, if you want to preserve the original shadcn/ui input height (36px), you should use the lg size in coss ui.
Input Group
Quick Checklist:
- No
InputGroupButtoncomponent - use the regularButtoncomponent directly insideInputGroupAddoninstead - To disable an input group, disable the
InputGroupInputorInputGroupTextareadirectly (and any Button inside it) - no need to add adata-disabledattribute onInputGroup.
Menu
Prop Mapping:
| Component | Radix UI Prop | Base UI Prop |
|---|---|---|
MenuItem | onSelect | onClick |
Quick Checklist:
- Replace
asChild→renderonMenuTriggerandMenuItem - Replace
onSelect→onClickon menu items - Update import path from
@/components/ui/dropdown-menu→@/components/ui/menu - Prefer
Menu*component names;DropdownMenu*remain for legacy - Prefer
MenuGroupLabelinstead ofDropdownMenuLabel - Prefer
MenuPopupinstead ofDropdownMenuContent - Prefer
MenuSubPopupinstead ofDropdownMenuSubContent - If you used
asChildon any other parts, switch to therenderprop
Comparison Example:
<DropdownMenu>
<DropdownMenuTrigger>Open menu</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onSelect={() => {
console.log("Dashboard")
}}
>
Dashboard
</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Sign out</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu><Menu>
<MenuTrigger>Open menu</MenuTrigger>
<MenuPopup>
<MenuItem
onClick={() => {
console.log("Dashboard")
}}
>
Dashboard
</MenuItem>
<MenuItem>Settings</MenuItem>
<MenuItem>Sign out</MenuItem>
</MenuPopup>
</Menu>Popover
Quick Checklist:
- Replace
asChild→renderonPopoverTriggerand closing buttons - Prefer
PopoverPopup;PopoverContentremains for legacy - If you used
asChildon any other parts, switch to therenderprop
Additional Notes:
Base UI introduces PopoverTitle and PopoverDescription to structure headings and helper text inside the popup. Base UI also introduces a PopoverClose component for adding close buttons to the popup.
Comparison Example:
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Open Popover</Button>
</PopoverTrigger>
<PopoverContent>
<h2>Popover Title</h2>
<p>Popover Description</p>
</PopoverContent>
</Popover><Popover>
<PopoverTrigger render={<Button variant="outline" />}>
Open Popover
</PopoverTrigger>
<PopoverPopup>
<PopoverTitle>Popover Title</PopoverTitle>
<PopoverDescription>Popover Description</PopoverDescription>
<PopoverClose render={<Button variant="ghost" />}>Close</PopoverClose>
</PopoverPopup>
</Popover>Preview Card
Quick Checklist:
- Update import path from
@/components/ui/hover-card→@/components/ui/preview-card - Prefer
PreviewCard*component names;HoverCard*remain for legacy - Prefer
PreviewCardPopupinstead ofHoverCardContent - If you used
asChildon parts, switch to therenderprop
Comparison Example:
<HoverCard>
<HoverCardTrigger asChild>
<Button variant="outline">Open Preview Card</Button>
</HoverCardTrigger>
<HoverCardContent>Preview Card Content</HoverCardContent>
</HoverCard><PreviewCard>
<PreviewCardTrigger render={<Button variant="outline" />}>
Open Preview Card
</PreviewCardTrigger>
<PreviewCardPopup>Preview Card Content</PreviewCardPopup>
</PreviewCard>Progress
Quick Checklist:
- Prefer
ProgressLabelandProgressValuefor label/value instead of inline elements - If you render children inside
Progress, you must includeProgressTrackandProgressIndicator(otherwise the bar will not display). Without children, a default bar is rendered for you - If you used
asChild, switch to therenderprop
Additional Notes:
Base UI introduces separate parts — ProgressLabel, ProgressValue, ProgressTrack, and ProgressIndicator — which you compose inside Progress for greater flexibility.
Radio Group
Quick Checklist:
- Use
Radiogoing forward;RadioGroupItemremains for legacy - Replace
asChild→renderon parts if used
Scroll Area
Quick Checklist:
- If you used
asChildon parts, switch to therenderprop
Select
Important: Base UI changes how options are provided. Instead of deriving options from children only (Radix pattern), you should pass an items prop (array or record) so values and labels are known before hydration. This avoids SSR pitfalls and improves mount performance. Alternatively, provide a function child to SelectValue to format the label. See the Base UI Select docs.
Prop Mapping:
| Component | Radix UI Prop | Base UI Prop |
|---|---|---|
Select | items | items |
SelectValue | placeholder | removed |
SelectPopup | alignItemWithTrigger | no equivalent |
Quick Checklist:
- Set
itemsprop onSelect - Remove
placeholderfromSelect - Prefer
SelectPopupinstead ofSelectContent - If you used
asChildon parts, switch to therenderprop
Size Comparison:
coss ui select sizes are more compact compared to shadcn/ui, making them better suited for dense applications:
| Size | Height (shadcn/ui) | Height (coss ui) |
|---|---|---|
sm | 32px | 28px |
default | 36px | 32px |
lg | - | 36px |
So, for example, if you were using the default size in shadcn/ui and you want to preserve the original height, you should use the lg size in coss ui.
Additional Notes:
Base UI introduces the alignItemWithTrigger prop to control whether the SelectContent overlaps the SelectTrigger so the selected item's text is aligned with the trigger's value text.
Comparison Example:
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a framework" />
</SelectTrigger>
<SelectContent>
<SelectItem value="next">Next.js</SelectItem>
<SelectItem value="vite">Vite</SelectItem>
<SelectItem value="astro">Astro</SelectItem>
</SelectContent>
</Select><Select
items={[
{ label: "Select a framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup alignItemWithTrigger={false}>
{items.map((item) => (
<SelectItem key={item.value} value={item}>
{item.label}
</SelectItem>
))}
</SelectPopup>
</Select>Sheet
Quick Checklist:
- Replace
asChild→renderonSheetTriggerand closing buttons - Prefer
SheetPopup;SheetContentremains for legacy - Use
SheetPanelto wrap main content - If you used
asChildon any other parts, switch to therenderprop
Comparison Example:
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Open Sheet</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Sheet Title</SheetTitle>
</SheetHeader>
Content here
<SheetFooter>
<SheetClose asChild>
<Button>Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet><Sheet>
<SheetTrigger render={<Button variant="outline" />}>
Open Sheet
</SheetTrigger>
<SheetPopup>
<SheetHeader>
<SheetTitle>Sheet Title</SheetTitle>
</SheetHeader>
<SheetPanel>Content here</SheetPanel>
<SheetFooter>
<SheetClose render={<Button />}>Close</SheetClose>
</SheetFooter>
</SheetPopup>
</Sheet>Slider
Quick Checklist:
- coss ui
Slideruses Base UI's multiple value approach - Always pass values as arrays (e.g.,
value={[50]}instead ofvalue={50}) onValueChangereceives an array of numbers- Multiple thumbs are supported natively via array length
- Replace
asChild→renderon parts if used
Switch
Quick Checklist:
- Replace
asChild→renderonSwitchif used
Tabs
Quick Checklist:
- Replace
asChild→renderon parts if used - Use
TabsTabgoing forward;TabsTriggerremains for legacy - Prefer
TabsPanel;TabsContentremains for legacy
Additional Notes:
Compared to shadcn/ui, our TabsList component adds variant prop, which allows you to choose between default and underline styles.
Comparison Example:
<Tabs defaultValue="tab-1">
<TabsList>
<TabsTab value="tab-1">Tab 1</TabsTab>
<TabsTab value="tab-2">Tab 2</TabsTab>
<TabsTab value="tab-3">Tab 3</TabsTab>
</TabsList>
<TabsContent value="tab-1">Tab 1 content</TabsContent>
<TabsContent value="tab-2">Tab 2 content</TabsContent>
<TabsContent value="tab-3">Tab 3 content</TabsContent>
</Tabs><Tabs defaultValue="tab-1">
<TabsList>
<TabsTab value="tab-1">Tab 1</TabsTab>
<TabsTab value="tab-2">Tab 2</TabsTab>
<TabsTab value="tab-3">Tab 3</TabsTab>
</TabsList>
<TabsPanel value="tab-1">Tab 1 content</TabsPanel>
<TabsPanel value="tab-2">Tab 2 content</TabsPanel>
<TabsPanel value="tab-3">Tab 3 content</TabsPanel>
</Tabs>Textarea
Compared to shadcn/ui, our Textarea component includes size variants (sm, default, lg) for better density control. For visual consistency, if you're using size="lg" on other form elements like inputs, you should add the same size to textareas as well.
Toast
The API is significantly different from shadcn/ui (Sonner). Please review both docs before migrating: Sonner Docs and shadcn/ui Sonner, and our Base UI toast docs.
Quick Checklist:
- Replace
<Toaster />component in layout →<ToastProvider>wrapper - Toast API calls differ significantly - see comparison below
- Toast actions use different patterns
Comparison Examples:
shadcn/ui (Sonner)
import { Toaster } from "@/components/ui/sonner"
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<main>{children}</main>
<Toaster />
</body>
</html>
)
}toast("Event has been created", {
description: "Sunday, December 03, 2023 at 9:00 AM",
cancel: {
label: "Undo",
},
})coss ui (Base UI)
import { ToastProvider } from "@/components/ui/toast"
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<ToastProvider>
<main>{children}</main>
</ToastProvider>
</body>
</html>
)
}onClick={() => {
const id = toastManager.add({
title: "Event has been created",
description: "Sunday, December 03, 2023 at 9:00 AM",
type: "success",
actionProps: {
children: "Undo",
onClick: () => toastManager.close(id),
},
})
}}Toggle
Quick Checklist:
- Replace
asChild→renderonToggleif used
Toggle Group
Prop Mapping:
| Component | Radix UI Prop | Base UI Prop |
|---|---|---|
ToggleGroup | type (enum, "single" or "multiple") | multiple (boolean, default: false) |
Quick Checklist:
- Replace
type="multiple"→multipleonToggleGroup - Remove
type="single"fromToggleGroup - Always use arrays for
defaultValue - Use
Togglegoing forward;ToggleGroupItemremains for legacy - Replace
asChild→renderon parts if used
Size Comparison:
coss ui toggle group sizes are more compact compared to shadcn/ui, making them better suited for dense applications:
| Size | Height (shadcn/ui) | Height (coss ui) |
|---|---|---|
sm | 32px | 28px |
default | 36px | 32px |
lg | - | 36px |
So, for example, if you were using the default size in shadcn/ui and you want to preserve the original height, you should use the lg size in coss ui.
Comparison Example:
<ToggleGroup type="multiple" defaultValue={["bold"]}>
<ToggleGroupItem value="bold">B</ToggleGroupItem>
<ToggleGroupItem value="italic">I</ToggleGroupItem>
<ToggleGroupItem value="underline">U</ToggleGroupItem>
</ToggleGroup><ToggleGroup multiple defaultValue={["bold"]}>
<Toggle value="bold">B</Toggle>
<Toggle value="italic">I</Toggle>
<Toggle value="underline">U</Toggle>
</ToggleGroup>Tooltip
Quick Checklist:
- Replace
asChild→renderonTooltipTrigger - Prefer
TooltipPopup;TooltipContentremains for legacy
Comparison Example:
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline">Hover me</Button>
</TooltipTrigger>
<TooltipContent>Tooltip content</TooltipContent>
</Tooltip><Tooltip>
<TooltipTrigger render={<Button variant="outline" />}>
Hover me
</TooltipTrigger>
<TooltipPopup>Tooltip content</TooltipPopup>
</Tooltip>Additional Resources
- Base UI Documentation - Official Base UI docs
- Component Documentation - Individual component docs with examples
- Styling Guide - Learn about our color system and theming
Need Help?
If you encounter issues during migration or have questions about specific components, please:
- Check the individual component documentation pages
- Review the Base UI documentation for deeper understanding
- Open an issue on our GitHub repository
On This Page