Skip to content
MCG Filament CMS Demo

Regions

A region is a named area of a layout into which blocks are placed. A region is added to a layout by adding an <x-cms::region> tag to the layout file. No other step is required:

Blade

<div class="cms-columns">
    <main class="cms-main" id="cms-content">
        <x-cms::region name="main" label="Main content" />
    </main>

    <aside class="cms-aside">
        <x-cms::region name="sidebar" label="Sidebar" />
    </aside>
</div>

How the editor finds regions

Regions are declared in one place only, so the template and the editor always list the same regions. LayoutRegions (src/Support/LayoutRegions.php) finds regions by scanning the layout's source with a regular expression instead of rendering it. Rendering a layout requires request state, a content payload and a full Blade compile, and the editor has none of these when it decides which fields to draw. The consequence is that only literal region names are detected (name="sidebar", name='sidebar' or :name="'sidebar'"). An editor chooses a region from a fixed list, so a region name has to be a literal in any case.

Region rules

  1. A layout with no region tags has one region, main. That region's blocks are passed to the layout as {{ $slot }}. Layouts written before regions existed therefore work without changes.
  2. A layout uses either {{ $slot }} or <x-cms::region name="main" />, never both. Both render the main region's blocks, so a layout containing both renders those blocks twice.
  3. The region name is stored on each block. Renaming sidebar in the template orphans every block already placed in it. A block with no region key belongs to main.
  4. label names the region in the panel and in the front-end editor. It titles the region tabs in the page editor. Since v0.54.0 it is also the text in the tag an editor sees when hovering a block with front-end editing on, for example Region Β· Main content. Visitors never see it. When no label is given, the region name is used, so an unlabeled main region reads Region Β· main. Write the label as the name an editor would recognize the region by, not as an internal note.

Switching a page to a template with fewer regions deletes nothing. Blocks in a region that the new template does not declare stop rendering and remain stored. Switching the page back to a template that declares the region renders them again.

The .cms-region wrapper

<x-cms::region> wraps its blocks in <div class="cms-region">. A shared region's wrapper also carries cms-region--shared.

When an editor has front-end editing on, the wrapper carries up to three data attributes:

  • data-cms-region: present on every ordinary region, and on a shared region only when its area record exists. The inline editor scopes a drag to it, so a block cannot be dragged out of its region.
  • data-cms-region-label: present on every region. The inline editor shows it in a hovered block's tag; see Editing on the page.
  • data-cms-owner: present on a shared region whose area record exists. The inline editor uses it to address the layout area record that owns the blocks.

All three attributes are omitted for every other request, including every visitor's. The inline editor is the only code that reads them, and it is not rendered unless editing is active. Emitting them to visitors added markup that nothing used, and in the label's case it put an internal region name into the markup of every public page. Write CSS and JavaScript selectors against .cms-region, which is always present. Do not write a selector that depends on the data attributes.

Do not give .cms-region display: flex or display: grid. A flex or grid container stops the vertical margins of adjacent children from collapsing, so every gap between blocks doubles from 32px to 64px. This was measured on /docs/data-model, which has 15 top-level blocks: with the shipped block-level <div>, the gaps measure 32px and the first block sits 74px below the top of .cms-main, which is identical to having no wrapper at all; with flex or grid, the gaps measure 64px. cms.css pins .cms-region to display: block. All six shipped skins (arcade.css, market.css, marquee.css, press.css, saas.css and signal.css) set --cms-rhythm to the finished gap on the assumption that margins collapse. To add space between blocks, set --cms-rhythm.

The wrapper sits between .cms-main and the blocks, so a selector written as .cms-main > .cms-block no longer matches anything. In a custom skin written against the old structure, change it to .cms-main > .cms-region > .cms-block. Where the rule targets the first block in the column, use .cms-main > .cms-region:first-child > .cms-block:first-child, because the docs and article layouts render their own <h1> above the region.

Shared regions

A shared region takes its blocks from the layout instead of from the page. Every page using that layout renders the same blocks, including pages created after the blocks were placed, without any editor placing anything on them. A region is made shared by adding the shared attribute to its tag:

Blade

<nav class="cms-docs__nav">
    <x-cms::region name="navigation" label="Navigation" shared />
</nav>

LayoutRegions accepts three spellings with the same meaning: shared, shared="true" and :shared="true".

Use a shared region for content that belongs to a section of the site instead of to one page. Examples are a documentation navigation rail, a call to action above the footer, and a notice across a campaign's landing pages. Use an ordinary region for content that describes the page it appears on. A table of contents describes its page, but it still works in a shared region; the reason is explained in The docs layout.

A shared region's blocks are stored in a layout_area content record. The layout_area content type (src/ContentTypes/LayoutArea.php) has no URL, which is the same arrangement snippets use. A layout area therefore uses the existing immutable versions, draft and publish states, version history, diffing, restore, block builder, admin resource and MCP tools, and none of these had to be written a second time.

