Custom Layout Plugins
When to Use
When you need section layouts that match your design system's grid (e.g., custom column ratios, asymmetric layouts, full-bleed sections) rather than core's basic one/two/three column layouts.
How Layout Plugins Work in Plus Suite
Plus Suite uses standard Drupal Layout plugins (same as core Layout Builder). The Layout tool presents all available layouts when changing a section. Custom layout plugins appear automatically.
Creating a Custom Layout Plugin
Option 1: YAML-defined layout (simplest)
# my_theme.layouts.yml
my_theme_hero_layout:
label: 'Hero Layout'
category: 'My Theme'
template: layouts/hero-layout
icon_map:
- [media]
- [content]
- [cta]
regions:
media:
label: Media
content:
label: Content
cta:
label: Call to Action
my_theme_sidebar_right:
label: 'Content + Sidebar'
category: 'My Theme'
template: layouts/sidebar-right
icon_map:
- [content, content, sidebar]
regions:
content:
label: Content
sidebar:
label: Sidebar
my_theme_three_unequal:
label: 'Wide Center (25/50/25)'
category: 'My Theme'
template: layouts/three-unequal
icon_map:
- [left, center, center, right]
regions:
left:
label: Left
center:
label: Center
right:
label: Right
my_theme_full_bleed:
label: 'Full Bleed'
category: 'My Theme'
template: layouts/full-bleed
icon_map:
- [content]
regions:
content:
label: Content
Template files:
{# templates/layouts/hero-layout.html.twig #}
{%
set classes = [
'layout',
'layout--hero',
]
%}
{% if content %}
<div{{ attributes.addClass(classes) }}>
<div class="layout__region layout__region--media">
{{ content.media }}
</div>
<div class="layout__region layout__region--content">
{{ content.content }}
</div>
<div class="layout__region layout__region--cta">
{{ content.cta }}
</div>
</div>
{% endif %}
Option 2: PHP class layout (configurable)
namespace Drupal\my_theme\Plugin\Layout;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Layout\Attribute\Layout;
use Drupal\Core\Layout\LayoutDefault;
use Drupal\Core\StringTranslation\TranslatableMarkup;
#[Layout(
id: 'my_theme_configurable_hero',
label: new TranslatableMarkup('Configurable Hero'),
category: new TranslatableMarkup('My Theme'),
template: 'layouts/configurable-hero',
regions: [
'media' => ['label' => new TranslatableMarkup('Media')],
'content' => ['label' => new TranslatableMarkup('Content')],
'cta' => ['label' => new TranslatableMarkup('CTA')],
],
)]
class ConfigurableHero extends LayoutDefault {
public function defaultConfiguration(): array {
return parent::defaultConfiguration() + [
'height' => 'medium',
'overlay' => 'none',
];
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
$form = parent::buildConfigurationForm($form, $form_state);
$form['height'] = [
'#type' => 'select',
'#title' => $this->t('Section Height'),
'#options' => [
'small' => $this->t('Small (300px)'),
'medium' => $this->t('Medium (500px)'),
'large' => $this->t('Large (80vh)'),
'full' => $this->t('Full Screen (100vh)'),
],
'#default_value' => $this->configuration['height'],
];
$form['overlay'] = [
'#type' => 'select',
'#title' => $this->t('Content Overlay'),
'#options' => [
'none' => $this->t('None'),
'light' => $this->t('Light overlay'),
'dark' => $this->t('Dark overlay'),
'gradient' => $this->t('Gradient'),
],
'#default_value' => $this->configuration['overlay'],
];
return $form;
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
parent::submitConfigurationForm($form, $form_state);
$this->configuration['height'] = $form_state->getValue('height');
$this->configuration['overlay'] = $form_state->getValue('overlay');
}
public function build(array $regions): array {
$build = parent::build($regions);
$build['#attributes']['class'][] = 'hero--height-' . $this->configuration['height'];
$build['#attributes']['class'][] = 'hero--overlay-' . $this->configuration['overlay'];
return $build;
}
}
LB+ Configure Tool Integration
When a section uses a configurable layout plugin (PHP class with buildConfigurationForm()), the Configure tool (o hotkey) opens the layout's configuration form in a dialog. No extra integration needed — LB+ handles this automatically.
Default Section Configuration
You can set your custom layout as the default for new sections:
# Via entity_view_display third-party settings
third_party_settings:
lb_plus:
default_section:
layout_plugin: my_theme_hero_layout
config: {}
Or configure via Structure → Content Types → Manage Display → LB+ Default Section.
Layout Icons (icon_map)
The icon_map in YAML layouts defines the visual preview shown in the Layout tool's chooser:
# Equal three-column
icon_map:
- [left, center, right]
# Two rows: full-width header + two columns
icon_map:
- [header, header]
- [left, right]
# 25/50/25 split
icon_map:
- [left, center, center, right]
Decision: YAML vs PHP Layout
| Use YAML When | Use PHP Class When |
|---|---|
| Static layouts with fixed regions | Configurable options (height, overlay, color) |
| Simple column arrangements | Dynamic region count |
| No per-instance customization needed | Settings form in Configure tool |
| Quick iteration during design | Complex rendering logic |
Layouts for Nested Layout Blocks
Layout blocks use the same layout plugins. Design layouts specifically for nesting:
# Designed for use inside layout blocks
my_theme_card_layout:
label: 'Card Layout'
category: 'My Theme - Nested'
template: layouts/card-layout
icon_map:
- [image]
- [body]
- [footer]
regions:
image:
label: Image
body:
label: Body
footer:
label: Footer
Common Mistakes
- Do not create too many layout variations — 5-10 covers most design systems.
- Do not put business logic in layout templates — keep them structural.
- Do not forget
icon_map— without it, the Layout tool shows a generic preview. - Do not create layouts with more than 4-5 regions — it becomes unusable in Edit Mode.
See Also
- Nested Layouts
- Custom Design System Integration
- Reference:
core/lib/Drupal/Core/Layout/LayoutDefault.php