TanStack Start Customization
Headless
When to go headless
The pre-built banner and dialog cover most designs through props, slots, and the stylesheet. Go headless when your markup has to be something else entirely: a design system component, a native sheet, or a layout the compound parts cannot express.
Headless code owns the rendered controls, so it also owns the compliance outcome. The hooks hand you the actions a policy requires, the rights it must keep reachable, and diagnostics when your presentation drops one. Render from those lists rather than from a fixed set of buttons, and a site that later adds a notice region keeps working without a code change.
Render your own banner
c15t/tanstack-start/headless re-exports the headless hooks from
c15t/react/headless. They read the runtime from ConsentRoot. Render your
component inside ConsentRoot in place of ConsentBanner, and keep
ConsentDialog and a preferences link:
With an awaited root loader, banner.isVisible is already true on the server
for a visitor who owes a choice, so your banner is in the first HTML like the
stock one. With a streamed loader or a prerendered page it turns true after
hydration.
Check for a policy before rendering other custom surfaces. useModel() returns
null while no policy has resolved and 'none' under a policy that owes no
consent UI.
banner.actionGroups is the resolved layout: reject and accept share a group
at equal prominence, and the rest follow. banner.orderedActions is the same
list flattened. Under a notice the only action is dismiss, and
banner.preferenceControls recommends additional buttons for opening
preferences. Under a notice it contains opt-out, which selects the
"Do not sell or share my data" label. The example renders that button and
an "OK" button. Both controls keep their own command: opening
preferences and dismissing the notice.
The list is a rendering helper. It does not establish that your UI implements all policy rights. Provide disclosure and persistent preferences access.
performAction saves all categories for accept, none for reject, the
current draft for save, records a dismissal for dismiss, and opens the
preference center for customize. banner.diagnostics reports when a host
layout drops a required action or gives equivalent actions different
prominence. Review each diagnostic when configuring custom presentation.
banner.variant, banner.position, and banner.blocking carry the resolved
shape from presentation.prompt, so a headless surface can follow the same
bar, widget, or wall choice the pre-built banner would make, and can trap
focus and lock scroll exactly when blocking is true.