Migrating from shadcn/ui to cascivo

cascivo's component and prop API is close to shadcn/ui, so most JSX ports over with small renames. The two real differences are variant names and the CSS setup (cascade layers + a theme, instead of Tailwind utility classes). This page maps the deltas the way a migrator hits them.

Component mapping

Every shadcn/ui component and its cascivo equivalent. Click a cascivo component to open its doc page, which carries the exact cascivo add command, props, and examples.

Generated from the parity matrix — 58 of 59 shadcn/ui components have a cascivo equivalent. Each cascivo link carries the exact cascivo add command.

shadcn/uicascivoNotes
Accordionaccordion
Alertalert
Alert Dialogalert-dialog
Aspect Ratioaspect-ratio
Avataravatar
Badgebadge
Breadcrumbbreadcrumb
Buttonbutton
Button Groupbutton-group
Calendarcalendar
Cardcard
Carouselcarousel
Chartchartchart family via @cascivo/charts
Checkboxcheckbox
Collapsiblecollapsible
Comboboxcombobox
Commandcommand-menu
Context Menucontext-menu
Data Tabledata-table
Date Pickerdate-picker
Dialogmodal
Directionby conventionRTL via CSS logical properties throughout; no JS provider
Drawersheetsheet covers the panel; no mobile swipe gesture. drawer queued (v18-t6)
Dropdown Menudropdown
Emptyempty-state
Fieldfield
Hover Cardhover-card
Inputinput
Input Groupinput-group
Input OTPotp-input
Itemitem
Kbdkbd
Labellabel
Menubarmenubar
Native Selectnative-selectWraps the native control; cascivo select is the custom listbox.
Navigation Menunavigation-menu
Paginationpagination
Popoverpopover
Progressprogress-barprogress-bar + progress-circle
Radio Groupradio
Resizableresizablea.k.a. splitter
Scroll Areascroll-area
Selectselect
Separatorseparator
Sheetsheet
Sidebarside-nav
Skeletonskeleton
Sliderslider
Sonnertoast
Spinnerspinner
Switchtoggle
Tabledata-table
Tabstabs
Textareatextarea
Toasttoast
Toggletoggle
Toggle Grouptoggle-group
Tooltiptooltip
Typographyproseprose + text + heading

Layout names that invert the ecosystem convention

Two layout primitives are named against what Chakra, MUI and Radix taught you. Both are deliberate, both are flagged in their TSDoc — but the TSDoc only helps once you have already picked the component, and by then you have usually written the markup. So, before you pick:

cascivoWhat you probably expectWhat it actually does
<Flex>a row — CSS flex-direction defaults to row, and so do Chakra's <Flex>, MUI's <Stack direction>, and Radix's <Flex>defaults to direction="vertical". Pass direction="horizontal" for a row.
<Stack>a spacing stack — a column with a gap, which is what <Stack> means in Chakra and MUIan overlap primitive: children are layered on top of one another (think position: relative + absolutely-stacked children), not spaced apart.

What you want instead:

GoalUse
A row of items with a gap<Flex direction="horizontal" gap={3}>
A column of items with a gap<Flex gap={3}> (vertical is the default)
Elements layered on top of each other<Stack>

A 2026-07-28 adopter's summary: the JSDoc "caught us before runtime — but the names still invert two of the strongest conventions in the ecosystem." The names are staying (renaming them now would break every existing app for a naming preference); this table exists so the inversion is findable while you are choosing a component rather than while hovering one.


CSS setup delta

shadcn relies on Tailwind: you copy a globals.css with @tailwind directives and a big block of CSS variables, and style with utility classes. cascivo ships real stylesheets — import the themes once and theme with a data-theme attribute. Component CSS comes along with each component import (tree-shaken per component by your bundler), so there's no component stylesheet to wire up. There is no Tailwind dependency and no tailwind.config to port:

// shadcn: Tailwind directives + utility classes in markup
// cascivo: one themes import, then plain components
import '@cascivo/themes/light-dark.css' // tokens once + base typography + light & dark
// component CSS (@layer cascivo.component) auto-included on import
<main data-theme="light">
  <Button>Save</Button>
</main>

Button variants

cascivo's Button variants are not shadcn's. There is no outline.

shadcn variantcascivo variantNotes
defaultprimarythe filled, primary action
secondarysecondarysame name
outlinesecondaryno bordered-only variant — use secondary
ghostghostsame name
destructivedestructivesame name
linkghost + Linkuse the Link component for link styling
// shadcn
<Button variant="default">Save</Button>
<Button variant="outline">Cancel</Button>

// cascivo
<Button variant="primary">Save</Button>
<Button variant="secondary">Cancel</Button>

size (sm | md | lg), loading, and disabled carry over; Button spreads ButtonHTMLAttributes.

Form fields come with their own label/hint/error

shadcn composes FormField + FormItem + FormLabel + FormMessage around each control. cascivo inputs (Input, Textarea, Select, …) accept label, hint, and error directly, removing the wrapper boilerplate:

// shadcn: ~6 wrapper components per field
// cascivo:
<Textarea label="Bio" hint="Markdown supported" error={errors.bio} />