Shared region rules

  1. A shared region ignores the page's own blocks. A region is either shared or not shared. Rendering both sets would require deciding whether the shared blocks come before or after the page's blocks, and no order is correct for every case. Blocks already stored in a region that later becomes shared are kept on every save and stop rendering.
  2. There is one area per layout and region, identified by its slug. LayoutAreas::slugFor() (src/Support/LayoutAreas.php) derives the slug: cms-layout::docs with region navigation becomes cms-layout-docs-region-navigation. The word region separates the two halves instead of a punctuation mark because the slug is slugged again when it is saved. ContentWriter re-slugs every slug it receives, and an earlier double-hyphen separator was collapsed to a single hyphen on the way into the database, so every lookup then missed by one character. The slug is the only link between the template and the content record, so the slug field is locked in the panel.
  3. Areas are created automatically and never created by hand. LayoutAreas::sync() creates a record for every declared shared region that has none. The Layout areas list in the panel runs sync() each time it loads, and php artisan cms:layout-areas runs it from a deploy script. The list has no Create button, because a record created by hand would carry a slug that matches no region.
  4. An area renders nothing until it is published. Creating the record does not make it render. A visitor, and a preview, see the area's published version. An editor browsing the front end with draft view on sees the area's draft, so that a block saved as a draft from the front end appears where it was placed. An area that is missing, or that exists and has never been published, renders as an empty region and does not cause an error.
  5. Publishing an area purges the whole page cache. A page that embeds a snippet records the reference in content_refs, so republishing that snippet purges exactly the pages that contain it. No record links a page to a shared region, because the layout references the area and the page does not. Every page on the layout may be stale, and no cheaper query identifies which pages use it. See Caching.
  6. Areas are per language. sync() creates the area in the default locale only. Other languages are created with the Translate action, in the same way as for a page. An area with no translation in the language being served renders nothing instead of rendering text in another language. See Multilingual sites.

Filling a shared region

  1. In the admin panel sidebar, click Layout areas. Loading the list creates any area that does not exist yet.
  2. In the Layout areas list, click the area named after the template and the region. The title is the layout's label, a colon and the region's label, for example "Docs: Navigation".
  3. In the area's editor, add blocks.
  4. In the same editor, click Publish. The blocks now render on every page that uses the layout.

Shared regions in the page editor

On a page that uses a layout with a shared region, the page editor replaces that region's block builder with a read-only list of the blocks the area holds and an Edit blocks button.

  • The section carries a Shared Region badge. The explanation is in the badge's tooltip ("This layout includes a shared region. Any block added to this region will appear on any page using this layout.") instead of in a line of text under every such section heading. The badge text comes from cms::cms.panel.content.shared_region_badge, so it is changed by editing the lang file.
  • The section is collapsed by default. Nothing in it can be edited in place, and its content is identical on every page using the template, so an open section would place unchanging text between the editor and the page content.
  • The read-only list shows the area's published version, because the list shows what this page displays to visitors. An area with unpublished changes shows the message "This shared area has unpublished changes."

Edit blocks opens the area's block builder in a slide-over without leaving the page. The slide-over loads the area's draft version, because it is an editing surface and must open on unpublished work instead of discarding it.

The slide-over has its own save buttons, separate from the page's:

  • An editor who may publish layout areas sees Save & publish (the primary button) and Save as draft.
  • An editor who may not publish layout areas sees Save as draft only, as the primary button.

The slide-over writes the area record and nothing else; the page's own save is unaffected. A page and an area are two records with separate publish states. A single save button covering both would publish the area's changes onto every other page on the layout whenever an editor published the page they were viewing.

The action is defined on EditContent as editSharedRegionAction(), not on the schema component. The region sections are built inside a closure so that changing the template rebuilds them, and a schema built inside a closure does not keep the component identity Filament needs to re-render the component that owns a modal. When the action is defined on the schema component, the failure is an empty partial that Livewire reports as a missing root tag, and the error message does not name the cause.

The docs layout

The included docs layout shares three of its four regions: page-header (labeled "Page header content"), navigation (labeled "Navigation") and contents (labeled "On this page"). The main region is per page.

The table of contents block works in the shared contents region. The block reads $cmsBlocks, which always holds the blocks of the page being rendered, not the blocks of the area the table of contents block is stored in. One shared table of contents block therefore produces a different and correct heading list on every page. The search block also works in a shared region, because it has no per-page state.

Switching an existing region to shared

Fill and publish the area before the template change reaches visitors. When the shared attribute is deployed, every block already placed in that region on every page stops rendering (shared region rule 1), and an area that has not been published renders nothing (shared region rule 4). Together, these two rules remove the region's content from the site until the area is published.

Shared regions over MCP

The MCP tools enforce shared region rule 1. cms_append_block refuses to append a block to a shared region and returns the area's path. cms_update_content returns a warning instead of refusing, because a block list read back from cms_get_content can legitimately contain blocks left in a region that later became shared. See The MCP server.