Troubleshooting cascivo
The failures adopters actually hit, in FAQ form. Each entry: symptom → cause → fix.
pnpm lint errors on every signal write: Error: This value cannot be modified
error Error: This value cannot be modified
Modifying a value returned from a hook is not allowed.
onValueChange={(v) => (env.value = v)}
^^^ `env` cannot be modified
Also seen as react-hooks/immutability, as oxlint's
warning react(immutability): This value cannot be modified, and on
open.value = !open.value, count.value++, or any other signal.value = …
assignment.
Cause: eslint-plugin-react-hooks@7 enables react-hooks/immutability in
recommended-latest — the config a stock 2026 React app gets — and oxlint
ports the same rule as react/immutability, which is what
pnpm create vite --template react-ts scaffolds. The rule reports writes to
values returned from hooks, and useSignal() / useSignalState() return one. Assigning
.value works at runtime — older cascivo docs taught it — but the rule flags it, and the React
Compiler refuses to compile it.
Fix without turning anything off: write through a setter instead of assigning. The setter form passes this rule and compiles under the React Compiler (checked in CI):
const [open, setOpen] = useSignalState(false)
// read open.value in render; in handlers call setOpen(next) instead of open.value = next
Or turn the rule off — ESLint:
pnpm add -D --save-exact @cascivo/eslint-config
// eslint.config.js
import cascivo from '@cascivo/eslint-config'
export default [...yourConfig, ...cascivo] // spread LAST
Or set it directly: { rules: { 'react-hooks/immutability': 'off' } }.
Or turn the rule off — oxlint (no eslint.config.js to spread into):
// .oxlintrc.json
{ "extends": ["./node_modules/@cascivo/eslint-config/src/oxlintrc.json"] }
Or set it directly: { "rules": { "react/immutability": "off" } }.
Not fixable by scoping the rule to src/components/ui/** — signal writes are
in your own pages, and on the prebuilt path that directory doesn't exist.
See USING-WITH-STRICT-ESLINT.md §1 for why the rule cannot be narrowed, what turning it off costs, and cascivo's React Compiler position.
Components render unstyled (no colors, wrong font, no padding)
Cause: the themes CSS is not loaded. Component CSS only defines structure —
it references var(--cascivo-*) custom properties that do not exist until
@cascivo/tokens + a theme are loaded, so alone it yields correctly-structured
but uncolored components.
Fix: import the themes once in your entry file (or Next.js root layout) and
set data-theme:
import '@cascivo/themes/light-dark.css' // tokens (once) + base typography + light & dark
<main data-theme="light">…</main>
See GETTING-STARTED.md.
tsc fails on the CSS import: Cannot find module '@cascivo/react/styles.css' (TS2307 / TS2882)
Symptom: the app runs and is styled, but a typecheck step (tsc --noEmit) reports
TS2307: Cannot find module '@cascivo/react/styles.css' or its corresponding type declarations on the CSS import (also @cascivo/themes/all.css, @cascivo/charts/styles.css).
On a strict scaffold with noUncheckedSideEffectImports (e.g. TanStack Start) the same import
reports TS2882 instead.
Cause: a bare CSS side-effect import has no TypeScript types on its own. Vite ships
ambient *.css module declarations via vite/client, but a project whose tsconfig doesn't
reference them — or one that opts into noUncheckedSideEffectImports — doesn't see them. This
is a standard Vite/TypeScript requirement, not a cascivo-specific issue, but the CSS imports in
our quick-start trip it on a fresh typechecked setup.
Fix: add Vite's client types once. Create src/vite-env.d.ts:
/// <reference types="vite/client" />
If you don't use Vite's client types (or noUncheckedSideEffectImports is on, which vite/client
alone doesn't satisfy), declare the CSS module explicitly instead — this is runtime-free, since a
CSS import is a pure side effect and the declaration only satisfies the type checker:
// src/css.d.ts
declare module '*.css'
The npx cascivo create scaffold writes src/vite-env.d.ts for you; the manual quick-start does
not, so add one of the above when you wire the CSS imports yourself.
Always import the package specifier (@cascivo/react/styles.css,
@cascivo/charts/styles.css), never the physical dist/ filename — the subpath is an
exports alias and the real file (dist/charts.css) is an implementation detail that can change.
SSR crash: Unknown file extension ".css" (TanStack Start, Vite SSR, Remix, workerd)
Symptom: a server-rendered page throws
Error: Unknown file extension ".css" for …/@cascivo/react/dist/<component>/<component>.css,
and the app silently falls back to client-only rendering.
Cause: you're on @cascivo/react < 0.10, whose bundle ships per-component
CSS as static side-effect imports (import './button.css' inside each
component chunk). A bundler resolves those at build time, but a bare server-side
ESM loader — Node's native loader, or a workerd/Cloudflare runtime — has no loader
for .css and throws.
Fix — upgrade: @cascivo/react 0.10+ ships a CSS-free node-condition
server build, so a bare server loader imports it with zero config. Upgrading
makes the crash go away with no vite.config.ts change. Still import the aggregate
stylesheet once so the server HTML is styled:
// your root route / server entry — once
import '@cascivo/react/styles.css'
import '@cascivo/themes/all.css'
Fix — if pinned to < 0.10: tell Vite to process the cascivo packages during SSR instead of leaving them for the runtime to load raw:
// vite.config.ts
export default defineConfig({
ssr: { noExternal: [/^@cascivo\//] }, // ← the < 0.10 fix
})
Or add the cascivoSsr() plugin from @cascivo/vite-plugin, which sets
ssr.noExternal for every @cascivo/* package. Full recipe (TanStack Start,
Remix, workerd): USING-WITH-VITE-SSR.md.
Build error: Cannot find module or type declarations for side-effect import (TS2882)
Symptom: TypeScript errors on a theme CSS import —
error TS2882: Cannot find module or type declarations for side-effect import on
import '@cascivo/themes/all'. Common in the TanStack Start scaffold, which enables
noUncheckedSideEffectImports by default.
Cause: the extensionless @cascivo/themes/all subpath resolves to a .css
file, and under noUncheckedSideEffectImports TS won't accept a side-effect import
whose specifier lacks a recognized module/extension.
Fix: use the .css-suffixed specifier — every theme export has a .css twin:
import '@cascivo/themes/all.css' // not '@cascivo/themes/all'
import '@cascivo/react/styles.css'
The .css form works in every bundler and every tsconfig, so it's the form all
cascivo docs use. (Related tooling note: @tanstack/cli create may drop a nested
pnpm-workspace.yaml inside the app; inside an existing pnpm monorepo, delete it so
pnpm doesn't treat the app as its own workspace root.)
Handlers fire but the UI never updates (toggles don't toggle, modals don't open)
This is the one failure that gives you nothing to search for: no error, no warning, no red console. Every filter, sort, and toggle does nothing, and it looks like your event handlers are broken. They aren't — nothing subscribed the component to the signal.
Cause: a component of yours reads a signal's .value during render without
subscribing. React apps get no Babel signals transform, so a subscription has to come
from somewhere.
Fix — it depends where the signal came from:
| Where your signal came from | What you need |
|---|---|
A cascivo hook — useSignalState, useSignal, useComputed, useDisclosure, useMachine, useTheme, … | Nothing. These subscribe you automatically. (On @cascivo/core < 0.6, useSignal/useComputed did not — upgrade, or add useSignals().) |
A module-level signal(), or a signal passed in as a prop | useSignals() as the component's first statement |
currentLocale() from @cascivo/i18n (a plain function, so it can't subscribe you) | useSignals() as the component's first statement |
import { signal } from '@cascivo/core'
import { useSignals } from '@cascivo/react' // or '@cascivo/core' on the copy-paste path
const isOpen = signal(false) // module-level: NOT a hook
function MyPanel() {
useSignals() // ← first statement, because `isOpen` is a raw signal
return <Modal open={isOpen.value} onClose={() => (isOpen.value = false)} />
}
With a hook-created signal, no useSignals() is needed at all:
import { useSignalState } from '@cascivo/react'
function MyPanel() {
const [isOpen, setIsOpen] = useSignalState(false) // subscribes this component for you
return <Modal open={isOpen.value} onClose={() => setIsOpen(false)} />
}
Write a hook-created signal through its setter, as here, rather than isOpen.value = false:
the assignment is what the React Compiler and react-hooks/immutability reject (see the
first entry on this page). Assigning a module-level signal, as above, is fine.
Which package do I import from? @cascivo/react on the prebuilt path (Path B) —
every primitive is re-exported there, so you never add @cascivo/core to your
package.json. @cascivo/core on the copy-paste path (Path A). See
HEADLESS.md.
Still frozen? useSignals() starts tracking where it is called, so put it first —
above any signal read.
My CSS doesn't override cascivo styles
Cause: cascivo ships everything in cascade layers
(cascivo.base < cascivo.theme < cascivo.component). If your override is
inside a layer that is ordered before cascivo.component, it loses no matter
how specific it is.
Fix: unlayered author CSS beats every cascivo layer regardless of
specificity — a plain stylesheet override just wins. To override from within a
layer, declare your layer ordered after cascivo.component.
The inverse pitfall also exists: a global reset like * { margin: 0; padding: 0 }
written outside any layer beats all cascivo layers too, zeroing out every
component's padding. Wrap resets in a lowest-priority layer. Full recipe:
CSS-LAYERS-PITFALL.md.
A third-party library's CSS is overriding my cascivo styles
Cause: the library ships an unlayered global stylesheet, and unlayered author CSS beats every cascivo layer regardless of specificity.
Fix: import the vendor CSS into a low-priority vendor layer declared before the
cascivo layers — @import url('lib/styles.css') layer(vendor);. Native CSS, no build
tooling. If you're importing the stylesheet from JavaScript
(import 'lib/styles.css'), it can't be layered from there — move it into a CSS file
first. Full recipe: THIRD-PARTY-CSS.md.
My :root token override doesn't apply until data-theme is set
Cause: the specificity footgun. Themes ship a
:root:not([data-theme]) default with specificity (0,2,0), which beats a plain
:root { --cascivo-color-accent: … } at (0,1,0) — so your override silently
loses in the no-attribute state.
Fix: mirror the theme's selector list inside @layer cascivo.theme and
import your file after the cascivo themes, or override from genuinely unlayered
CSS. The recommended pattern (a brand indirection variable) is in
THEMING.md.
cascivo add / cascivo list fails offline or the registry is unreachable
Behavior: the registry index is fetched network-first with an offline
fallback — the CLI caches every fetched copy under ~/.cascivo/cache and, when
the network fails, falls back to the last cached copy with a
Could not reach … — using the last cached copy. notice. Component file
payloads are never cached (they must be fresh, and a truncated install must
fail), so cascivo add itself needs a working connection.
Fix: check connectivity and the registry URL in cascivo.config.ts
(default https://cascivo.com/registry.json). A
Network error fetching … message after four retries means the host is
genuinely unreachable, not that your config is broken.
The docs sites are unreachable (403 from npmjs.com, blocked/offline cascivo.com)
Cause: npmjs.com returns HTTP 403 to non-browser fetches, and cascivo.com
may be blocked by a corporate proxy, uncrawlable page-by-page, or simply offline.
AI agents and firewalled setups hit this often.
Fix: read the docs from the npm registry instead — the same channel that
installed your packages. The entire docs surface ships as
@cascivo/docs, usable with no
install:
npx -y @cascivo/docs # the index (llms.txt)
npx -y @cascivo/docs button # one component's reference
npx -y @cascivo/docs guide theming # a concept guide
npx -y @cascivo/docs --full # the entire library, one file
npx -y @cascivo/docs --list # every available doc path
No npm at all? npm pack @cascivo/docs (or curl the registry tarball) and read
content/llms-full.txt. The package is republished with every release, so its
copy never lags the packages.
A component looks or behaves differently than the docs show
Cause: version drift. Docs and Storybook track the registry head; your copy (or installed package) may be older — or you edited a copied component and forgot.
Fix:
npx cascivo update --check # copied components: lists what changed upstream
npx cascivo update <name> # three-way merge of upstream changes
For the prebuilt packages, compare your installed versions against
breaking-changes.json (every major/minor release per package with notes) —
see UPGRADING.md.
Popover / Sheet / Drawer doesn't open in an older browser
Cause: overlay components are built on the Popover API and
@starting-style, supported in Chrome 114+, Firefox 125+, and Safari 17.4+.
cascivo targets the last 2 versions of Chrome, Firefox, and Safari; older
browsers are outside the support matrix.
Fix: check the feature table in COMPATIBILITY.md. Note
Modal uses the native <dialog> element (much older support) — it is the
conservative choice if you must reach browsers below the Popover API line.
children / className / onClick "does not exist" on every component (TS2322, TS2559)
error ts(2322): Property 'children' does not exist on type 'IntrinsicAttributes & BadgeProps'.
error ts(2559): Type '{ children: Element[]; }' has no properties in common with CardProps.
Every component loses children, className, style, onClick and all aria-* props at
once, usually a dozen-plus errors from one small file.
Cause: @types/react is not resolvable from @cascivo/*. cascivo's .d.ts files
import { HTMLAttributes } from 'react' and most interfaces extend those types; when the
import cannot resolve, extends HTMLAttributes<…> collapses to an error type and each
interface keeps only its own members. skipLibCheck: true (which most setups enable)
hides the diagnostic that would explain it.
When it happens: only when your package manager's hoisting is restricted. pnpm's default
layout builds a hidden node_modules/.pnpm/node_modules/ holding every transitive package,
and TypeScript finds React's types there by accident — so most installs never hit this. Set
hoist: false (or a hoist-pattern that excludes @types/*) and that safety net is gone.
If your pnpm-workspace.yaml or .npmrc restricts hoisting, this is your bug.
Fix: upgrade to @cascivo/react ≥ 0.14.0 — every package that ships React types now
declares @types/react as an optional peer, so pnpm puts it on the resolution path even with
hoisting off. pnpm isolated:check verifies this in CI against a hoist: false workspace,
and it is observed failing when the peer is removed.
On an older version, add it yourself:
# pnpm-workspace.yaml
publicHoistPattern:
- '@types/react'
- '@types/react-dom'
The page has a horizontal scrollbar I didn't create
Often a second, vertical one appears next to it: the horizontal bar makes the document taller than the viewport.
Cause: no global box-sizing: border-box. Components that are width: 100% and
padded (Textarea, Input, Select) compute wider than their container under the
browser's content-box default.
Fix: upgrade to @cascivo/tokens ≥ 0.6.0 — the cascivo.reset layer now ships the
floor, and it arrives automatically with any theme or @cascivo/react/styles.css. It is
the lowest cascade layer, so your own reset still wins.
On an older version, fill the layer cascivo reserves:
@layer cascivo.reset {
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
}
}
A button below a MultiSelect / Sheet stopped responding to clicks
Nothing looks wrong. Screenshots are perfect, there is no console output, and the element below the control simply never receives a click.
Cause: the closed overlay panel was still laid out. The browser hides a closed popover
with [popover]:not(:popover-open) { display: none }, but that is a UA-origin rule, so
an author display: flex in the component's base rule beat it. The panel stayed invisible
(opacity: 0), fixed-position and hit-testable — an unmarked rectangle swallowing every
click beneath it.
Fix: upgrade to @cascivo/react ≥ 0.14.0.
On an older version, put the UA behaviour back:
@layer cascivo.override {
[popover]:not(:popover-open) {
display: none;
}
}
This does not break the open/close animation — display is already in those components'
transition with allow-discrete.
Charts render unstyled
@cascivo/react auto-loads its CSS, so it is reasonable to assume @cascivo/charts does
too. It did not.
Fix: upgrade to @cascivo/charts ≥ 0.8.0 — it imports its own stylesheet, matching
@cascivo/react. (@cascivo/editor, @cascivo/flow and @cascivo/ai had the same gap and
were fixed together.) On an older version, import it explicitly:
import '@cascivo/charts/styles.css'
Still unstyled on a current version? You are probably on the node export condition.
Vite-SSR frameworks externalise dependencies on the server, and cascivo ships a CSS-free
node/ twin for those packages (a bare .css import is unloadable by a plain Node ESM
loader). On that path nothing imports the sheet for you, so the explicit import above is
required — and skipping it renders the chart's screen-reader data-table fallback visibly, as
a table of numbers under every chart. See the stylesheet table in
GETTING-STARTED.md.
I need a type that lives in @cascivo/core, but I'm on the prebuilt path
Status.status and Badge.variant are typed ToneInput; every layout gap is a
SpaceStep. Those declarations live in @cascivo/core, which on the prebuilt path is a
transitive dependency — so import type { Tone } from '@cascivo/core' is a phantom
import under pnpm's strict layout, and adding it as a direct dependency puts you in a
two-package version lockstep.
Fix: import them from the subpath.
import type { Tone } from '@cascivo/react/types'
const DEPLOY_TONE: Record<DeployState, Tone> = { ready: 'success', error: 'danger' }
@cascivo/react/types exports Tone, ToneAlias, ToneInput, Progress, ProgressAlias,
ProgressInput, SpaceStep and RovingOrientation. On the copied-source path, import from
@cascivo/core directly — you depend on it there.
Reading core's .d.ts from a terminal. It is a transitive dep, so it sits under pnpm's
content-addressed store rather than top-level node_modules. Let Node find it:
node -p "require.resolve('@cascivo/core/package.json')"
Or skip the trip entirely: npx -y @cascivo/docs serves the whole documentation set offline.
setTheme() runs but nothing changes
useTheme() reports the new theme, no error is thrown, and data-theme never changes.
Cause: setTheme() writes the theme signal; the mounted <ThemeProvider> is what
writes the attribute. With no provider mounted, the signal updates and the DOM does not.
Fix: wrap your app in <ThemeProvider> (SSR-safe, and it persists the choice). If you
are theming outside React — an imperative shell, a pre-hydration script, a Storybook
decorator — use applyTheme(theme, target?) instead, which writes the attribute directly:
import { applyTheme } from '@cascivo/react'
applyTheme('midnight')
@cascivo/react ≥ 0.14.0 warns in dev when setTheme() runs with no provider mounted.
Components render greyscale after setting a theme like cyberpunk
Cause: that theme's CSS is not loaded. @cascivo/themes/light-dark.css carries light
and dark only; @cascivo/react/styles.css bundles the same two.
Fix: import @cascivo/themes/all.css (all twelve themes) or the single theme file you
need. Before 0.14.0, all.css itself contained only light and dark despite the name — if
you are on an older version, import @cascivo/themes/cyberpunk.css explicitly.
ThemeProvider emits a dev warning naming the exact import to add.
pnpm refuses to install cascivo: "is younger than the minimum release age"
Cause: pnpm's minimumReleaseAge gate and a recently published cascivo release. Not a
cascivo defect — the same gate trips on any fresh package.
Fix: wait out the window, or exclude the packages:
# pnpm-workspace.yaml
minimumReleaseAgeExclude:
- '@cascivo/*'
Quick answers
Is cascivo free? Yes — MIT licensed. Commercial and private use, no fee, no attribution requirement.
Do I have to adopt all of it, or can I add one component? Add exactly what you need. Components are copied into your repo one at a time — no required provider, nothing to buy into. Start with a single button.
Do I need Tailwind? No. Styling is modern platform CSS — @layer, custom
properties, container queries — driven by a three-tier token system. Using
Tailwind v4 alongside cascivo works too: USING-WITH-TAILWIND.md.
Does it work with Next.js / React Server Components? Yes — components ship
'use client' preserved. Setup in USING-WITH-NEXTJS.md.
Does it work with Vite SSR / TanStack Start / Remix / workerd? Yes — install,
import a theme once, and render. On @cascivo/react < 0.10 add
ssr.noExternal: [/^@cascivo\//] (or the cascivoSsr() plugin), or the server
loader throws Unknown file extension ".css" (see the entry above). You do not need
@cascivo/react/styles.css: component CSS rides the module graph and tree-shakes.
Full recipe: USING-WITH-VITE-SSR.md.
How is this different from shadcn/ui? Same ownership model (you own copied source), plus signal reactivity, a closed token system, twelve themes, built-in WCAG 2.2 AA, and a machine-readable AI layer. Mapping in MIGRATING-FROM-SHADCN.md.
How do I change a component's behavior, not just its color? You own the
source — edit it directly. The cascivo-extend skill walks the
safe way to add behavior without breaking the accessibility contract.
Will my AI agent generate correct cascivo code? Every component ships a
machine-readable manifest, the @cascivo/mcp server exposes
them to agents, and npx cascivo audit --ai <paths> flags hardcoded values,
invented props, and missing i18n in generated output.
What browsers are supported? The last two versions of Chrome, Firefox, and
Safari — cascivo relies on :has() and @container. CSS @function/if()
usage is a Chrome-leading pilot with static fallbacks everywhere else, so
nothing breaks where it is unsupported. Matrix: COMPATIBILITY.md.
As Markdown: /docs/troubleshooting.md