For full forms, Field and the signal-based createForm/useForm store cover validation without a resolver library.

App shell / sidebar

Don't hand-roll the shell. AppShell wires ShellHeader + SideNav + content into one sticky-header, full-height-nav, single-scroll-container layout, with the header burger bound to the nav, an animated (and prefers-reduced-motion-aware) show/hide, inert/focus handling, and a mobile drawer:

<AppShell header={<ShellHeader brand={{ name: 'Acme' }} />} nav={<SideNav items={items} />}>
  <h1>Dashboard</h1>
</AppShell>

Theming: CSS variables → tokens

shadcn's theme is one flat layer of semantic CSS variables (--background, --primary, --radius) plus a .dark class that overrides them. cascivo uses a three-tier token system — primitive → semantic → component — and scopes themes with a data-theme attribute instead of a class, so a theme can apply to any subtree, not just the document root.

shadcncascivo
--background / --foreground--cascivo-color-bg / --cascivo-color-text
--primary / --primary-foreground--cascivo-color-accent / --cascivo-color-accent-foreground
--muted / --muted-foreground--cascivo-color-surface / --cascivo-color-text-subtle
--border--cascivo-color-border
--radius--cascivo-radius-* (control/surface/full)
.dark { … }[data-theme="dark"] (or warm, plus 9 more)
next-themes ThemeProvider / useTheme@cascivo/react ThemeProvider / useTheme
/* shadcn: override the flat variables under .dark */
.dark { --primary: 210 40% 98%; }

/* cascivo: retint by scope — no rebuild, works on any element */
[data-theme="dark"] { --cascivo-color-accent: oklch(0.7 0.15 250); }

The runtime switcher maps 1:1 in concept, but the hook shape differs — don't copy next-themes' destructuring:

next-themescascivo
const { theme, setTheme } = useTheme() (object; theme is a string)const [theme, setTheme] = useTheme() (tuple; theme is a string — use it directly)

cascivo's useTheme() returns a [theme, setTheme] tuple where theme is a plain string (the current theme name — use it directly, e.g. theme === 'dark'), not a { theme, setTheme } object. The hook is signal-backed and calls useSignals() for you, so the component re-renders on theme changes; for signal-native code use the themeSignal() export. themePreloadScript() covers the SSR pre-paint script you'd otherwise hand-write. See THEMING.md.

Full catalog in TOKENS.md; brand a single component by overriding its component-tier tokens (see THEMING.md).

Styling: cn() and utility classes → data attributes

There is no cn() / clsx / tailwind-merge in cascivo, because there are no utility classes to merge. Variants and states are props that map to data-* attributes the component's own CSS targets — so conditional styling is data, not class-string concatenation:

// shadcn: cn() composes utility classes for each state
<button className={cn('inline-flex …', variant === 'ghost' && 'bg-transparent', disabled && 'opacity-50')} />

// cascivo: props → data-attributes, styled in the component's CSS
<Button variant="ghost" disabled />   // renders data-variant="ghost" [disabled]

To extend a component you own (copied in via the CLI), edit its .module.css directly — no @apply, no config.

Forms: react-hook-form → createForm

shadcn wires forms with react-hook-form + a zod resolver + the Form* component family. cascivo ships a signal-based store, createForm / useForm, with built-in sync/async validation — no resolver library. form.field(name) returns the value/onChange/onBlur/error to wire onto any control:

// shadcn: useForm (RHF) + zodResolver + <FormField> render props
// cascivo:
const form = useForm({
  initialValues: { email: '' },
  validate: (v) => (v.email.includes('@') ? {} : { email: 'Invalid email' }),
})
const email = form.field('email')
<Form form={form} onValid={console.log}>
  <Input
    label="Email"
    value={email.value}
    onChange={(e) => email.onChange(e.currentTarget.value)}
    onBlur={email.onBlur}
    error={email.error}
  />
  <Button type="submit">Save</Button>
</Form>

Charts: Recharts → @cascivo/charts

shadcn charts wrap Recharts (an SVG React chart lib you install as a dependency). @cascivo/charts is built from scratch — its own scales and shapes, signal-driven, zero runtime dependencies, CVD-safe palettes, and keyboard-navigable tooltips:

// shadcn: <ChartContainer> around Recharts <AreaChart>/<Area>
// cascivo:
import { AreaChart } from '@cascivo/charts'
<AreaChart series={series} x={(d) => d.date} y={(d) => d.value} />

The chart families map closely (area, bar, line, pie, radar, radial); cascivo adds many Recharts doesn't ship (candlestick, sankey, treemap, sunburst, funnel, gauge, boxplot, and more). See the charts overview.

What else exists

Before hand-rolling a component, check the index — cascivo ships heavy ones that are easy to miss: DataTable (sort/filter/paginate/select/expand), CommandMenu (⌘K), EmptyState, Stat, DataList, Combobox, MultiSelect, and more. The full categorized list is in the @cascivo/react README and at cascivo.com.

As Markdown: /docs/migrating-from-shadcn.md

← All guides