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

useDocumentClassOutput

hookAdvancedbrowserlive demo

Returns the merged html and body attributes every useDocumentClass() call has registered, as React props ready to spread.

import { useDocumentClassOutput } from '@salla.sa/twilight-theme-engine/providers';

In plain words

Components can ask for classes and attributes on <body> and <html> with useDocumentClass(). The provider merges all those requests, and useDocumentClassOutput() gives you the merged result as two objects of React props (className instead of class).

Signature

function useDocumentClassOutput(): {
  bodyAttrs: Record<string, string>;   // { className: 'salla-raed color-mode-light rtl …' }
  htmlAttrs: Record<string, string>;   // { lang: 'ar', dir: 'rtl' }
}

Try it live

The merged html and body attributes every useDocumentClass() call on this page has registered, as React props.Try this: turn the marker on: its class joins the theme classes in bodyAttrs.className, and data-playground-demo appears. Turn it off and both go.
Storefront canvas · en · LTR
Runs in the browser…
Controls
mount a component that calls useDocumentClass
What a theme writes
import { useDocumentClass } from '@salla.sa/twilight-theme-engine/hooks/useDocumentClass';
import { useDocumentClassOutput } from '@salla.sa/twilight-theme-engine/providers';

// A page asks for a body class while it is mounted.
export function ProductPage() {
  useDocumentClass({ body: { class: 'playground-demo' } });
  return <main></main>;
}

// Anything below TwilightProvider can read the merged result.
export function BodyClassDebug() {
  const { bodyAttrs } = useDocumentClassOutput();
  return <pre>{bodyAttrs.className}</pre>;
}

Example

app/components/BodyClassDebug.tsx
import { useDocumentClassOutput } from '@salla.sa/twilight-theme-engine/providers';

export function BodyClassDebug() {
  const { bodyAttrs, htmlAttrs } = useDocumentClassOutput();
  return <pre>{JSON.stringify({ bodyAttrs, htmlAttrs }, null, 2)}</pre>;
}

How it behaves

  • Merge rules: every class value is split, joined and de-duplicated; any other attribute takes the value of the last registration; empty strings are skipped.

  • It subscribes to the provider's store (useSyncExternalStore), so it renders again whenever a registration changes. With no DocumentClassProvider above it, both objects are empty.

  • The provider applies the attributes to the real <body> and <html> itself, in an effect; this hook only reads them.

Gotchas

  • Its result is empty in the server render. useDocumentClass() registers in an effect, and effects never run on the server, so spreading the result onto <html> or <body> adds nothing to the HTML; docs/20-document-class.md says this hook "makes SSR work". For attributes the server must send, set lang and dir from getTwilightContext() as the reference theme does, or use route staticData.documentAttrs with useAttrs() from /tanstack.

  • Because the server and the browser disagree, render its output only after hydration.

Related

Source and docs