Reference
Known limitations
A serious library doesn't pretend everything is universal. These are the boundaries we know about — documented so they're not a surprise.
1View Transitions API availability
Runtime transitions use the View Transitions API when available and fall back to a CSS-variable cross-fade otherwise. The View Transitions API is not available in every browser or context:
- Not available in Firefox as of writing — the CSS cross-fade fallback is used.
- Only applies to top-level same-origin navigations/updates; scoped updates inside
ThemeScopeuse the fallback path. - If
prefers-reduced-motion: reduceis set, transitions are disabled entirely rather than degraded.
2Custom scrollbar
ThemeScrollbar is an overlay scrollbar: it hides the native scrollbar and draws its own. This has consequences:
- The native scrollbar is hidden via the pre-paint CSS; content width does not change (no layout shift), but the scrollbar itself occupies no space — content can be obscured until the overlay thumb is over it.
- Browser-specific native scrollbar styling (Firefox
scrollbar-width, WebKit pseudo-elements) is replaced, not enhanced. - Scroll-linked effects that measure
window.innerWidthbefore the overlay mounts can observe the wider viewport.
3Touch and mobile
On touch devices the overlay scrollbar is intentionally inert: it does not fight native touch scrolling. The scrollbar engine observes pointer/touch input and does not attach drag handling to touch pointers. Consequences:
- Touch scrolling is fully native — the overlay thumb is informational, not draggable, on touch.
- On hybrid devices (touch laptops), a coarse-pointer touch event disables drag interaction until a fine pointer is detected again.
- iOS Safari's rubber-banding and scroll-to-refresh behave exactly as the native scrollbar would — the overlay does not interfere.
4SSR
SSR support differs slightly by framework. Next.js and Nuxt are the reference integrations with the full story (cookie resolution, fingerprint, blocking bootstrap, zero-flash). The others hydrate client-side:
- Vue / Svelte / Solid / Angular providers are client components; SSR renders the provider but theme resolution happens on hydration. Use the core
createThemeBootstrapScriptfor zero-flash in those frameworks. - Angular ships an
esm+defaultexport map (ng-packagr layout) rather than the standardimport/requireconditions; Noderequire()of the Angular package is not supported. - The
ThemeScrollbarSSR output is pre-paint CSS only; the overlay engine mounts on hydration.
5System dark with JavaScript disabled
With JavaScript disabled and initialMode: "system", the first paint uses the light theme. The @media (prefers-color-scheme: dark) fallback block cannot override the inline light CSS variables that the SSR render applies to <html>. The blocking bootstrap script is the primary zero-flash mechanism; with JS enabled, system dark is applied before first paint in every browser that supports matchMedia.
6Adapter constraints
Adapters bridge tokens into a component library — they don't replicate every library behavior:
- MUI / Chakra / Ant Design / Mantine adapters are React-based and are available in React, Next.js, Astro, and Remix. They are not shipped for Vue, Svelte, or Solid — using a React-only component library in a non-React framework is not a supported combination.
- shadcn/ui, Bootstrap, daisyUI, Open Props have framework-neutral factories and CSS output, so they work in every framework.
- UnoCSS is a preset: it maps tokens to utilities that reference the live
--theme-*variables, so it has no runtime install/update/cleanup lifecycle of its own. - Mantine is a theme builder + hook; apply it manually when the theme changes (it has no runtime adapter).
7Framework-specific gaps
- Svelte —
ThemeScopereadstheme/family/modeprops at mount only (the component is a legacy-style Svelte component). React, Vue, and Solid react to prop changes. Remount the scope to change an explicit theme in Svelte. - Angular — the docs app and adapters are verified against the standalone
provideThemeintegration; Angular Universal streaming is not verified. - Nuxt — the module targets Nuxt 4 (compatible with 3.10+); auto-imported composables work in the app directory, and server utilities are exported from the package root.
Each integration declares the framework version it supports as a peer dependency, and the compatibility table lists it. Where a framework has released a new major since that range was set, the range has not been widened yet — the integration is untested there, not known to be broken.
8Framework dependency advisories
@themvora/* packages ship zero external runtime dependencies — frameworks are peer dependencies. When you install a framework, its own dependency tree is yours. Run npm audit / pnpm audit in your application and upgrade framework versions to clear advisories in their transitive dependencies (for example, Next.js→postcss, Astro→sharp, Nuxt→brace-expansion, Remix→turbo-stream, Angular→fast-uri).
9Svelte ThemeScope reactivity
This limitation is called out above and repeated for visibility: ThemeScope in Svelte reads its props at mount. It still follows provider mode changes for family/boundary scopes via the store subscription, but an explicit theme prop change requires a remount. See the Svelte API reference.
These are the boundaries we know about. If you hit something that behaves differently across environments and isn't listed here, file an issue — it's either a bug or a limitation we should document.