Layouts
A layout defines named regions and a template that arranges render arrays into them. Register one and it becomes available everywhere core places content into layouts, most visibly Layout Builder, core’s drag-and-drop tool for arranging content into sections.
Provide your own layouts: a static one declared in YAML, and a configurable one declared with the #[Layout] attribute on a plugin class with its own settings form. Layouts are plugins, so the plugins concept explains the machinery underneath; this page sticks to the layout-specific parts.
Prerequisites
Section titled “Prerequisites”- The Layout Discovery module (
layout_discovery) installed; it provides the plugin manager and thelayouttheme hook. Layout Builder depends on it, so on most sites it is already there. Install withddev drush pm:install layout_discoveryotherwise. - A custom module or theme to hold the layout. Both can provide layouts; the examples below live in a module.
Declare a static layout in YAML
Section titled “Declare a static layout in YAML”For a layout with no settings and no logic, a MODULE.layouts.yml (or THEME.layouts.yml) entry is all the registration needed. \Drupal\Core\Layout\LayoutPluginManager merges YAML-discovered definitions with attribute-discovered classes, so both styles coexist:
# A static layout needs no PHP class: declare it in MODULE.layouts.yml# (or THEME.layouts.yml) and provide the template.theming_howtos_hero: label: Hero category: 'Documentation examples' # Resolved relative to this extension. The template file is # layouts/theming_howtos_hero/layout--theming-howtos-hero.html.twig. path: layouts/theming_howtos_hero template: layout--theming-howtos-hero default_region: content # icon_map draws the thumbnail shown when picking a layout: each row is # a list of region names. icon_map: - [banner] - [content] regions: banner: label: Banner content: label: ContentWithout a class key the plugin uses \Drupal\Core\Layout\LayoutDefault, which renders the regions through the template and nothing more.
Write the layout template
Section titled “Write the layout template”The template receives one variable per concern: content.REGION for each region’s render array, region_attributes.REGION for per-region attributes, attributes for the wrapper, and settings for the plugin configuration:
{#/** * @file * Template for the "Split" layout. * * Available variables: * - content: One render array per region, keyed by region name. * - attributes: HTML attributes for the layout wrapper. * - region_attributes: HTML attributes, keyed by region name. * - settings: The layout plugin's configuration values. * - in_preview: TRUE while previewed in the Layout Builder UI. */#}{% set classes = [ 'layout', 'layout--split', 'layout--split--' ~ settings.ratio|clean_class,] %}{% if content %} <div{{ attributes.addClass(classes) }}> {% if content.main %} <div{{ region_attributes.main.addClass('layout__region', 'layout__region--main') }}> {{ content.main }} </div> {% endif %} {% if content.aside %} <div{{ region_attributes.aside.addClass('layout__region', 'layout__region--aside') }}> {{ content.aside }} </div> {% endif %} </div>{% endif %}Two conventions worth copying from core’s own layouts: guard each region with {% if content.REGION %} so empty regions produce no wrapper markup, and keep the layout and layout__region classes so site-wide layout CSS applies. The template name’s dashes become the theme hook name’s underscores, and every layout template gets layout as its base hook, so a theme can override layout--theming-howtos-hero.html.twig like any other template (see Twig in Drupal).
Register a configurable layout with the attribute
Section titled “Register a configurable layout with the attribute”When a layout needs settings, declare it as a class in src/Plugin/Layout with the \Drupal\Core\Layout\Attribute\Layout attribute. No YAML entry is needed; the attribute is the registration:
/** * A two-region layout with a configurable width ratio. * * Layout plugins live in src/Plugin/Layout. The class is optional for * static layouts (LayoutDefault is used then); provide one when the * layout needs settings or build-time logic. */#[Layout( id: 'theming_howtos_split', label: new TranslatableMarkup('Split'), category: new TranslatableMarkup('Documentation examples'), description: new TranslatableMarkup('Main content with a configurable aside column.'), // Resolved relative to this module; the template file is // layouts/theming_howtos_split/layout--theming-howtos-split.html.twig. path: 'layouts/theming_howtos_split', template: 'layout--theming-howtos-split', default_region: 'main', regions: [ 'main' => ['label' => new TranslatableMarkup('Main')], 'aside' => ['label' => new TranslatableMarkup('Aside')], ],)]final class ThemingHowtosSplitLayout extends LayoutDefault {The attribute’s keys mirror the YAML keys: regions (each with a label), default_region, template plus path, an optional library to attach CSS with the layout, and icon_map for the picker thumbnail. A label is required unless the plugin uses a deriver to generate definitions dynamically.
Add the settings form
Section titled “Add the settings form”The plugin class supplies defaults, a form, and the submit handler that stores the values. Whatever ends up in the configuration reaches the template as settings:
/** * {@inheritdoc} */public function defaultConfiguration() { return parent::defaultConfiguration() + [ 'ratio' => '2-1', ];}
/** * {@inheritdoc} */public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form = parent::buildConfigurationForm($form, $form_state); $form['ratio'] = [ '#type' => 'select', '#title' => $this->t('Column ratio (main : aside)'), '#options' => [ '2-1' => '2 : 1', '3-1' => '3 : 1', '1-1' => '1 : 1', ], '#default_value' => $this->configuration['ratio'], ]; return $form;}
/** * {@inheritdoc} */public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { parent::submitConfigurationForm($form, $form_state); $this->configuration['ratio'] = $form_state->getValue('ratio');}LayoutDefault already implements PluginFormInterface and provides the administrative label field; call the parent methods so you keep it. The template above turns the stored ratio into a class with settings.ratio|clean_class, which is the whole round trip: form value to configuration to template to CSS.
Use the layouts in Layout Builder
Section titled “Use the layouts in Layout Builder”Nothing to register: with Layout Builder enabled for a display, Add section lists every available layout with the thumbnail drawn from icon_map. Selecting a layout opens its configuration form in the off-canvas sidebar; for the configurable layout that form includes your ratio select, while plain LayoutDefault layouts only offer the administrative label.
Layouts also serve as the section templates for entity view displays, and contributed modules consume the same plugin manager, so one layout definition serves them all.
Verify
Section titled “Verify”-
Run
ddev drush cache:rebuild(ddev drush cr). -
Confirm registration from the command line:
Terminal window ddev drush php:eval "print_r(array_keys(\Drupal::service('plugin.manager.core.layout')->getDefinitions()));"Both
theming_howtos_heroandtheming_howtos_splitappear in the list. -
On a content type with Layout Builder enabled (Administer → Structure → Content types → Manage display, check Use Layout Builder), edit the layout, select Add section, and pick Split. The ratio select from the settings form appears; after saving, the section’s wrapper carries the
layout--split--2-1class.
Variations
Section titled “Variations”Layouts from a theme
Section titled “Layouts from a theme”Themes declare layouts the same two ways. A theme layout is only available while that theme provides it, which suits design-system layouts that are meaningless outside the theme; ship layouts in a module when content depends on them, because switching themes must not invalidate stored sections.
Dynamic layouts with derivers
Section titled “Dynamic layouts with derivers”One class can produce many layout definitions through a deriver, the standard plugin derivative pattern. Reach for it when layouts differ only by data (column counts, ratios), not markup.
Restrict which layouts Layout Builder offers
Section titled “Restrict which layouts Layout Builder offers”Layout Builder filters the definitions it offers through the plugin filtering system; implement hook_plugin_filter_layout__layout_builder_alter() in a module to remove layouts from the Add section list without unregistering them.
Related
Section titled “Related”- Plugins explains attributes, derivers, and plugin managers in general.
- Twig in Drupal covers overriding the templates layouts render through.
- Asset libraries for the
librarya layout can attach. \Drupal\Core\Layout\LayoutDefinitiondocuments every definition key the attribute and YAML map to.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.