Layout and components

The page frame around every route, and how to swap an engine component for your own.

Read this when you want to change how the header, footer, product card or home page look.

A component is a function that returns markup. The engine ships many (header, footer, product card, home blocks), and your theme can replace the ones you want to change. The sections below go from the biggest swap to the smallest.

The layout wraps every page

The layout is the frame around each route: header on top, footer at the bottom, your page in between. By default the engine uses its MasterLayout. You can pass your own to TwilightProvider in the root route:

app/routes/__root.tsx
import { TwilightProvider } from '@salla.sa/twilight-theme-engine';
import themeTranslations from 'virtual:twilight/theme-translations';
import { ThemeLayout } from '../components/layout/ThemeLayout';

// inside RootComponent
<TwilightProvider translations={themeTranslations} layout={ThemeLayout}>
  <Outlet />
</TwilightProvider>

A layout is a component that receives children (the current page). This is the starter theme's, shortened:

app/components/layout/ThemeLayout.tsx
import { Suspense, type ReactNode } from 'react';
import { Footer } from '@salla.sa/twilight-theme-engine/layout';
import { Header } from './Header';

export function ThemeLayout({ children }: { children: ReactNode }) {
  return (
    <>
      <div className="app-inner flex flex-col min-h-full">
        <Header />
        <main id="main-content" className="flex-1">
          {children}
        </main>
        <Suspense fallback={null}>
          <Footer />
        </Suspense>
      </div>
      {/* global modals: offer, login, scopes … */}
    </>
  );
}

It mirrors the engine's layout: its own header, the engine's footer, and the same global modals. Start from a copy of the engine's layout and change only what you need. The engine's Footer has two looks, picked by the theme setting footer_layout (see Header and footer). See the MasterLayout reference.

Only the layout swaps the header and footer. To just add a banner above the header, use a hook slot instead of replacing everything.

Swap the product card with the registry

The component registry is a name-to-component map. The engine looks a few names up at render time, so registering your component under that name replaces the built-in one everywhere.

The product card is the main one (product:card). It needs both calls:

app/router.tsx (your theme)
import { registry } from '@salla.sa/twilight-theme-engine';

registry.register('product:card', MyProductCard); // seeds an "original"
registry.override('product:card', MyProductCard); // now it counts as an override

The engine card only uses your component when register has run first and override after it, so override alone does nothing. Step by step: Replace the product card.

The engine looks up only these names: product:card, product:gallery, product:list-page and product:list-skeleton (every product listing page and its loading skeleton), account:layout-pending (the skeleton shown while account pages load), and the home:* names. Anything else is not swappable this way.

Swap a home block

The home page is built from blocks that a merchant arranges. Each block has a key, and registerHomeComponents binds keys to components. The starter theme starts from the engine defaults and replaces some:

app/router.tsx
import {
  registerHomeComponents,
  DefaultHomeComponents,
} from '@salla.sa/twilight-theme-engine/routes/home';

registerHomeComponents({
  ...DefaultHomeComponents,
  brands: Brands,
  'products-slider': ProductsSlider,
});

Keep ...DefaultHomeComponents first, so blocks you did not replace still render. To add a brand new block, see Add a home block.

Or just wrap the engine component

You do not have to rebuild a component. The starter theme's ProductCard imports the engine card (import { ProductCard as EngineProductCard } from '@salla.sa/twilight-theme-engine/product') and renders it with small changes. Wrapping keeps engine fixes flowing in.

Which to choose

GoalUse
New header or footerYour own layout
Different product cardRegistry product:card
Different home blockregisterHomeComponents
Add content in one spotA hook slot
Change a whole pageReplace the route

Every engine component is documented in the Components tab, starting with MasterLayout.

Next: Hooks and slots, to add content without replacing anything.

On this page