Page frame slots

Slots that wrap every page - the head, body, header and footer.

Use these slots when the content belongs on every page, such as an announcement bar or a tracking snippet.

Where they sit

any page
body:startabove the header
Header
header:startfirst thing in the header
Announcement bars (PageAdvertisements)
Top bar: menu, language, currency
Logo
Main menu and search
Account and cart
header:endlast thing in the header
The current page (a route)
Footer
footer:startfirst thing in the footer
Logo, description, contacts, social links and trust badges
Footer menu
Apps
copyrightdefault: the copyright line
Payment methods
footer:endlast thing in the footer
body:endbelow the footer
Slot: add your content hereDrawn by the engine
Page frame: the slots every page has

The three head slots are not drawn, because they output nothing visible.

The head:* slots are not inside the document <head> tag. They render at the top of the app, before body:start, which is why they exist mostly for installed apps' elements. To change the real <head> (title, meta, links), use the route head option instead.

Slot reference

SlotWhereContextNotes
head:startTop of the app, first of the three head slotstwilightServer-rendered (ssr).
headBetween head:start and head:endtwilightServer-rendered (ssr).
head:endLast of the head slotstwilightServer-rendered (ssr).
body:startAfter the head slots, before the layouttwilightThe engine registers defaults here: Google Tag Manager, fraud-check snippet and the store-closed notice (priority 100, 90, 80).
header:startFirst child inside <header class="store-header">, above the merchant's announcement bars and the top bartwilightGood for a bar of your own.
header:endLast child inside <header>, after the mobile menutwilight
footer:startFirst child inside <footer class="store-footer">, above the footer columnstwilightNot rendered when the footer has inSheet.
copyrightIn the footer's bottom row, before the payment iconsstoreName, twilightShows the default copyright line while nothing is registered. Any handler replaces that line.
footer:endLast child inside <footer>, after the bottom rowtwilightNot rendered when the footer has inSheet.
body:endAfter the layout, at the end of the apptwilightThe engine registers defaults here: its tracking bundles and the fast-checkout script (priority 100, 90).

header:*, footer:* and copyright are rendered by the engine's Header and Footer components. A layout that replaces them must render these slots itself (the header and footer page explains the swap). body:start and body:end belong to the provider and keep working.

Example: announcement bar

Register it once, in the module your app loads at startup (see Slots):

app/hooks/index.tsx
import { hookRegistry, type HookContext } from '@salla.sa/twilight-theme-engine/hooks';

// Announcement bar above the header content
hookRegistry.register('header:start', ({ twilight }: HookContext) => {
  const code = twilight.currency?.code;
  return (
    <div className="bg-black text-white text-center text-sm py-2">
      Free shipping on orders over 200 {code}
    </div>
  );
});

Next

On this page