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

How hook slots render

Beginner7 min

What happens between hookRegistry.register and the content appearing: priority order, the merged context, subscriptions and apps.

A hook slot is a named, empty place in a page. Your theme registers functions for a name, and whatever they return appears in that place, without copying the page.

This page follows what happens between hookRegistry.register(…) and the content showing up. For the task itself, see the recipe Add content to every page with a hook slot.

Theme hooks filemodule scopeEngine defaultson import of /hooksuseHook()in an effecthookRegistrysorted by priorityHookSlotname · contextContext{ ...context, twilight }Handlers runhighest priority firstfallbackonly with no handlersalla-hook elementafter mount · apps

1. The engine registers its own handlers

As soon as the hooks module loads, the engine puts a few handlers of its own in the registry: Google Tag Manager, a "store closed" notice and others. Each decides when the page renders whether to show anything.

In engine terms

hooks/index.ts imports registerDefaultHooks.tsx, which registers on body:start (GTM 100, Sift 90, store closed 80), body:end (bundles 100, fast checkout 90), cart:submit.start (price quote) and cart:items.start (pre-order notice). Importing @salla.sa/twilight-theme-engine/hooks is enough: calling registerDefaultHooks() again adds a second copy of each.

Priority, appending and re-rendering, live

Register a few handlers with different priorities. Each registration notifies the slot, which re-renders on its own, and the list stays sorted by priority.

A hook slot on this page, and the registry that fills it. Every handler you register renders.Try this: register the same text twice: registration appends, it never replaces. Then raise the priority of a third one.
Storefront canvas · en · LTR
Runs in the browser…
Controls
Higher runs first. The default is 50.
What a theme writes
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>,
);
app/hooks/index.tsx
import { hookRegistry, HookName, type HookContext } from '@salla.sa/twilight-theme-engine/hooks';
import type { Product } from '@salla.sa/twilight-theme-engine/types';

interface DescriptionContext extends HookContext {
  product?: Product;
}

// app/hooks/index.tsx: run once, when the theme starts.
hookRegistry.register(
  HookName.PRODUCT_DESCRIPTION_END,
  ({ product, twilight }: DescriptionContext) => {
    // The engine product page renders this slot twice; only one copy passes the product.
    if (!product) return null;
    return (
      <p className="delivery-note">
        {twilight.locale === 'ar' ? 'يصل خلال يومين' : 'Arrives in 2 days'}
      </p>
    );
  },
  80 // before handlers at the default priority, 50
);

Why it matters

  • Content appears twice: register ran twice (the registration file runs from two places, or it sits in a component body). On the engine product page, the product:single.description, .start and .end slots are rendered twice, once with product in the context and once without.
  • Content is missing from the page source but appears a moment later: it was registered with useHook or in an effect. Register at module scope for server HTML.
  • Other content disappears when a component unmounts: that component used useHook, whose cleanup clears every handler on the name.
  • Nothing shows for a `HookName`: the engine may never render that slot (search:*, homepage:*, body:inner, head:inner). Render <HookSlot name="…" /> yourself.
  • A marketplace app does not show in a slot of your own: declare the name with registerThemeHookSlots.
Check yourself

On header:start, handler A is registered with priority 10, then handler B with no priority. Which appears first?