Comparison to nuqs
How kvant relates to nuqs, and the key differences between them.
nuqs is the library that popularized type-safe URL query
state for React: useQueryState syncs a piece of state with a search param,
using parsers to convert between strings and typed values.
kvant's API is heavily inspired by nuqs. If you've used it, the core loop (schema to hook to
URL as source of truth) will feel familiar.
import { , } from 'nuqs'
const [, ] = ('page', .(0))Key differences
Many interfaces, not only search params
nuqs syncs state with URL search params. kvant treats search params as one of many key-value interfaces, all sharing one API:
| nuqs | kvant | |
|---|---|---|
| Search Params | ✅ useQueryState | ✅ useSearchParams / useRouteQuery (Vue Router, Nuxt) |
| Route Params | — | ✅ useRouteParams (Vue Router, Nuxt) |
| Router Query | — | ✅ useRouterQuery (Next.js pages router) |
| Local Storage | — | ✅ useLocalStorage |
| Session Storage | — | ✅ useSessionStorage |
| Cookies | — | ✅ useCookies |
| Custom Adapters | Limited to search params, undocumented | ✅ Supported & documented |
Same mental model everywhere:
import { } from 'kvantjs/next'
import { , , } from 'kvantjs/react'
import * as from 'kvantjs/schema'
// URL search params: shareable, bookmarkable
const [, ] = ('q', .().(''))
// localStorage: persists across reloads, syncs across tabs
const [, ] = ('theme', .(['light', 'dark']).('light'))
// sessionStorage: scoped to the current tab
const [, ] = ('draft', .().(''))
// Cookies: readable by the server, respect Set-Cookie attributes
// (requires additional setup for SSR, see the full guide)
const [, ] = (
'consent',
.().(false),
{ : 60 * 60 * 24 * 365 },
)Full guides: Search Params · Local Storage · Cookies
Multiple framework families
nuqs is React-only (with adapters for Next.js, React Router and other React-based frameworks). kvant additionally supports Vue, Vue Router, and Nuxt with idiomatic composables that return writable refs:
<script setup lang="ts">
import { } from 'kvantjs/vue'
import * as from 'kvantjs/schema'
const = ('q', .().(''))
</script>Options live at binding time, not call time
In nuqs, you can pass options per setState call
(setPage(2, { history: 'push' })). kvant doesn't allow this by design:
options are set when binding (hook call) or globally (options providers,
defineKvantState defaults).
kvant hooks mirror the framework's original state API as closely
as possible: React's setState doesn't take options, Vue's ref.value =
can't take options, so kvant's setters don't either. One state API to
learn, no surprises when switching interfaces.
Schemas instead of parsers
nuqs parsers define parse/serialize (with eq for comparison). kvant
schemas define parse/encode and add a fluent, Zod-like API on top:
checks, transforms, defaults, structures, via the built-in
kvantjs/schema library. Zod and other validation libraries
slot in too.
Migrating from nuqs?
The concepts map closely:
| nuqs | kvant |
|---|---|
useQueryState(key, parser) | useSearchParams(key, schema) |
useQueryStates(keyMap) | useSearchParams(keyMap) |
parsers (parseAsInteger, ...) | schemas (kv.int(), ...) |
parse / serialize | parse / encode |
.withDefault(value) | .default(value) |
clearOnDefault | .default(value, { clearOnDefault }) |