* fix(coss-ui): convert /settings/my-account/general to coss-ui * update guide * fix: add menuPosition fixed to TimezoneSelect in TravelScheduleModal Co-Authored-By: [email protected] <[email protected]> * fix: use menuPortalTarget to render TimezoneSelect dropdown above dialog footer Co-Authored-By: [email protected] <[email protected]> * fix: use Object.assign for type-safe styles in TimezoneSelect Co-Authored-By: [email protected] <[email protected]> * revert comment * fix: guard document access for SSR in TravelScheduleModal Addresses Cubic AI review feedback: menuPortalTarget={document.body} will throw during server rendering where document is undefined. Added typeof check to guard against SSR. Co-Authored-By: unknown <> * change dialog backdrop from blur to dim * fix: add menuPlacement auto to open dropdown upward on mobile Co-Authored-By: [email protected] <[email protected]> * fix: use menuPlacement top on mobile for TimezoneSelect dropdown Co-Authored-By: [email protected] <[email protected]> * fix: make timezone button stack vertically on mobile to prevent overflow Co-Authored-By: [email protected] <[email protected]> * fix: make timezone select and button 50/50 width on larger viewports Co-Authored-By: [email protected] <[email protected]> * fix date range picker issue on mobile * prevent travel schedule dialog dismissal with date picker --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
28 KiB
title, description
| title | description |
|---|---|
| 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
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:
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:
```tsx title="shadcn/ui" // [!code word:type="multiple"] // [!code word:collapsible] // [!code word:"item-1":1] Title Content ``` ```tsx title="coss ui" // [!code word:multiple={true}] // [!code word:\{\["item-1"\]\}] Title Content ```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 a 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:
```tsx title="shadcn/ui" // [!code word:asChild] // [!code word:AlertDialogCancel] // [!code word:AlertDialogAction] Show Alert Dialog Are you absolutely sure? This action cannot be undone. Cancel Continue ``` ```tsx title="coss ui" // [!code word:render] // [!code word:AlertDialogClose] // [!code word:Content] }> Show Alert Dialog Are you absolutely sure? This action cannot be undone. Content }> Cancel }> Continue ```Avatar
Quick Checklist:
- Replace
asChild→renderonAvatar
Comparison Example:
```tsx title="shadcn/ui" // [!code word:asChild] U ``` ```tsx title="coss ui" // [!code word:render] }> U ```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:
```tsx title="shadcn/ui" // [!code word:asChild] New ``` ```tsx title="coss ui" // [!code word:render] }>New ```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:
```tsx title="shadcn/ui" // [!code word:asChild] Login ``` ```tsx title="coss ui" // [!code word:render] }>Login ```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:
```tsx title="shadcn/ui" // [!code word:asChild] Toggle Content here ``` ```tsx title="coss ui" // [!code word:render] // [!code word:CollapsiblePanel] }>Toggle Content here ```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:
```tsx title="shadcn/ui" // [!code word:asChild] Show Dialog Dialog Title Dialog Description Cancel ``` ```tsx title="coss ui" // [!code word:render] // [!code word:Content] }> Show Dialog Dialog Title Dialog Description Content }>Cancel ```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:
```tsx title="shadcn/ui" // [!code word:onSelect] Open menu { console.log("Dashboard") }} > Dashboard Settings Sign out ``` ```tsx title="coss ui" // [!code word:onClick] Open menu { console.log("Dashboard") }} > Dashboard Settings Sign out ```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:
```tsx title="shadcn/ui" // [!code word:asChild] Open PopoverPopover Title
Popover Description
``` ```tsx title="coss ui" // [!code word:PopoverTitle] // [!code word:PopoverDescription] // [!code word:PopoverClose] // [!code word:render:2] }> Open Popover Popover Title Popover Description }>Close ```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:
```tsx title="shadcn/ui" // [!code word:asChild] Open Preview Card Preview Card Content ``` ```tsx title="coss ui" // [!code word:render] }> Open Preview Card Preview Card Content ```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:
```tsx title="shadcn/ui" // [!code word:placeholder="Select a framework"] Next.js Vite Astro ``` ```tsx title="coss ui" // [!code word:alignItemWithTrigger={false}] // [!code word:items:2] {items.map((item) => ( {item.label} ))} ```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:
```tsx title="shadcn/ui" // [!code word:asChild] Open Sheet Sheet Title Content here Close ``` ```tsx title="coss ui" // [!code word:render] // [!code word:Content here] }> Open Sheet Sheet Title Content here }>Close ```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:
```tsx title="shadcn/ui" // [!code word:TabsContent] Tab 1 Tab 2 Tab 3 Tab 1 content Tab 2 content Tab 3 content ``` ```tsx title="coss ui" // [!code word:TabsPanel] Tab 1 Tab 2 Tab 3 Tab 1 content Tab 2 content Tab 3 content ```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:
```tsx title="shadcn/ui" // [!code word:type="multiple"] B I U ``` ```tsx title="coss ui" // [!code word:multiple] B I U ```Tooltip
Quick Checklist:
- Replace
asChild→renderonTooltipTrigger - Prefer
TooltipPopup;TooltipContentremains for legacy
Comparison Example:
```tsx title="shadcn/ui" // [!code word:asChild] Hover me Tooltip content ``` ```tsx title="coss ui" // [!code word:render] }> Hover me Tooltip content ```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