Skip to content
Twilight React Playground
ثيم رائدaren

useAttrs

hookAdvancedserverbrowser

Collects the html and body attributes the matched routes declare in staticData.documentAttrs, as React props for the root shell.

import { useAttrs } from '@salla.sa/twilight-theme-engine/tanstack';

In plain words

A route can declare attributes for the page's outer elements in its options: staticData: { documentAttrs: { body: { class: 'page-faq' } } }. staticData is TanStack's place for fixed values attached to a route.

useAttrs() takes every route matched for the current address, from the root down, merges what they declare and returns htmlAttrs and bodyAttrs to spread in the root shell. It runs while rendering, so the attributes are in the server's HTML.

Signature

function useAttrs(): {
  htmlAttrs: Record<string, string>;
  bodyAttrs: Record<string, string>;
}

// Added by the engine to TanStack's StaticDataRouteOption:
documentAttrs?: DocumentElementDescriptor;

Example

app/routes/faq.tsx and app/routes/__root.tsx (excerpts)
// app/routes/faq.tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/{-$locale}/faq')({
  staticData: { documentAttrs: { body: { class: 'page-faq', 'data-page': 'faq' } } },
  component: FaqPage,
});

// app/routes/__root.tsx
import { useAttrs } from '@salla.sa/twilight-theme-engine/tanstack';

function RootComponent() {
  const { bodyAttrs } = useAttrs();
  return (
    <html>
      <body {...bodyAttrs}>{/* … */}</body>
    </html>
  );
}

How it behaves

  • Matches are merged root first, so for attributes other than class the deepest route wins, and classes from every level are kept.

  • It renders again when the matched routes change (useRouterState), and must render inside the router.

  • staticData.documentAttrs is typed by the engine's addition to TanStack's StaticDataRouteOption, available once your program imports /tanstack.

  • TwilightProvider does not read documentAttrs. Its own page class (cart, product-single…) comes from a built-in route-id map and is applied in the browser. Declaring documentAttrs does nothing until your root shell spreads useAttrs().

Gotchas

  • Neither the reference theme nor this playground spreads it on <body>, and doing so makes React own that element's whole class attribute. When a navigation changes bodyAttrs.className, React rewrites the attribute and removes the classes TwilightProvider added in the browser (rtl, salla-<theme>, the font and color-mode classes). The provider applies them again only when its own registrations change, which does not happen between two routes outside its route-class map. For per-page classes, call useDocumentClass() in the page component.

  • docs/20-document-class.md imports a RouteStaticData type from /tanstack. No such export exists, and none is needed: staticData is already typed.

Related

Source and docs