Skip to content

Layouts

Abdulkader Safi edited this page Aug 18, 2026 · 1 revision

Layouts

A site is rarely one shell. Marketing pages want a navbar and a footer, documentation wants a sidebar, a landing page often wants neither. The blocks are the same either way, so the shell is a per-page choice rather than a different set of blocks.

A layout is a Blade view you write, registered with a key and a label. The client picks one from a dropdown on the page settings screen.

Registering them

Where you register blocks:

use Safi\Atelier\AtelierPlugin;
use Safi\Atelier\Blocks\DefaultBlocks;

->plugins([
    AtelierPlugin::make()
        ->blocks(DefaultBlocks::all())
        ->layouts([
            'site' => [
                'label' => 'Navbar and footer',
                'view' => 'layouts.site',
                'description' => 'The marketing shell. Full-width sections.',
            ],
            'docs' => [
                'label' => 'Sidebar',
                'view' => 'layouts.docs',
            ],
            'bare' => 'layouts.bare',
        ]),
])

The long form takes label, view and an optional description. The short form is just the view name, and the label comes from the key, so 'bare' => 'layouts.bare' shows as Bare.

A layout is a map rather than a class. Blocks earn a class because they carry a schema, an icon, a category and translatable keys; a layout is a key, a label and a view, and a class holding three strings is ceremony.

Choosing one

Page settings, under the title: a Layout dropdown. It hides itself entirely when the app has registered no layouts, because a question with one answer is not a question.

Empty means Default, the site-wide atelier.layout. Registering layouts does not force every page to pick one.

The choice is page-level, not per locale. A layout is structure, and both locales share one structure by design, the same reason Arabic cannot have a different section order from English.

Writing one

A layout receives:

Variable What it is
$blocks The rendered HTML of every section. Echo it unescaped.
$page The page model, for a title, a nav highlight, anything else
$locale Current locale code
$title Meta title, falling back to the page title
$preview True when the editor's iframe is rendering it

A documentation shell with a sidebar:

@php
    $locales = config('atelier.locales', []);
    $dir = $locales[$locale]['dir'] ?? 'ltr';
    // Don't assume Atelier's controllers are the only caller.
    $page = $page ?? null;
    $nav = \Safi\Atelier\Models\Page::query()
        ->where('status', 'published')
        ->orderBy('title')
        ->get();
@endphp
<!DOCTYPE html>
<html lang="{{ $locale }}" dir="{{ $dir }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    @include('atelier::partials.meta')

    @vite(['resources/css/app.css'])

    @include('atelier::partials.tokens')
</head>
<body class="bg-white text-neutral-900 antialiased">
    <div class="mx-auto flex max-w-7xl gap-10 px-6 py-10">
        <aside class="hidden w-56 shrink-0 lg:block">
            <nav class="space-y-1 border-s border-neutral-200 ps-4">
                @foreach ($nav as $item)
                    @if ($url = $item->url($locale))
                        <a href="{{ $url }}"
                           @class([
                               'block py-1 text-sm',
                               'font-medium' => $page && $item->is($page),
                               'text-neutral-500 hover:text-neutral-900' => ! ($page && $item->is($page)),
                           ])>{{ $item->title }}</a>
                    @endif
                @endforeach
            </nav>
        </aside>

        <main class="min-w-0 flex-1" data-atelier-canvas>
            {!! $blocks !!}
        </main>
    </div>
</body>
</html>

There is a working copy of this in example/resources/views/layouts/docs.blade.php.

Four things that will bite you otherwise

  1. Include both partials. atelier::partials.meta in the head carries the title, description, canonical, hreflang, Open Graph and the noindex on previews. atelier::partials.tokens goes after your stylesheet and defines every var(--atelier-*). Leave either out and nothing errors: the page renders with no head, or the section controls and the Arabic font silently stop working. See Using your own layout.
  2. Put data-atelier-canvas on the element wrapping {!! $blocks !!}. The editor swaps the contents of that element on every preview refresh. Without it the preview reloads the whole iframe, losing scroll position, or stops updating.
  3. Guard $page. Write $page = $page ?? null if you use it. A layout rendered by anything other than Atelier's controllers will not have it.
  4. Tailwind has to scan your layout. Layouts live in your app, so it already does. It is the package's views that need the @source line from Installation.

What happens to a page whose layout disappears

Nothing visible. Delete a layout from the panel provider and pages still naming it fall back to the site-wide default. A page keeps its layout key after the code that defined it is gone, and a 500 on every public page is a bad way to find that out.

The preview uses it too

The editor's preview renders through the same layout the public page will use, resolved by the same method. A preview rendered through a different shell is a preview that lies, which is the whole reason the preview exists.

Clone this wiki locally