URL State Management

Sync component state with TanStack Router search params for shareable, bookmarkable UI state.

Cubitt components can mirror their state to TanStack Router search params through Cubitt's shared URL-state hooks. This gives you shareable URLs, bookmarkable states, and browser navigation that stays aligned with the UI without wiring every component manually.

Requirements

Cubitt URL state requires TanStack Router context. If your app already uses TanStack Start or TanStack Router, there is no extra adapter to install.

app/root.tsx
import { RouterProvider } from "@tanstack/react-router";
import { router } from "./router";

export function App() {
  return <RouterProvider router={router} />;
}

Type-safe paramName keys

Cubitt narrows paramName from your registered TanStack Router route tree.

  • TanStack Start / file-based routing: route generation usually handles this automatically.
  • Effect in Cubitt: built-in URL-state components can validate paramName at compile time, offer autocomplete for known keys, and stay safe through route refactors.

That means this:

<Input paramName="search" />

is not just a string prop in a properly registered app. TypeScript can:

  • suggest known search keys in autocomplete
  • reject invalid param names at compile time
  • keep those usages in sync if you rename a search key in the route tree

If paramName is only typed as plain string in your app, the usual cause is missing TanStack Router registration. In TanStack Start/file-based apps, check that your generated route types are present. In manual router setups, add the standard TanStack Router Register declaration once in your app.

Escape hatch for unregistered keys

If you intentionally need a query key that is not part of your registered route tree, wrap it with untypedUrlStateParamName(...).

import {
  Input,
  untypedUrlStateParamName,
} from "@tilt-legal/cubitt-components";

<Input paramName={untypedUrlStateParamName("debug-panel")} />;

Lost type safety

untypedUrlStateParamName(...) opts that callsite out of TanStack Router key checking. You lose autocomplete for known keys and TypeScript can no longer catch typos or route-search refactors for that parameter. Prefer fixing router registration first and use this only for deliberate exceptions.

Validation model

Cubitt intentionally splits URL-state responsibilities:

  • Cubitt codecs parse and serialize the individual URL parameter value.
  • Your route validateSearch owns full-object validation, transforms, and defaults.

That keeps Cubitt route-agnostic while still giving strong typing and app-level search validation.

Basic usage

Add URL state to any supported component by providing a paramName:

<Input paramName="search" placeholder="Search..." />

When the user types, the URL updates to ?search=hello and the input value stays synchronized with that parameter.

Supported components

23 components support URL state synchronization, organized by behavior:

Display Components (Read URL State)

These components read open/closed state from the URL and expose matching helpers:

Shared URL props

OptionTypeDescriptionDefault
paramNamestringURL parameter name. Narrows to known search keys when your router is registered.-
paramMatchValuestringOpen only when the URL value matches this string-
onUrlValueChangefunctionCallback when the parameter value changes-
paramClearOnDefaultbooleanRemove the parameter when the component returns to defaulttrue
paramThrottlenumberThrottle URL writes in milliseconds-
paramDebouncenumberDebounce URL writes in milliseconds-

Trigger Components (Write URL State)

These components only write to the URL when triggered:

Shared URL props

OptionTypeDescriptionDefault
paramNamestringURL parameter name. Narrows to known search keys when your router is registered.-
paramSetValuestringValue to set in the URL when triggered-
paramThrottlenumberThrottle URL writes in milliseconds-
paramDebouncenumberDebounce URL writes in milliseconds-

State Components (Two-Way URL Sync)

These components fully synchronize their value with URL parameters:

Text Inputs

Selection

Toggles

Numeric Input

Data Table Composites

Shared URL props

OptionTypeDescriptionDefault
paramNamestringURL parameter name. Narrows to known search keys when your router is registered.-
paramValueTControlled value for the URL parameter-
paramCodecUrlStateCodec<T>Custom codec for advanced values or custom parsingauto
onUrlValueChangefunctionCallback when the parameter value changes-
paramClearOnDefaultbooleanRemove the parameter when the value equals defaulttrue
paramThrottlenumberThrottle URL writes in milliseconds-
paramDebouncenumberDebounce URL writes in milliseconds-

Custom value shapes

Most primitives auto-select a codec from the value type:

  • stringstringCodec
  • booleanbooleanCodec
  • numbernumberCodec
  • string[]stringArrayCodec
  • number[]numberArrayCodec

For custom payloads, pass paramCodec with createJsonCodec.

import { createJsonCodec } from "@tilt-legal/cubitt-components/utilities/hooks";

const filtersCodec = createJsonCodec<MyFilterState>((value) =>
  isValidFilterState(value) ? value : null
);

<SomeCustomComponent paramName="filters" paramCodec={filtersCodec} />;

Custom components

If you're building your own primitives or wrappers, use the shared hooks directly:

On this page