---
title: Hooks
description: Read consent permissions, open the preference dialog and save
  choices in TanStack Start components with the hooks exported from
  c15t/tanstack-start.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Read only the state you need

`c15t/tanstack-start` re-exports every hook from `c15t/react`. Each hook reads
the runtime from `ConsentRoot`, so call hooks in components rendered inside it.
Any route component qualifies, because `ConsentRoot` wraps the `Outlet` in the
root route.

```tsx title="src/components/optional-feature.tsx"
import { useConsent } from 'c15t/tanstack-start';

export function OptionalFeature() {
	const allowed = useConsent('marketing');
	return allowed ? <div>Optional marketing content</div> : null;
}
```

On the server, hooks read the state the root loader resolved, so a route
renders the same result in the server HTML and in the first browser render.

Call hooks inside the consent provider. `useConsent` reads effective permission,
which can be true under an opt-out policy without a recorded grant.

|Need|Hook|
|--|--|
|One category's current permission|`useConsent(category)`|
|All current permissions|`useConsents()`|
|Recorded per-category choices|`useExplicitChoice()`|
|Whether one declared vendor may run|`useVendorAllowed(id)`|
|Whether a prompt is required|`usePromptRequirement()`|
|Active policy and resolution|`usePolicyRule()`, `usePolicyResolution()`|
|Save an explicit choice|`useSaveConsents()`|
|Open or close a consent surface|`useSetActiveUI()`|
|Acknowledge a notice|`useDismissNotice()`|

## Open the preference dialog

Set the active surface to `'dialog'` from any component inside `ConsentRoot`:

```tsx title="src/components/privacy-menu-item.tsx"
import { useSetActiveUI } from 'c15t/tanstack-start';

export function PrivacyMenuItem() {
	const setActiveUI = useSetActiveUI();
	return (
		<button type="button" onClick={() => setActiveUI('dialog')}>
			Cookie preferences
		</button>
	);
}
```

`ConsentDialogLink` does the same without a hook.

## Save from a visitor action

```tsx title="src/components/reject-button.tsx"
import { useSaveConsents } from 'c15t/tanstack-start';

export function RejectButton() {
	const save = useSaveConsents();
	return (
		<button type="button" onClick={() => void save('none')}>
			Reject optional
		</button>
	);
}
```

This is an isolated action example, not a complete policy-aware banner. Use the
stock UI or headless surface actions for a complete prompt. Do not call save on
mount to persist a state derived from permissions.

Use focused hooks for feature gates. For a complete custom prompt or preference
center, use `useHeadlessConsentUI()` for surface actions and `useConsentDraft()`
for staged choices, inside one `ConsentDraftProvider` so the save commits the
same draft. v3 has no `useConsentManager()`; the
[`use-consent-manager-to-hooks` codemod](/docs/cli/commands/codemods#migrate-useconsentmanager-to-v3-hooks)
rewrites most of its calls to hooks. Read [how consent works](/docs/concepts/how-consent-works#a-permission-is-not-a-recorded-choice) before
treating a callback as evidence of a choice.

## Where hooks do not work

Hooks need React context, so they do not run in loaders, `beforeLoad`, server
functions or server routes. On the server, read the request's consent state
with `resolveConsent` from `c15t/tanstack-start/server`.
