# Slots overview (https://docs.salla.dev/twilight/react/slots)



Read this page when you want to add content to a page without forking the component that renders it, or when you need to know which slots exist.

A slot is a named spot in the markup, written `<HookSlot name="header:start" />`. Anything registered under that name appears there: your theme code, the engine's own defaults, and apps installed on the store. If nothing is registered, the slot renders nothing (or its `fallback`).

## Use a slot in three steps [#use-a-slot-in-three-steps]

**Pick the slot.** Find the page in the catalog below and choose a name, for example `product:details.end`. Each page lists where the slot sits and what context its handler receives.

**Register a handler once, at app start.** Use `hookRegistry.register` (or `defineHooks` for several at once) in a module your app imports at start. The starter theme keeps them in `app/hooks/index.tsx` and calls `registerThemeHooks()` from `app/router.tsx`, as in [Add content to a slot](https://docs.salla.dev/twilight/react/recipes/add-content-to-a-slot).

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

hookRegistry.register('header:start', ({ twilight }: HookContext) => (
  <div className="announcement">Free shipping on orders over 200 {twilight.currency?.code}</div>
));
```

Inside a component you can use [`useHook`](https://docs.salla.dev/twilight/react/hooks/use-hook) instead. Read [how its cleanup works](#usehook-removes-only-its-own-handler) first.

**Render.** Nothing else to do: the engine's pages already render the slot. If you replaced a layout or a page, you must render the slot yourself with [`HookSlot`](https://docs.salla.dev/twilight/react/hooks/hook-slot).

## Slot catalog [#slot-catalog]

Names use `area:part` or `area:part.position`. Most areas come in `.start` and `.end` pairs. Each page below says where the slot sits, what context it passes, and shows an example.

| Area              | What it covers                                                                                                                      | Page                                          |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| Page frame        | `body:start`, `body:end`, `head:start`, `head`, `head:end`, `header:start`, `header:end`, `footer:start`, `footer:end`, `copyright` | [Page frame](https://docs.salla.dev/twilight/react/slots/page-frame)               |
| Home and catalog  | Home page, product list, brands, search, not found page                                                                             | [Home and catalog](https://docs.salla.dev/twilight/react/slots/home-and-catalog)   |
| Product page      | Product details, description, form, related products                                                                                | [Product page](https://docs.salla.dev/twilight/react/slots/product-page)           |
| Cart and checkout | Cart, cart summary, submit button, thank-you page                                                                                   | [Cart and checkout](https://docs.salla.dev/twilight/react/slots/cart-and-checkout) |
| Account           | Orders, order details, profile, settings, wallet, wishlist, notifications                                                           | [Account](https://docs.salla.dev/twilight/react/slots/account)                     |
| Blog              | Blog index, article, category, tag, author                                                                                          | [Blog](https://docs.salla.dev/twilight/react/slots/blog)                           |

<Callout type="info" title="No slots inside drawers and modals">
  The engine renders no slot inside a drawer, modal or popup (for example the mini cart or the language switcher). A name the engine never renders does nothing, so check the catalog for the exact name before you register.
</Callout>

## Resolving conflicts and edge cases [#resolving-conflicts-and-edge-cases]

### Order: priority, then registration [#order-priority-then-registration]

`register(name, handler, priority = 50)`. Handlers for one name are sorted by priority, higher first. Handlers with the same priority keep the order they were registered in. Every handler renders, one after another, so several handlers on one slot stack up.

```tsx
hookRegistry.register('footer:end', () => <Second />, 10);
hookRegistry.register('footer:end', () => <First />, 90); // renders first
```

The engine registers its own defaults with priorities 80 to 100 on `body:start` (tracking, store-closed notice), `body:end`, `cart:items.start` and `cart:submit.start`. Use a priority below 80 to render after them.

### `fallback` is shown only when the slot is empty [#fallback-is-shown-only-when-the-slot-is-empty]

A slot can have a `fallback`, which a component passes to `HookSlot` for content to show when nothing is registered. The moment you register any handler for that name, the fallback is replaced, not added to. To keep the default content, render it yourself in your handler.

### `useHook` removes only its own handler [#usehook-removes-only-its-own-handler]

`useHook` registers on mount and, on unmount, removes the handler it registered. Other handlers for the same name stay, including the engine's defaults.

`hookRegistry.register` works the same way: it returns a function that removes the one handler it added.

```tsx
const remove = hookRegistry.register('header:start', () => <Promo />);
remove(); // removes only this handler
```

`hookRegistry.clear(name)` is different: it removes **every** handler for the name. Avoid calling it on a slot the engine fills.

The safe pattern is to register once at startup and never clear:

```tsx title="app/hooks/index.tsx"
import { defineHooks } from '@salla.sa/twilight-theme-engine/hooks';

defineHooks({
  'header:start': () => <Announcement />,
  'footer:end': [
    { handler: () => <Newsletter />, priority: 60 },
    { handler: () => <Badges />, priority: 40 },
  ],
});
```

Content from `useHook` shows only while its component is mounted, and only in the browser: it is never in the server HTML. Use it for content that belongs to one component, and register at startup for content every page should have.

### A handler that throws renders nothing [#a-handler-that-throws-renders-nothing]

If a handler throws, that handler renders nothing and the other handlers still render. The error is logged only in development. Return `null` (or nothing) to render nothing on purpose.

### Context [#context]

A handler receives the context the slot passes, plus `twilight`: the full Twilight context (store, theme, currency and more). If your handler needs data the slot does not pass, read it with the matching hook inside the component you return, such as [`useStore`](https://docs.salla.dev/twilight/react/hooks/use-store). Type the argument with `HookContext`:

```tsx
import type { HookContext } from '@salla.sa/twilight-theme-engine/hooks';

hookRegistry.register('header:start', ({ twilight }: HookContext) => (
  <span>{twilight.store.name}</span>
));
```

### Apps installed on the store [#apps-installed-on-the-store]

Besides your handlers, every slot renders a `<salla-hook name="...">` element where apps installed on the store inject their content.

* It renders on the client only, to avoid a hydration mismatch when an app changes the DOM before React hydrates.
* The engine's `head:*` slots pass `ssr`, so their element is also in the server HTML. Set the `ssr` prop on your own slot only if no app script will change it.
* Add this rule to your global CSS so the element takes no space:

```css title="app/styles/app.css"
salla-hook { all: unset; display: none; }
```

### Your own slot name for apps [#your-own-slot-name-for-apps]

You can render `<HookSlot name="sidebar:top" />` anywhere in your theme. To let installed apps mount into a name of yours, declare it with `registerThemeHookSlots`. The Salla SDK only lets apps mount into names on its allow list, and this call adds your names to it.

```ts title="app/slots.ts"
import { registerThemeHookSlots } from '@salla.sa/twilight-theme-engine/utils';

registerThemeHookSlots('sidebar:top', 'sidebar:bottom');
```

Call it at module scope, so it runs before the page loads. Your own handlers in the registry do not need it.

### You replaced a layout or a page [#you-replaced-a-layout-or-a-page]

The slots live inside the engine's `Header`, `Footer`, pages and `TwilightProvider`. If you swap one for your own component, its slots go with it. Render `<HookSlot />` with the same names in your version so handlers and apps keep working. The page-frame slots `body:start` and `body:end` are rendered by the provider, so they survive a layout swap.

## Next [#next]

* [Page frame slots](https://docs.salla.dev/twilight/react/slots/page-frame) is the best page to start with.
* [`HookSlot`](https://docs.salla.dev/twilight/react/hooks/hook-slot) and [`useHook`](https://docs.salla.dev/twilight/react/hooks/use-hook) document the API.
* [Add content to a slot](https://docs.salla.dev/twilight/react/recipes/add-content-to-a-slot) walks through one end to end.
