useAttrs
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
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
classthe 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.documentAttrsis typed by the engine's addition to TanStack'sStaticDataRouteOption, available once your program imports/tanstack.TwilightProviderdoes not readdocumentAttrs. Its own page class (cart,product-single…) comes from a built-in route-id map and is applied in the browser. DeclaringdocumentAttrsdoes nothing until your root shell spreadsuseAttrs().
Gotchas
Neither the reference theme nor this playground spreads it on
<body>, and doing so makes React own that element's wholeclassattribute. When a navigation changesbodyAttrs.className, React rewrites the attribute and removes the classesTwilightProvideradded 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, calluseDocumentClass()in the page component.docs/20-document-class.md imports a
RouteStaticDatatype from/tanstack. No such export exists, and none is needed:staticDatais already typed.
Related
Turns an { html, body } attribute descriptor into React props to spread onto the html and body elements: class becomes className.
mergeAttrsMerges several { html, body } descriptors into React props: every class is kept once, and for other attributes the last value wins.
useDocumentClassAdds classes and attributes to the page <html> and <body> while the calling component is mounted, merged with everyone else.
routeDocumentSyncs, registerDocumentSyncThe per-framework table of components that add route-based body classes, and the function that adds a framework to it.