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/ui | cascivo | Notes |
|---|---|---|
| Accordion | accordion | |
| Alert | alert | |
| Alert Dialog | alert-dialog | |
| Aspect Ratio | aspect-ratio | |
| Avatar | avatar | |
| Badge | badge | |
| Breadcrumb | breadcrumb | |
| Button | button | |
| Button Group | button-group | |
| Calendar | calendar | |
| Card | card | |
| Carousel | carousel | |
| Chart | chart | chart family via @cascivo/charts |
| Checkbox | checkbox | |
| Collapsible | collapsible | |
| Combobox | combobox | |
| Command | command-menu | |
| Context Menu | context-menu | |
| Data Table | data-table | |
| Date Picker | date-picker | |
| Dialog | modal | |
| Direction | by convention | RTL via CSS logical properties throughout; no JS provider |
| Drawer | sheet | sheet covers the panel; no mobile swipe gesture. drawer queued (v18-t6) |
| Dropdown Menu | dropdown | |
| Empty | empty-state | |
| Field | field | |
| Hover Card | hover-card | |
| Input | input | |
| Input Group | input-group | |
| Input OTP | otp-input | |
| Item | item | |
| Kbd | kbd | |
| Label | label | |
| Menubar | menubar | |
| Native Select | native-select | Wraps the native control; cascivo select is the custom listbox. |
| Navigation Menu | navigation-menu | |
| Pagination | pagination | |
| Popover | popover | |
| Progress | progress-bar | progress-bar + progress-circle |
| Radio Group | radio | |
| Resizable | resizable | a.k.a. splitter |
| Scroll Area | scroll-area | |
| Select | select | |
| Separator | separator | |
| Sheet | sheet | |
| Sidebar | side-nav | |
| Skeleton | skeleton | |
| Slider | slider | |
| Sonner | toast | |
| Spinner | spinner | |
| Switch | toggle | |
| Table | data-table | |
| Tabs | tabs | |
| Textarea | textarea | |
| Toast | toast | |
| Toggle | toggle | |
| Toggle Group | toggle-group | |
| Tooltip | tooltip | |
| Typography | prose | prose + 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:
| cascivo | What you probably expect | What 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 MUI | an overlap primitive: children are layered on top of one another (think position: relative + absolutely-stacked children), not spaced apart. |
What you want instead:
| Goal | Use |
|---|---|
| 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>
- Styles live in cascade layers (
cascivo.base < cascivo.theme < cascivo.component). Your own unlayered CSS always wins — seeCSS-LAYERS-PITFALL.md. - Design tokens are
--cascivo-*custom properties, enumerated inTOKENS.md(and@cascivo/tokens/tokens.jsonfor tooling). Notailwind.configto mirror.
Button variants
cascivo's Button variants are not shadcn's. There is no outline.
| shadcn variant | cascivo variant | Notes |
|---|---|---|
default | primary | the filled, primary action |
secondary | secondary | same name |
outline | secondary | no bordered-only variant — use secondary |
ghost | ghost | same name |
destructive | destructive | same name |
link | ghost + Link | use 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.
| shadcn | cascivo |
|---|---|
--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-themes | cascivo |
|---|---|
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