

Use these hooks when you're building custom components and want them to behave exactly like Cubitt's built-in URL state props.

<Callout type="info">
  Many Cubitt components already support URL state out of the box. Start with the
  [URL state guide](/guides/url-state) if you're using the built-in components.
</Callout>

## Hooks [#hooks]

<Cards columns="2">
  <Card title="useUrlOpen" href="/hooks/url-state/use-url-open">
    Read-only open/close state for dialogs, sheets, alert dialogs, and popovers.
  </Card>

  <Card title="useUrlTrigger" href="/hooks/url-state/use-url-trigger">
    Write-only URL updates for buttons, menu items, and other triggers.
  </Card>

  <Card title="useUrlState" href="/hooks/url-state/use-url-state">
    Two-way sync for inputs, toggles, tabs, pagination, and other value controls.
  </Card>

  <Card title="useUrlControlledString" href="/hooks/url-state/use-url-controlled-string">
    Controlled string inputs that mirror URL state without losing local control.
  </Card>
</Cards>

## Router setup [#router-setup]

These hooks must run inside TanStack Router context.

* **TanStack Start / file-based routing** usually generates the router registration for you.
* **Manual TanStack Router setup** should still use the standard `Register` declaration once so `paramName` narrows to known search keys.

With registration in place, the hook layer can:

* autocomplete valid `paramName` keys
* reject invalid keys at compile time
* keep URL-state usages aligned when route search keys change

If your app only sees `paramName` as plain `string`, verify that your router registration is present and that the generated route types are up to date.

## Building your own components [#building-your-own-components]

The hooks are designed to align with shared prop types so your components stay consistent with Cubitt:

```tsx
import type {
  UrlOpenComponentProps,
  UrlTriggerComponentProps,
  UrlStateComponentProps,
} from "@tilt-legal/cubitt-components/utilities/hooks";
```

Pick the hook that matches your component's behavior and pass through the URL props.

## Validation model [#validation-model]

Cubitt URL-state hooks intentionally split responsibilities:

* **Cubitt codecs** parse and serialize the single URL param the hook owns.
* **Your route `validateSearch`** owns full-object validation, transforms, and defaults.

That keeps the hooks route-agnostic while still allowing strong typing and app-level search validation.

## Codecs [#codecs]

Custom codecs for use with `paramCodec` on any URL-state hook.

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

### booleanOrIndeterminateCodec [#booleanorindeterminatecodec]

Parses a URL param as `boolean | "indeterminate"`. Useful for checkbox tri-state values that need more than the default boolean codec.

```tsx
<Checkbox paramName="agree" paramCodec={booleanOrIndeterminateCodec} />
```

### createJsonCodec [#createjsoncodec]

Builds a JSON codec with a validator function. Use this for complex objects or arrays in URL params.

```tsx
const myCodec = createJsonCodec<MyFilter>((value) =>
  isValidFilter(value) ? value : null
);
```

If your app uses a custom TanStack Router serializer that leaves complex values as raw strings, opt in explicitly with `parseRawString` so the codec can deserialize those strings before validation:

```tsx
const myCodec = createJsonCodec<MyFilter>(
  (value) => (isValidFilter(value) ? value : null),
  {
    parseRawString: customParseSearchValue,
  }
);
```
