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

mergeAttrs

functionAdvancedlive demo

Merges several { html, body } descriptors into React props: every class is kept once, and for other attributes the last value wins.

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

In plain words

When attributes come from more than one place, a layout and a page for example, mergeAttrs(layout, page) combines them in order. Classes add up. For any other attribute, such as data-theme, the later descriptor's value replaces the earlier one.

The result has the shape attrs() returns: htmlAttrs and bodyAttrs, with class renamed to className.

Signature

function mergeAttrs(...descriptors: DocumentElementDescriptor[]): {
  htmlAttrs: Record<string, string>;
  bodyAttrs: Record<string, string>;
}

Try it live

Two descriptors merged: classes from both are kept once each, and for every other attribute the later descriptor wins.Try this: clear the page's data-theme: the layout's value survives, because an empty string never overrides.
Storefront canvas · ar · RTL
mergeAttrs(layout, page): {…} 2 keys
bodyAttrs: {…} 2 keys
data-theme: "dark"
className: "antialiased font-sans page-cart"
htmlAttrs: {}
Controls
What a theme writes
import { mergeAttrs } from '@salla.sa/twilight-theme-engine/tanstack';

const { bodyAttrs } = mergeAttrs(
  { body: { class: 'antialiased font-sans', 'data-theme': 'light' } },
  { body: { class: 'page-cart antialiased', 'data-theme': 'dark' } }
);

Example

app/lib/document-attrs.ts
import { mergeAttrs } from '@salla.sa/twilight-theme-engine/tanstack';

const layout = {
  html: { class: 'antialiased' },
  body: { class: 'font-sans', 'data-theme': 'light' },
};
const offersPage = { body: { class: 'page-offers', 'data-theme': 'dark' } };

export const { htmlAttrs, bodyAttrs } = mergeAttrs(layout, offersPage);
// htmlAttrs → { className: 'antialiased' }
// bodyAttrs → { 'data-theme': 'dark', className: 'font-sans page-offers' }

How it behaves

  • Descriptors are read left to right. Classes are collected in the order first seen; any other attribute takes the last non-empty string.

  • It is mergeDocumentDescriptors from /utils under a shorter name. /nextjs exports the same function as mergeAttrs.

Gotchas

  • A later descriptor cannot remove an attribute: an empty string or undefined is skipped, so the earlier value stays. Leave the earlier descriptor out of the call instead.

Related

Source and docs