Add content to every page with a hook slot
Hang an announcement at header:start without copying or editing an engine page.
Goal: show an announcement at the top of every page, without copying or editing any engine page.
Mechanism: a hook slot. The engine leaves named, empty places in its pages and layout (header:start, product:single.form.end, cart:summary.start…). A theme registers a small function, a handler, for one of those names, and whatever the handler returns appears in that place on every page that has it.
1. Try it
The demo hangs content on a slot of the playground's own. Register the same text twice, then change the priority.
import { hookRegistry, HookName } from '@salla.sa/twilight-theme-engine/hooks';
// app/hooks/index.tsx: register once, when the theme starts.
hookRegistry.register(
HookName.HEADER_START,
() => <p className="announcement">{'Free delivery over 200 SAR'}</p>,
);
2. Register the handler
Keep every registration in one file. The handler receives a context object: twilight (the store, theme and settings) plus whatever the slot passes, such as product on the product slots. Returning null renders nothing, which is how content follows a merchant setting.
import { hookRegistry, HookName, type HookContext } from '@salla.sa/twilight-theme-engine/hooks';
let registered = false;
export function registerThemeHooks() {
// register() appends: running this twice would render every handler twice.
if (registered) return;
registered = true;
hookRegistry.register(HookName.HEADER_START, (context: HookContext) => {
// `twilight` carries the store, theme and settings of the page being drawn.
const settings = (context.twilight.theme?.settings ?? {}) as Record<string, unknown>;
// A setting of your own from twilight.json: switched off, the slot stays empty.
if (settings.show_announcement === false) return null;
return (
<p className="bg-primary py-2 text-center text-sm text-white">Free delivery over 200 SAR</p>
);
});
}
In engine terms
hookRegistry.register(name, handler, priority = 50) pushes a definition and sorts the list by priority, highest first (src/hooks/HookRegistry.ts). <HookSlot name> calls every handler with { ...context, twilight: useTwilight() } and subscribes to the registry, so a later registration re-renders the slot. HookContext types twilight; your own settings are not in ThemeSettings, hence the cast to Record<string, unknown>.
3. Call it once, when the theme starts
import { createRouter } from '@salla.sa/twilight-theme-engine/tanstack';
import { routeTree } from './routeTree.gen';
import { registerThemeHooks } from './hooks';
// Module scope: this file runs on the server and in the browser, before any page renders.
registerThemeHooks();
export function getRouter() {
return createRouter(routeTree);
}
Traps
Go deeper: hookRegistry, HookSlot, HookName, and the concept How hook slots render.