TwilightProvider
The component a theme mounts once around its pages; it shares the store, theme, language and navigation with everything inside it.
import { TwilightProvider, TwilightProviderProps, TwilightConfig } from '@salla.sa/twilight-theme-engine';In plain words
A component is a function that returns what should appear on the page. A provider is a component that wraps other components and makes data available to all of them, however deep they sit.
TwilightProvider is the provider every theme mounts exactly once, in app/routes/__root.tsx, around <Outlet /> (the spot where the current page appears). Everything else in the engine reads from it: the store, the merchant's theme settings, translations, links and navigation, toast messages and hook slots.
You rarely pass it anything. The reference theme passes only translations; the other props switch built-in behaviour off or change it.
Signature
function TwilightProvider(props: TwilightProviderProps): React.ReactElement
interface TwilightProviderProps {
children: ReactNode;
translations?: TranslationMessages; // from 'virtual:twilight/theme-translations'
layout?: ComponentType<LayoutProps> | false; // default: MasterLayout
client?: { framework?: 'tanstack' | 'nextjs'; interceptLinks?: boolean } | false;
// default: { framework: 'tanstack', interceptLinks: true }
toast?: boolean; // default: true
toastMobilePosition?: ToastPosition; // default: 'bottom-center'
addToCartToast?: boolean; // default: true
routeClass?: boolean; // default: true
skeleton?: ReactNode; // shown while isReady is false
onReady?: (data: { store: Store; theme: Theme }) => void;
debug?: boolean; // default: false
onError?: (error: Error) => void; // accepted, never called
gtm?: boolean; // accepted, never read
}
interface TwilightConfig {
debug?: boolean;
}Try it live
// app/routes/__root.tsx
import { HeadContent, Outlet, Scripts } from '@tanstack/react-router';
import { TwilightProvider } from '@salla.sa/twilight-theme-engine';
import {
createTwilightRootRoute,
getTwilightContext,
} from '@salla.sa/twilight-theme-engine/tanstack';
import themeTranslations from 'virtual:twilight/theme-translations';
export const Route = createTwilightRootRoute()({ shellComponent: RootComponent });
function RootComponent() {
const ctx = getTwilightContext();
return (
<html lang={ctx.locale} dir={ctx.dir} suppressHydrationWarning>
<head>
<HeadContent />
</head>
<body suppressHydrationWarning>
<TwilightProvider translations={themeTranslations}>
<Outlet />
</TwilightProvider>
<Scripts />
</body>
</html>
);
}
Example
import { HeadContent, Outlet, Scripts } from '@tanstack/react-router';
import { TwilightProvider } from '@salla.sa/twilight-theme-engine';
import {
createTwilightRootRoute,
getTwilightContext,
} from '@salla.sa/twilight-theme-engine/tanstack';
import themeTranslations from 'virtual:twilight/theme-translations';
export const Route = createTwilightRootRoute()({ shellComponent: RootComponent });
function RootComponent() {
const ctx = getTwilightContext();
return (
<html lang={ctx.locale} dir={ctx.dir} suppressHydrationWarning>
<head>
<HeadContent />
</head>
<body suppressHydrationWarning>
<TwilightProvider translations={themeTranslations}>
<Outlet />
</TwilightProvider>
<Scripts />
</body>
</html>
);
}
How it behaves
What it renders, outermost first:
DocumentClassProvider, the twilight context,I18nProvider, the theme and route body-class syncs; then, onceisReady, the framework navigation setup (NavigationProvider+LinkProvider) aroundWidgetHead,AppsSnippets,HookSlotbody:start,Toaster,NavigationInterceptor, yourlayoutwith the page inside, andHookSlotbody:end.Store data never arrives through props.
twilightMiddleware()(inapp/start.ts) andcreateTwilightRootRoute()load the settings for the request, and the provider reads them fromgetTwilightContext()on its first render. With settings present,isReadyis alreadytruein the server render.With no settings,
isReadystartsfalseand the provider renders only<div className="loading-overlay">holdingskeleton(or a.loading-spinner) untilSalla.onReady()resolves.In the browser it calls
Salla.init()with the settings and the page, awaitsSalla.onReady(), dispatches atheme::readyevent ondocument, then callsonReady({ store, theme }). This runs again only if the initial locale changes.Body classes it keeps in sync:
salla-<theme name>,color-mode-lightorcolor-mode-dark,rtlorltr,preview-mode,font-<name>,footer-is-darkorfooter-is-light,topnav-is-dark,is-sticky-product-bar, pluslanganddiron<html>. WithrouteClass, a per-page class is added too, such ascartorproduct-single.It follows the Salla SDK login and logout events, keeps
useTwilight().authTokencurrent, and mirrors a fresh token into thetokencookie (30 days,SameSite=Lax) so the next server render is signed in.client.frameworkpicks a code-split navigation setup (TanStack by default), androuteDocumentSyncs[framework]the route class sync.client={false}mounts neither.The same component is also exported from
@salla.sa/twilight-theme-engine/providers.
Gotchas
Mount it once. A second provider brings a second
DocumentClassProvider; both write to the same<body>and share itsdata-de-managedrecord, so each removes the classes the other added (syncAttrsToElementin src/utils/document-class.ts), and the SDK would be initialised twice. Put per-section wrappers inlayoutor in your routes instead.onError,gtmanddebugdo nothing you can see.onErroris handed on as_onErrorand never called (src/providers/twilight-init.ts);gtmis never read, because Google Tag Manager is injected by a defaultbody:starthook handler;debugonly appears asuseTwilight().config.debug. docs/02-theme-engine-core.md callsdebugverbose engine logging.Nothing inside
skeletoncan use the engineLinkor a workinguseNavigate(): the overlay renders before the navigation setup mounts, soLinkthrowsLinkProviderErrorthere anduseNavigate()falls back to a full page load.docs/16-client-navigation.md and docs/20-document-class.md pass a
storeIdentifierprop in their examples. The prop does not exist; the store comes from the request.
Related
Reads everything TwilightProvider knows: store, theme, settings, language, direction, current page, login token and the Salla SDK.
useDocumentClassOutputReturns the merged html and body attributes every useDocumentClass() call has registered, as React props ready to spread.
LinkProviderTells the engine's Link which real anchor component to render; TwilightProvider supplies TanStack Router's, and tests can supply a plain one.