Skip to content

Layouts & partials

Layouts and partials are the other two page primitives — the same page store, a different type. Together with pages they make composition a first-class idea: everything on a page is a labeled partial, dropped into place.

Layouts — the chrome around a page

A layout (type = layout) is the shell that wraps a page: the header, footer, nav, and outer markup a page renders inside. A page names its layout with the layout_key column, pointing at a layout row's stable page_key.

When Tiger_Cms_Renderer::render() renders a page that has a layout_key, it:

  1. Renders the page body by its format → HTML.
  2. Fetches the layout row and renders its body, passing the page's HTML in as the content view var.
  3. The layout emits that HTML wherever it places the shortcode.

So a layout is just another content row whose body places where the page should go:


<main class="container py-5">
  
</main>

Layouts aren't publish-gated — they're infrastructure, fetched by key regardless of status (Tiger_Model_Page::fetchByKey), and they cascade per org like everything else (a tenant's own layout wins over the global one).

A layout row is an author-editable template. It's distinct from a theme's layout files, which are presentation chrome resolved by path (see Theming). Reach for a CMS layout row when you want the template itself to be editable content; theme files own the site's structural chrome.

Partials — reusable fragments

A partial (type = partial) is a named fragment you compose into pages and layouts: a hero, a call-to-action, a pricing band, a footer. You place it by reference with the shortcode:

name is the partial's page_key. At render time the shortcode looks up the published partial row (org cascade — an org's own partial wins over the global one) and renders its body in place. Because it's placed by reference, editing the partial once updates it everywhere it's dropped.

Partials render recursively: a partial can itself contain , , and nested s. A cycle-and-depth guard (max depth 10, no repeated name on the stack) stops infinite loops — a skipped partial leaves an HTML comment rather than breaking the page.

"Everything is a labeled partial"

The payoff of these three primitives plus is that a whole site is composed from labeled fragments. A visually-built layout is itself just partials around the content slot:

    

Change the header partial once and every page using that layout updates. Build a page from a stack of section partials and reorder them without touching the sections themselves. This is the composition model the visual builder leans on.

The widget in the builder

In the GrapesJS visual builder, partials show up as draggable blocks. Dropping a partial widget renders a live preview of the fragment in the canvas, but exports the shortcode, not the frozen markup. So the design stays dynamic and auth-filtered at view time: the builder saves the reference, and the partial is resolved fresh on every render. (Editing a fragment in the builder is the page builder inverted — the layout becomes locked context and the fragment is the one editable region.)

Contrast this with a block (type = block), the builder's copy-in library fragment: dropping a block inlines its HTML into the page, detached from the source. A partial is a live reference; a block is a one-time copy. Blocks are covered in The visual builder.

Recipe — a section with its own layout and menu

The common ask: "give this part of the site its own look and its own navigation." Four steps, and one thing that deliberately does not work the way people expect.

1. Create the layout

Either fork one the theme ships — the theme's content/*.phtml files hinted <!-- tiger:layout --> appear in the CMS as forkable skeletons — or author a type = layout row from scratch. Give it a stable page_key; that string is what pages will point at, so pick something you won't rename (layout-members, not layout-2).

A layout is just a content row that places where the page goes. The shipped full-width skeleton is the whole idea in four lines:


<div class="container py-4">
  
</div>

2. Give it its own menu

Put a in the layout, naming a menu key of its own rather than the site-wide one:

Then build members-nav in the Menus admin. Items are ACL-filtered and labels translated, so one menu can serve signed-in and signed-out visitors without a second copy.

Because the menu is named in the layout, every page using that layout gets that navigation, and pages on other layouts are untouched.

3. Point pages at it

Set the page's layout_key to the layout's page_key. That's the entire binding — no registration step, no config.

4. Skins are site-wide, not per-layout

This is the part that surprises people. A skin is a CSS-variable overlay within a theme — the .css files in <theme>/assets/skins/ (puma ships bengal, cheetah, jaguar, puma, tabby). It is selected by the tiger.skin config value, which cascades per org, with a tiger_skin cookie for previewing. There is no skin column on a layout or a page.

So:

  • Different colours for a whole site or a whole tenant → set tiger.skin (per-org override).
  • Different colours for one section → do it in the layout, with your own classes or a scoped stylesheet the layout pulls in. That is a layout concern, not a skin.

If you want a genuinely different structure rather than different colours, that is a theme, not a skin — see Theming for the theme/skin split.

A fresh install's menu is not empty — it is the theme's. Until something writes menu rows, Tiger_Menu falls back live to the theme's configs/menus.ini. So a brand-new site shows the theme's stock navigation, which on the default theme means Tiger's own marketing links pointing at routes your install does not have. Import or fork the theme menu and prune it; a page that is not reachable from the navigation is not really published.

See also

  • Pages — the store, formats, slug dispatch, cascade
  • Shortcodes — , , , and registering your own
  • The visual builder — the partial widget and blocks
  • Theming — theme layout files vs CMS layout rows, and the theme/skin split
  • Menus — building the menus renders