Compatibility & support matrix
What cascivo runs on, which package versions go together, and the build-tooling baseline. If an integration surprises you, start here.
Frameworks
| Framework | Supported | Notes |
|---|---|---|
| React 18 / 19 | ✅ Yes | Primary target. Components ship 'use client' preserved. |
| Next.js App Router (RSC) | ✅ Yes | Import the CSS once in a Server Component (e.g. app/layout.tsx); components are client. Working example: apps/examples/react-next. See USING-WITH-NEXTJS.md. |
| Vite + React (CSR/SPA) | ✅ Yes | Reference setup. See apps/examples/react-vite. |
| Vite SSR / TanStack Start | ✅ Yes¹ | Requires ssr.noExternal: [/^@cascivo\//] (or the cascivoSsr() plugin). Working example: apps/examples/react-vite-ssr. See USING-WITH-VITE-SSR.md. |
Preact 10 (preact/compat) | ✅ CSR only | Verified on Vite CSR (@preact/preset-vite) — components, signals, overlays and charts all behave as on React, at roughly half the JS. Not verified under SSR/prerender, and known to fail under Astro's compat aliasing. See USING-WITH-PREACT.md. |
| Cloudflare Workers (client app + API) | ✅ Yes | Client-rendered (no SSR): static assets with an SPA fallback, /api/* routed to the Worker. npx cascivo create --framework cloudflare (or npm create cascivo) emits it wired up, on Preact or React; the framework:check CI job builds that scaffold from packed tarballs and runs its Worker. A ready-to-deploy copy lives in starters/cloudflare (Deploy to Cloudflare button, npm create cloudflare --template). Working example with Workers AI streaming: apps/examples/chat. |
| Astro (React islands) | ✅ Yes² | Requires @cascivo/react ≥ 1.0.1 and vite.resolve.noExternal: [/^@cascivo\//] in astro.config.mjs — without both, SSR'd islands render unstyled. npx cascivo create --framework astro emits it wired up. Working example: apps/examples/astro-islands. See USING-WITH-ASTRO.md. |
| Vue / Svelte / Angular | ⚠️ Tokens/themes only | @cascivo/tokens + @cascivo/themes are framework-agnostic CSS; the components are React. |
| Ghost (Handlebars themes) | ⚠️ Tokens/themes only | Themes are Handlebars rendered server-side with no JS framework layer, so React components cannot mount. The token + theme CSS works once flattened (bare @imports, no build step in Ghost). Working theme, validated by Ghost's own gscan in CI: apps/examples/ghost-theme. See USING-WITH-GHOST.md. |
¹ The published @cascivo/react bundle ships per-component CSS as static
side-effect imports. Bundlers resolve these; a bare server-side ESM loader
(Node native, workerd) does not and throws Unknown file extension ".css".
Since 0.10 the node export condition selects a CSS-free server twin, so no
config is needed; ssr.noExternal (or cascivoSsr()) remains the fallback for
pinned older versions. Since 0.18.0 a react-server condition points RSC back at
the CSS-bearing build, so component CSS tree-shakes under SSR the same way it does
in an SPA — no aggregate stylesheet in either recipe. The
Vite SSR guide has the measurements.
² Astro's unstyled-island problem had two independent causes, and both must be
addressed. (a) Export conditions match in declaration order, and until 1.0.1 the
CSS-free node twin was listed ahead of import; a Vite-based SSR framework resolves
with node active and react-server inactive, so it got the CSS-free build. Since
1.0.1 a module condition sits ahead of node — bundlers match it, while Node's ESM
resolver, which does not implement module, still falls through to the twin, so
footnote ¹'s guarantee is unchanged. This affected @cascivo/react, @cascivo/charts,
@cascivo/ai, @cascivo/editor and @cascivo/flow; the ordering is enforced by
pnpm css-contract:check. (b) Vite externalizes node_modules packages in its server
build, so the package's module graph is never walked and Astro — which collects a page's
CSS from that graph — emits none; vite.resolve.noExternal puts it back. It must be
resolve.noExternal, not ssr.noExternal, which Astro's prerender environment does not
read. Cause (b) does not appear in the monorepo's own Astro fixture, because a
workspace:* link is never externalized — see the verification notes in
USING-WITH-ASTRO.md.
Browsers
cascivo targets the last 2 versions of Chrome, Firefox, and Safari. It relies on modern CSS that is broadly shipped as of 2025:
| Feature | Min support | Used for |
|---|---|---|
@layer | Chrome 99, FF 97, Saf 15.4 | predictable cascade ordering |
@container | Chrome 105, FF 110, Saf 16 | slot-aware responsive components |
:has() | Chrome 105, FF 121, Saf 15.4 | stateful styling without JS |
oklch() | Chrome 111, FF 113, Saf 15.4 | the entire color system |
Popover API / @starting-style | Chrome 114+, FF 125+, Saf 17.4+ | overlays (Sheet, Drawer, Popover) |
subgrid | Chrome 117, FF 71, Saf 16 | Grid's alignRows — cross-card band alignment |
field-sizing: content | Chrome 123, FF 121, Saf 26 | Textarea's autosize — progressive |
anchor-size() / position-visibility | Chrome 125+, Saf 26+ | panel widths and anchor-tied visibility — progressive |
scroll-state container queries | Chrome 133+ only | ScrollArea shadows, DataTable stuck header — progressive |
appearance: base-select | Chrome 135+ only | NativeSelect's themed option list — progressive |
CSS @function / if() | Chrome 133+ only | progressive enhancement only (below) |
CSS @function is opt-in
--cascivo-step / --cascivo-scale live in @cascivo/tokens/functions.css and
are not auto-imported, because current CSS minifiers (lightningcss, used by
Tailwind v4) cannot parse @function and silently drop the rule. Every call site
in cascivo ships a static fallback for the same property, so omitting functions is
always visually correct. Opt in only if your pipeline supports @function:
import '@cascivo/tokens/functions.css' // Chrome 133+ progressive enhancement
Build tooling
- Bundlers: Vite/Rolldown, webpack, esbuild, and any bundler that honors the
package
exportsmap. On a bundler you import no component CSS at all — it rides the module graph. If you do need the aggregate (CDN, no build step), import the@cascivo/react/styles.cssspecifier — never the underlyingdist/cascivo.csspath (strictexportsblocks it). - CSS minifiers: cssnano and esbuild handle the shipped CSS as-is.
lightningcss (Tailwind v4) works too as long as you don't opt into
@cascivo/tokens/functions.css(see above). - Using Tailwind v4 alongside cascivo? See
USING-WITH-TAILWIND.mdfor the@layerorder, the.dark↔[data-theme]dark-mode bridge, and the opt-in@cascivo/themes/tailwind.cssthat maps cascivo tokens onto Tailwind's--color-*utilities. - The
@importorder is spec-clean: tokens no longer emit an@importafter a@layer, so there's no@import must precede all other statementswarning.
Package compatibility
The runtime packages are 1.x and covered by semver. The ten packages that share
@cascivo/core release in lockstep — install them at the same version. See
UPGRADING.md for which packages are 1.x and which
tooling packages are still 0.x.
This table is generated from the packages themselves by pnpm regen and verified by
CI's drift check — it cannot go stale. (It once sat thirteen minors behind, claiming
@cascivo/react 0.2.x while npm served 0.13.0, which is why it is no longer hand-written.)
| Package | Version | Peer requirements |
|---|---|---|
@cascivo/core | 1.6.x | @preact/signals-react >=3.0.0, @types/react >=18.0.0 (optional), react >=18.0.0, react-dom >=18.0.0 |
@cascivo/tokens | 1.2.x | none (CSS only) |
@cascivo/themes | 1.0.x | @cascivo/tokens (direct dep) — themes @import it |
@cascivo/react | 1.6.x | @preact/signals-react >=3.0.0, @types/react >=18.0.0 (optional), react >=18.0.0, react-dom >=18.0.0 |
@cascivo/icons | 1.1.x | @types/react >=18.0.0 (optional), react >=18.0.0 |
@cascivo/charts | 1.6.x | @preact/signals-react >=3.0.0, @types/react >=18.0.0 (optional), react >=18.0.0, react-dom >=18.0.0 |
@cascivo/i18n | 1.6.x | @preact/signals-react >=3.0.0 |
@cascivo/storage | 1.6.x | @preact/signals-react >=3.0.0 |
@cascivo/data | 0.1.x | none |
@cascivo/app | 1.6.x | @preact/signals-react >=3.0.0, @types/react >=18.0.0 (optional), react >=18.0.0 |
@cascivo/mcp | 0.8.x | (server; run via npx) |
React 19 requires
@preact/signals-react3.x. React 19 removed the internal that signals-react 2.x imports, so a 2.x runtime fails to load under React 19 (SyntaxError: … does not provide an export named '__SECRET_INTERNALS…'). The peer range (>=3) enforces this; signals-react 3.x still supports React 16.14+/17/18, so the floor costs React-18 users nothing. If a lockfile from an earlier install pins 2.x, runcascivo doctor— it flags the mismatch with the upgrade command.
Minimal install
pnpm add @cascivo/react @cascivo/themes @preact/signals-react
@cascivo/tokens arrives transitively through @cascivo/themes — it is a direct
dependency of themes (the theme CSS @imports it), so it installs automatically on
every package manager, with or without auto-install-peers.
Required CSS import order
import '@cascivo/themes/light-dark.css' // tokens (once) + base typography + light & dark
import './my-theme.css' // optional brand overrides — always LAST
Component CSS is not in that list because it is not yours to import: each component
chunk carries its own stylesheet and your bundler collects only what you use. The
no-bundler path adds @cascivo/react/styles.css first; it defines component
structure only — it references var(--cascivo-*) values that don't exist until a
theme + tokens are loaded, so importing it alone yields correctly-structured but
uncolored components. See THEMING.md.
Right-to-left
RTL works out of the box: set dir="rtl" (or dir="auto") on any ancestor — <html>, a
route wrapper, a single panel — and the catalog mirrors. No import, no prop, no theme
variant.
<html dir="rtl" lang="ar">
<!-- every cascivo component now mirrors -->
</html>
This holds because every shipped rule uses CSS logical properties (margin-inline-start,
padding-block, inset-inline-start, text-align: start) rather than physical ones, and
both halves of that are enforced rather than asserted. pnpm rtl:check fails on any physical
inline property in shipped CSS, and separately mounts components in real Chromium under both
directions and requires every asymmetric inline box to swap. The one deliberate exception is
ContextMenu, which positions from the pointer's viewport x — a physical coordinate by
nature.
What is not covered: bidirectional text handling inside your own content (that is the
browser's job, and dir="auto" is usually the right answer), and RTL-specific iconography —
a directional icon you pass as a prop is yours to mirror.
As Markdown: /docs/compatibility.md