attrs
Turns an { html, body } attribute descriptor into React props to spread onto the html and body elements: class becomes className.
import { attrs } from '@salla.sa/twilight-theme-engine/tanstack';In plain words
Some attributes belong on the page's outer elements: lang and dir on <html>, a page class on <body>. The engine describes them as { html: {…}, body: {…} }, with HTML attribute names such as class.
React needs className instead of class. attrs(descriptor) returns two ready objects, htmlAttrs and bodyAttrs, to spread with {...htmlAttrs}. It is a plain function: it reads nothing and changes nothing on the page.
Signature
function attrs(descriptor: DocumentElementDescriptor): {
htmlAttrs: Record<string, string>;
bodyAttrs: Record<string, string>;
}
// Types from @salla.sa/twilight-theme-engine/utils
interface DocumentElementDescriptor {
html?: DocumentElementAttrs;
body?: DocumentElementAttrs;
}
interface DocumentElementAttrs {
class?: string | string[];
[attribute: string]: string | string[] | undefined;
}Try it live
{ html, body } descriptor turned into props you can spread onto <html> and <body>.Try this: repeat a class in body.class: it appears once in className. Then clear data-page: an empty value is dropped.attrs(descriptor): {…} 2 keys
bodyAttrs: {…} 2 keys
import { attrs } from '@salla.sa/twilight-theme-engine/tanstack';
const { htmlAttrs, bodyAttrs } = attrs({
body: { class: 'page-offers sale sale', 'data-page': 'offers' },
});
// <html {...htmlAttrs}> and <body {...bodyAttrs}> in a root shell.
Example
import { HeadContent, Outlet, Scripts } from '@tanstack/react-router';
import { TwilightProvider } from '@salla.sa/twilight-theme-engine';
import { attrs, getTwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';
function RootComponent() {
const ctx = getTwilightContext();
const { htmlAttrs, bodyAttrs } = attrs({
html: { lang: ctx.locale, dir: ctx.dir },
body: { class: ['theme-raed', 'antialiased'] },
});
return (
<html {...htmlAttrs} suppressHydrationWarning>
<head>
<HeadContent />
</head>
<body {...bodyAttrs} suppressHydrationWarning>
<TwilightProvider>
<Outlet />
</TwilightProvider>
<Scripts />
</body>
</html>
);
}
How it behaves
classtakes a string or an array. Values are split on whitespace, repeated classes are kept once, and the result is joined with single spaces asclassName.Every other key keeps its name and value, unless the value is an empty string or an array, which are dropped.
It applies the same rules as
mergeAttrs(),useAttrs()andattrsfrom/nextjs.
Gotchas
Only
classis renamed. Other names keep their HTML spelling, sotabindexreaches React as a lowercase prop and React warns in development. Use names that are the same in both:lang,dir,id,data-*andaria-*.An empty string cannot set an attribute:
{ 'data-sale': '' }is dropped. Pass a value such as'true'.
Related
Merges several { html, body } descriptors into React props: every class is kept once, and for other attributes the last value wins.
useAttrsCollects the html and body attributes the matched routes declare in staticData.documentAttrs, as React props for the root shell.
useDocumentClassAdds classes and attributes to the page <html> and <body> while the calling component is mounted, merged with everyone else.
useDocumentClassOutputReturns the merged html and body attributes every useDocumentClass() call has registered, as React props ready to spread.