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

HookSlot

componentBeginnerserverbrowserlive demo

A named empty place in the page that renders every handler registered under its name, plus a spot where Salla apps inject content.

import { HookSlot, HookSlotProps, HookContext } from '@salla.sa/twilight-theme-engine/hooks/HookSlot';

In plain words

Engine pages leave named empty places in their layout, such as header:start or cart:summary.end. Each one is a <HookSlot name="…" />. Anything registered under that name, with hookRegistry.register, appears there, highest priority first. You can put slots in your own components too.

A component is a function that returns what should appear on the page, and its props are the attributes you pass to it (name, context…). The slot hands each registered function one object: whatever you passed as context, plus twilight, the engine's store, theme and language data.

Salla marketplace apps can also add their own blocks at the same name. See the recipe Add content to a slot.

Signature

function HookSlot(props: HookSlotProps): JSX.Element | null

interface HookSlotProps {
  name: HookName | string;
  context?: Record<string, unknown>;  // merged into what each handler receives
  fallback?: React.ReactNode;         // shown when no handler is registered
  ssr?: boolean;                      // default false: <salla-hook> only after mount
}

interface HookContext {
  twilight: TwilightContextValue;     // store, theme, locale, settings, salla…
  [key: string]: unknown;             // your context
}

Try it live

A HookSlot with one handler. The handler reads the slot context (badge) and the engine context (twilight).Try this: clear the badge text: the handler returns null, so the slot is empty, yet the fallback stays hidden because a handler is registered.
Storefront canvas · en · LTR
No handler for this slot
Controls
Handler registered
What a theme writes
import { HookSlot } from '@salla.sa/twilight-theme-engine/hooks/HookSlot';

export function PromoArea() {
  return (
    <HookSlot
      name="playground:hook-slot"
      context={{ badge: 'Free delivery' }}
      fallback={<em>{'No handler for this slot'}</em>}
    />
  );
}

Example

app/components/product/ProductExtras.tsx
import { HookSlot, type HookContext } from '@salla.sa/twilight-theme-engine/hooks/HookSlot';
import { hookRegistry } from '@salla.sa/twilight-theme-engine/hooks/HookRegistry';
import type { Product } from '@salla.sa/twilight-theme-engine/types';

interface ExtrasContext extends HookContext {
  product?: Product;
}

// Usually in app/hooks/index.tsx: registered once, when the theme starts.
hookRegistry.register('theme:product.extras', ({ product, twilight }: ExtrasContext) =>
  product?.is_on_sale ? <p className="badge">On sale at {twilight.store.name}</p> : null
);

export function ProductExtras({ product }: { product: Product }) {
  return <HookSlot name="theme:product.extras" context={{ product }} />;
}

How it behaves

  • Each handler renders in its own small component, keyed by its registry id, and receives { ...context, twilight: useTwilight() }. Because it is called while that component renders, a handler may call React hooks, always in the same order.

  • A handler returning null or undefined renders nothing. A handler that throws is caught and skipped.

  • The slot subscribes to the registry, so a handler registered after the slot mounted appears without its parent re-rendering, and other slots do not re-render.

  • It also renders Salla's <salla-hook name> element, where marketplace apps inject content. That element is added only after hydration unless ssr is set, because apps change its DOM before React takes over. The engine sets ssr only on head:start, head and head:end, where nothing is injected.

  • The component's JSDoc asks themes to add salla-hook{all:unset;display:none} to their global CSS.

  • Every handler runs again whenever the slot re-renders: context is rebuilt each time. Keep handlers cheap.

  • Also exported from @salla.sa/twilight-theme-engine/hooks, the import the reference theme uses.

Gotchas

  • fallback shows only when no handler is registered. A handler that returns null still counts, so the fallback stays hidden and the slot is empty.

  • Only an error thrown by the handler function itself is caught. If it returns an element whose component throws while rendering, the error goes to the nearest React error boundary and can take the page section down. The caught errors are logged only when NODE_ENV is development, through window.Salla.logger.

  • A key named twilight in your context is overwritten: twilight is added after your context.

  • It calls useTwilight(), so rendering a slot outside TwilightProvider throws.

Related

Source and docs