Add a home block
Create a section merchants can add to the home page and render it from your theme.
Framework: TanStack Start
Registration below lives in app/router.tsx, which is specific to TanStack Start. See what depends on the framework.
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). 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.
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.
{
"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
It receives { data }.
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 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.
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
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.
Traps
- Omit
...DefaultHomeComponentsand every built-in block is gone. Nothing else registers the engine's blocks, so only what you pass is available. - The key is
pathminushome..home.brandsregisters asbrands. A mismatch shows a yellow "Unknown component" box in development and nothing in production. view_stylewins. For a block withview_style: "style2"the renderer triesfeatured-products:style2beforefeatured-products. To replace a styled block, register the:styleNkeys too (the reference theme does).- Config replaces, not merges. A theme entry in
registerHomeComponentConfigreplaces 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.
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".
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
- Home route: how the page loads its blocks.
- Replace the product card: product lists inside blocks use the same card.
- Add translation keys for the text inside your block.