# Add a home block (https://docs.salla.dev/twilight/react/recipes/add-a-home-block)



<Callout title="Framework: TanStack Start">
  Registration below lives in `app/router.tsx`, which is specific to TanStack Start. [See what depends on the framework](https://docs.salla.dev/twilight/react/start/frameworks).
</Callout>

Use this when the home page needs a section the engine does not ship, and you want merchants to place and fill it from the dashboard.

**Goal:** a `brands` section with a title and a list of brands.

Merchants arrange sections from the components declared in `twilight.json` ([Settings](https://docs.salla.dev/twilight/react/guide/settings)). The home loader fetches that list and strips the `home.` prefix from each `path`. The renderer then looks up `home:<path>` in a registry and renders your component with the block's values as a `data` prop. See also [Layout and components](https://docs.salla.dev/twilight/react/guide/layout-and-components).

### Declare the block [#declare-the-block]

The `path` is `home.` plus your key. Each field `id` becomes a key on `data`. The `brands` field below is a picker the merchant fills with their brands.

```json title="twilight.json"
{
  "components": [
    {
      "key": "6b0f2c2e-0a4e-4c1b-9d57-3d2f8a7e1c10",
      "title": { "en": "Brands", "ar": "الماركات التجارية" },
      "icon": "sicon-award-ribbon",
      "path": "home.brands",
      "fields": [
        { "type": "string", "id": "title", "label": "Title", "multilanguage": true, "format": "text" },
        {
          "type": "items",
          "id": "brands",
          "label": "Brands",
          "format": "dropdown-list",
          "source": "brands",
          "multichoice": true,
          "searchable": true,
          "required": true,
          "selected": [],
          "options": []
        }
      ]
    }
  ]
}
```

### Write the component [#write-the-component]

It receives `{ data }`.

```tsx title="app/components/home/Brands.tsx"
import { memo } from 'react';
import type { Brand } from '@salla.sa/twilight-theme-engine/types';
import { Link } from '@salla.sa/twilight-theme-engine/common';

export const Brands = memo(function Brands({ data }: { data: { brands: Brand[]; title?: string } }) {
  if (!data.brands.length) return null;
  return (
    <div className="container">
      {data.title && <h2>{data.title}</h2>}
      {data.brands.map((b) => (
        <Link key={b.id} to={b.url}>{b.name}</Link>
      ))}
    </div>
  );
});
```

### Register it [#register-it]

Register it next to the engine's defaults. The optional config reserves height and a class on the wrapper section so the page does not jump while the block loads.

```tsx title="app/router.tsx"
import {
  registerHomeComponents,
  registerHomeComponentConfig,
  DefaultHomeComponents,
} from '@salla.sa/twilight-theme-engine/routes/home';
import { Brands } from './components/home/Brands';

registerHomeComponents({
  ...DefaultHomeComponents,
  brands: Brands,
});

registerHomeComponentConfig({
  brands: { height: '300px', className: 's-block s-block--brands' },
});
```

## Check it worked [#check-it-worked]

Add the block in the theme editor, pick a few brands, and open the home page: your list renders. With no block placed, nothing changes.

<Callout type="warn" title="Traps">
  * **Omit `...DefaultHomeComponents` and every built-in block is gone.** Nothing else registers the engine's blocks, so only what you pass is available. That includes `latest-products`, which a home page with no blocks shows.
  * **The key is `path` minus `home.`.** `home.brands` registers as `brands`. A mismatch shows a yellow "Unknown component" box in development and nothing in production.
  * **`view_style` wins.** For a block with `view_style: "style2"` the renderer tries `featured-products:style2` before `featured-products`. To replace a styled block, register the `:styleN` keys too (the reference theme does).
  * **Config replaces, not merges.** A theme entry in `registerHomeComponentConfig` replaces the engine's entry for that path entirely.
  * **Register at module scope** in `app/router.tsx`, not inside a component, so the server and the browser agree.
  * **Blocks mount when scrolled into view**, except the first three, which render immediately. Do not rely on effects running at page load for later blocks.
</Callout>

## A home page with no blocks [#a-home-page-with-no-blocks]

When the merchant has placed no blocks, the engine's `HomePage` shows the store's latest products instead, as Twig does. It renders a `latest-products` block in a `s-block s-block--latest-products container mt-6 sm:mt-8` section: the `blocks.home.latest_products` title over a grid of product cards that loads more as the customer scrolls. The theme editor's action bar leaves it alone, because the merchant cannot edit or move it.

To draw it your way, register your own component under `latest-products`. Its `data` holds no merchant fields, only `path`, `position` (1) and `priority` (true).

```tsx title="app/router.tsx"
registerHomeComponents({
  ...DefaultHomeComponents,
  'latest-products': MyLatestProducts,
});
```

## The "order again" row [#the-order-again-row]

One row on the home page is not a block: `ReorderProducts`, from `@salla.sa/twilight-theme-engine/components/home`. The engine's `HomePage` renders it after the blocks and the `home:end` slot. It shows only to a signed-in customer, and only when the store's `features` include `reorder` (the merchant turned Reorder on). It lists the products that customer ordered before, drawn with the `products-slider` component you registered, titled with the `pages.orders.repurchase` translation and linking to `/products?source=reorder`. Replacing `products-slider` therefore changes this row too.

Inside the row, each add button reads "order again" and adds the product as the customer last ordered it. `useReorderButtons(enabled)` does the same for your own markup: call it, and put the products inside an element with `data-source="reorder"`.

```tsx title="app/components/product/ReorderGrid.tsx"
import type { ReactNode } from 'react';
import { useReorderButtons } from '@salla.sa/twilight-theme-engine/components/home';

export function ReorderGrid({ children }: { children: ReactNode }) {
  useReorderButtons(true);
  return <div data-source="reorder">{children}</div>;
}
```

The Salla SDK cannot remove a hook, so the button change is registered once and stays until the next full page load.

## Next [#next]

* [Home route](https://docs.salla.dev/twilight/react/routes/home): how the page loads its blocks.
* [Replace the product card](https://docs.salla.dev/twilight/react/recipes/replace-the-product-card): product lists inside blocks use the same card.
* [Add translation keys](https://docs.salla.dev/twilight/react/recipes/add-translation-keys) for the text inside your block.
