Advertising sustains the DA. Ads are hidden for members. Join today

On this page

Best Practices

Last updated on
7 January 2025

This documentation needs review. See "Help improve this page" in the sidebar.

Introduction

3 logics to drive a component structure:

  • design system:
    • Keep things simple

  • stick to the design system used:
    • if not doing from scratch

  • stick to Drupal logic to ease usage in Drupal:
    • menus
    • breadcrumb

Potentially easier with components for specific design systems than for the features-rich design systems like Bootstrap or Zurb Foundation.

For information, common terminology in similar solutions:

UI Patterns 1.x Drupal SDC WebComponents ReactJS Vuejs
pattern ⚠
component ✓
component ✓
component ✓
component ✓
fields ⚠
slots ✓
slots ✓
children ⚠
slots ✓
settings ⚠
props ✓
attributes ⚠
props ✓
props ✓

That’s why we are mostly using “components”, “slots” and “props” in this document.

Definitions: Components and variants

Stay business agnostic while naming component

For example:

  • Don't use words relative to the data model, or business structure
  • If possible, use the BEM block name of the root element as a component name
Do ✓
Don't 🚫
  • Card
  • Carousel
  • Carousel item
  • Event card
  • Article card
  • News slideshow

Use variants when needed

A variant is a different visual version of the component. It has a label and a description. All variants share the same data model, they may not use some slots or props, but they do the same use of the slots and props.

So, don’t fake it with a prop.

Examples, set 4 variants in a component:

Do, in YML ✓
button_group:
  label: Button Group
  variants:
    default:
  	label: Default
    vertical:
  	label: Vertical
  	description: Make a set of buttons appear vertically stacked rather than horizontally. Split button dropdowns are not supported here.
    lg:
  	label: Large
    sm:
  	label: Small

And then add the class in the Twig template based on the variant name:

Do ✓
{% if variant != '' and variant|lower != 'default' %}
  {% set attributes = attributes.addClass('btn-group--' ~ variant|lower|replace({'_': '-'})) %}
{% endif %}
<div{{ attributes.addClass('btn-group') }}>
...
</div>

If a variant name must be split in many HTML class names:

Do ✓
{% if variant and variant|lower != 'default' %}
  {% set variants = variant|split('__')|map(v => v|lower|replace({(v): 'progress-bar-' ~ v})|replace({'_': '-'})) %}
  {% set attributes = attributes.addClass(variants) %}
{% endif %}
<div{{ attributes.addClass(progress-bar) }}>
...
</div>

Always set a default variant

Each component with variants must be rendered correctly, in the most expected way, if no variant is specified.

It is common to name it “default”, but it can be any name. It must act the same as a missing or empty “variant” value:

Do ✓
{% if variant != '' and variant|lower != 'default' %}
  {% set attributes = attributes.addClass('btn-group--' ~ variant|lower|replace({'_': '-'})) %}
{% endif %}
<div{{ attributes.addClass('btn-group') }}>
...
</div>
{% if variant != '' and variant|lower != 'primary' %}
  {% set attributes = attributes.addClass('highlight--' ~ variant|lower|replace({'_': '-'})) %}
{% endif %}
<div{{ attributes.addClass('highlight) }}>
...
</div>
Don't 🚫
{% set attributes = attributes.addClass('highlight--' ~ variant|lower|replace({'_': '-'})
<div{{ attributes.addClass('highlight) }}>
...
</div>

If possible, name variants according to markup

And name it according to markup class names if possible.

In this example:

<button type="button" class="btn btn-primary btn-lg">Large button</button>
<button type="button" class="btn btn-secondary btn-lg">Large button</button>
<button type="button" class="btn btn-primary btn-sm">Small button</button>
<button type="button" class="btn btn-secondary btn-sm">Small button</button>

The component is “btn” and the variants are:

  • primary__lg
  • secondary__lg
  • primary__sm
  • Secondary__lg

With BEM naming, it is even easier, because blocks are components and blocks modifiers are variants:

<a class="mdc-button" href="http://example.com">
<a class="mdc-button mdc-button--outlined" href="http://example.com">
<a class="mdc-button mdc-button--raised" href="http://example.com">
<a class="mdc-button mdc-button--unelevated" href="http://example.com">

The component is “button” and the variants are:

  • “” (default)
  • outlined
  • raised
  • unelevated

[soon] Add layout icon maps

Definitions: Slots & props

Use noun or adjectives in slots and props names

Don’t use verbs or actions.

Do ✓ Don't 🚫
  • ...
  • ...
  • ...
  • ...

Keep components flat by avoiding nested slots

Each component must, most of the time, fit in a view mode display. Otherwise site building will become impossible with those components.

Example: split carousel between carousel and carousel item.

Do, in carousel.ui_patterns.yml ✓
carousel:
  label: "Carousel"
  category: "Carousel"
  variants:
    ...
  settings:
    ...
  fields:
    slides:
      type: "render"
      label: "Slides"
      description: "Each slide is a collection of carousel items."
      preview:
        - type: "pattern"
          id: "carousel_item"
          fields:
            image:
              - theme: "image"
          uri: "data:image/svg+xml;base64,...=="
            caption:
              - type: "html_tag"
                tag: "h5"
                value: "First slide label"
              - type: "html_tag"
                tag: "p"
                value: "Nulla vitae elit libero, a pharetra augue mollis interdum."
Do, in carousel_item.ui_patterns.yml ✓
carousel_item:
  label: "(Carousel Item)"
  description: "Internal: to be used in the 'Carousel' component. https://getbootstrap.com/docs/5.2/components/carousel/"
  category: "Carousel"
  fields:
    image:
      type: "render"
      label: "Image"
      description: "The image of the item."
      preview:
        - theme: "image"
          uri: "data:image/svg+xml;base64,...=="
    caption:
      type: "render"
      label: "Caption"
      description: "The caption of the item."
      preview:
        - type: "html_tag"
          tag: "h5"
          value: "Slide label"
        - type: "html_tag"
          tag: "p"
          value: "Nulla vitae elit libero, a pharetra augue mollis interdum."
Don’t, in carousel.ui_patterns.yml
Usable with custom PHP code, but not for site building. 🚫
carousel:
  label: "Carousel"
  fields:
    slides:
      type: "render"
      label: "Slides"
      description: "Each slide is a collection of title, subtitle and slide image."
      preview:
        - - theme: "image"
            uri: "data:image/svg+xml;..."
          - type: "html_tag"
            tag: "h5"
            value: "First slide label"
          - type: "html_tag"
            tag: "p"
            value: "Nulla vitae elit libero, a pharetra augue mollis interdum."
          ...

Make sub components easily spottable in library

In other words, components intended to be used only in a specific context.

To be able to distinguish such components:

  • Put the component’s label inside parenthesis.
    • (Carousel Item)
  • Begin the component’s description with “Internal: ” and describe with which other component the component is intended to be used with.
    • Internal: to be used in the 'Carousel' component.
  • You can also prefix the component machine name with an underscore
  • Use categories to group components togethers.

This issue will made this section obsolete: UI Patterns Library: add sub-components

Don’t put renderables in props

Don’t (in menu props) 🚫 Don’t (beware of fake slots with mappings) 🚫
items:
  type: menu
  label: Items
  preview:
    - title:
    	  theme: image
    	  uri: assets/image-1.png
    - url: "#"
      title:
    	 theme: image
    	 uri: assets/image-2.jpg
items:
  type: array
  label: Items
  preview:
     - image:
    	  theme: image
    	  uri: assets/image-1.png
     - label: Image 2
  	image:
    	  theme: image
    	  uri: assets/image-2.jpg

Use slots instead of props any time possible.

Example: renderable image element instead of “src” prop. That way you can inject your image with field formatters and benefit from the Drupal image manipulation system.

Slots must accept renderable or array of renderables.

Do ✓

Don’t 🚫
fields:
    link:
      type: "render"
      label: "Link"
      preview:
          type: "html_tag"
          tag: "a"
          value: "Click me"
          attributes:
            href: "https://example.com"
settings:
    label:
      type: "textfield"
      label: "Label"
      preview: "Click me"
   url:
      type: "url"
      label: "URL"
      preview: "https://example.com"
{{ link }}
<a href="{{ url }}">{{ label }}</a>

In case you need a precise variable that can only contain one specific value or type, use settings.

If you have to print your variable in the HTML attribute, use settings.

Keep slots independent from each others

When working with two sequences of slots, don’t get an item from one using the index of the other one. Because:

  • It may break if not the same number of items
  • It will be hard to use in Drupal backoffice
Don’t (in YML) 🚫 Don’t (in Twig) 🚫
fields:
    images:
      type: render
      label: Images
      preview:
        - theme: image
          uri: assets/image-1.png
        - theme: image
          uri: assets/image-2.jpg
    labels:
      type: render
      label: Labels
      preview:
        - "Portrait blue"
        - "Landscape yellow"
{% for image in images %}
<li class="mdc-image-list__item">
  {{ image }}
  {% set index = loop.index0 %}
  {% if labels[index] %}
  <span class="mdc-image-list__label">{{ labels[index] }}</span>
  {% endif %}
</li>
{% endfor %}

A solution may be to create a sub-component.

Pick the props types according to the data

Props are currently managed by ui_patterns_settings module.

Following props types are supported:

Type Data Notes
textfield
number
attributes
token
checkbox
checkboxes
radio
select
url
group
media_library
colorwidget
coloriswidget
menu

This props will be reorganized around clearer data types in UI Patterns 2.x

Avoid props paradoxes by leveraging orthogonality

Do ✓ Don’t 🚫
responsive:
      type: "select"
      label: "Responsive"
      options:
        offcanvas-sm: "Hide below small"
        offcanvas-md: "Hide below medium"
        offcanvas-lg: "Hide below large"
        offcanvas-xl: "Hide below extra large"
        offcanvas-xxl: "Hide below extra extra large"
    backdrop:
      type: "select"
      label: "Backdrop"
      description: "When backdrop is set to static, the offcanvas will not close when clicking outside of it."
      options:
        "false": "No backdrop"
        static: "Static"
    scroll:
      type: "boolean"
      label: "Body scrolling"
      description: "By default, body scrolling is disabled."
      preview: false

Use token with props

The plugin source system of UI patterns does not allow mapping between Drupal API (field properties in field formatters, regions in layouts, etc.) and component props, only component slots. But fortunately, UI Patterns Settings has token support and exposes mechanisms to inject Drupal values in a prop.

This allows you to control a prop value based on a field value by adding “allow_token: true” to your setting.

This is in addition to the override mechanism in UI Patterns Settings which is on a field storage configuration, but this one requires field type to match setting type so not usable for some types.

Do, in YML ✓
settings:
    aria_label:
      type: "textfield"
      label: "Aria label"
      description: "Name of the progress bar for assistive technology."
      allow_token: true

In UI Patterns 2.x, it will be possible to map Drupal API to props without the needs of tokens.

UI Patterns Settings 2.x

Do, keep the recursive structure and the variables names
{{ menus.menu_links(items, attributes, 0) }}

{% macro menu_links(items, attributes, menu_level) %}
  {% if items %}
    {% if menu_level == 0 %}
      <ul{{ attributes }}>
    {% else %}
      <ul>
    {% endif %}
    {% for item in items %}
      <li{{ item.attributes }}>
        {{ link(item.title, item.url) }}
    	  {% if item.below %}
      	    {{ _self.menu_links(item.below, attributes, menu_level + 1) }}
    	  {% endif %}
  	</li>
    {% endfor %}
   </ul>
  {% endif %}
{% endmacro %}

Definitions: Previews

No useless lists of renderables in slots

Do ✓ Do ✓ Don’t 🚫
preview:
  - type: pattern_preview
    id: card
  - type: pattern_preview
    id: card
preview:
  type: pattern_preview
  id: card
preview:
  - type: pattern_preview
    id: card

Components templates: Generic Twig advices

Be careful with looping

Looping on a slot is a risky thing, because there is no way of knowing if an array is a renderable or a list of renderable. It is not possible to use an iterable test because render arrays are considered as iterable.

For example, this will break if 'slides' value is not a list but a single renderable:

<div class="carousel-inner" role="listbox">
  {% for slide in slides %}
    <div class="carousel-item">
      {{ slide }}
    </div>
  {% endfor %}
</div

It is also possible to get two or more nested loops (example: a table where every cell is a slot).

There is no magical solution right now, just be careful.

The solution may come from the Twig project: https://github.com/twigphp/Twig/issues/3828

[soon] Replace shorthand ternary operators by the default filter

[soon] Don't nest or chain ternary conditions

[soon] No useless tests: != '', is empty, is defined

[soon] Don’t use default filter with boolean

[soon] Always use default() filter with random function()

Components templates: Drupal & UI Suite specific advices

UI Patterns templates are more strict than usual Drupal templates.

Always use the attribute object

An ‘attributes’ variable is automatically injected in every component template, it is a Drupal Attribute object: https://www.drupal.org/docs/8/theming-drupal-8/using-attributes-in-templates 

It is useful for Drupal render API.

Use it directly in the component wrapper:

Do ✓
<a{{ attributes.addClass('ds-btn') }}>
  {% if icon_attributes %}<i{{ icon_attributes }}></i>{% endif %}
  {{ label }}
</a>
Do ✓
<div{{ attributes.addClass('ds-quote') }}>
  <i class="ds-quote__icon ds-ico ds-ico-quote-sign"></i>
  <div class="ds-quote__text">
    {{ value }}
  </div>
</div>
Don’t (missing attributes object and the BEM modifier is hardcoded) 🚫
<ul class="ds-nav ds-nav--buttons">
  {% for item in items %}
  ...
  {% endfor %}
</ul>
Don’t (using attribute object twice, or outside the wrapper) 🚫
<div{{ attributes.addClass('ds-quote') }}>
  <i class="ds-quote__icon ds-ico ds-ico-quote-sign"></i>
  <div {{ attributes.addClass('ds-quote__text') }}>
    {{ value }}
  </div>
</div>

Don’t rely on slots typing

With UI Patterns 1.x, slot types are only used for documentation purposes. 

There is no shared list of types, but it is common to find those ones:

  • “Render” if the slot is expecting any renderable or already rendered markup. 
  • “array”  if the slot is expecting a sequence of renderbales
  • “Text” if the slot is expecting a raw text or any printable scalar

Never do some tests and conditions on slots types. The component should be designed so that you can inject anything in your slots.

Slots typing will be removed from UI Patterns 2.x.

Don’t rely on props typing

Props are strongly typed, but the type must not be tested in templates or alter processing. Because:

  • The type is set in component definitions and must not change
  • There is no safe and reliable way of testing the types in Twig. Source: https://twig.symfony.com/doc/1.x/tests/index.html 
  • Type checking to filter out unsafe values must be done before the template execution

Slots values are totally free and optional

Your component must survive with any components injected in its slots. As if you use a randomizer.

Of course, it doesn’t have to look “clean”, but it doesn’t have to break.

This has also an impact on filters. Filters can be really powerful and save us a lot of time. But they are dependent on existing data structures and can crash if applied to another.

So, most of the filters are forbidden on slots. Except a few filters identified a few slots friendly filters, and called “slot filters” (add_class, set_atributes…), don’t use filters on slots.

Use default values when props are mandatory

Error proof: nothing  is expected to be mandatory.  Always test your components with only empty values. Of course, it doesn’t have to look “clean”, but it doesn’t have to break.

If you absolutely need a value when processing in the templates, use |default() filter.

Do ✓ Don’t 🚫
{{ set heading_level = heading_level|default(3) }}
<h{{ heading_level }}>{{ title}}</h{{ heading_level }}>
<h{{ heading_level }}>{{ title}}</h{{ heading_level }}>

Keep it stateless

Components are inert and stateless, waiting to be used. They always produce the same result when called with the same slots & props.

Components are not pulling data from Drupal, they are receiving data.

Do ✓ Don’t 🚫
Twig Drupal route

Don’t use complex objects or Drupal API in a template

Need to be reusable. Don't inject objects (PHP in twig, objects with methods in JS) but only primitives. 

Drupal agnostic, except attributes objects https://www.drupal.org/node/2513632 

No entities. No objects. Only simple values or flat arrays.

For theme components to be truly stand-alone, they should be independent of Drupal. It means that the components are not restricted by Drupal's data model or render API.

Don’t 🚫
{% if node.field_displayed_title is not empty %}
  <h1 class="h1">{{ node.field_displayed_title.value|raw }}</h1>
{% endif %}
Don’t 🚫
{% if content.dropdown.0['#children'].0 %}
  <a href="#" class="ds-btn ds-btn--murmur"><i class="ds-ico ds-ico-dropdown-chevron-mini"></i>View features</a>
{% endif %}
Do ✓
{% if displayed_title %}
  <h1 class="h1">{{ displayed_title }}</h1>
{% endif %}

And of course, it's even worse if this complex object is a configurable field. Don’t!

Keep components independent of each other

Do ✓ Don’t 🚫
{{ pattern() }}
{{ include() }}

Let the render arrays do this job of imbrication. So, as the front developer you have to create nestable templates, but the template nesting is the job of the back developer & the site builders, not yours.

Don’t (in templates) 🚫
{% include directory ~ '/templates/includes/other-events.html.twig' with {'content': {'otherEvent': other_events}} %}

[soon] For unavoidable hardcoded dependencies, use pattern() function

Don’t hardcode ID values

Components must be repeatable and nestable, so different instances of the component can’t share the same ID. 

Instead, you can:

  • Generate the ID with the Twig random() function
  • Inject the ID from a prop 
Do ✓ Don’t 🚫
{{ attributes.setAttribute("id", "foo") }}
<span id="foo">Lorem ipsum</span>

Don't use attribute() function

https://www.drupal.org/project/ui_suite_bootstrap/issues/3382230

Only string literals should be passed to t()

You should never use t() to translate variables

Components templates: What is allowed? Forbidden?

Tags

Tags control logic in templates.

Tags Provider Status for UI Suite Notes
apply twig3 Good ✓
autoescape twig3 Good ✓
cache twig3 Bad 🚫 Cache management. Overkill
deprecated twig3 Good ✓
do twig3 Careful ⚠ Too niche
flush twig3 Bad 🚫 Cache management. Overkill
for twig3 Good ✓
if twig3 Good ✓
macro twig3 Good ✓
plural drupal10 Careful ⚠ To be used with trans
set twig3 Good ✓
trans drupal10 Careful ⚠ Stateful. Calls to the database. But useful.
verbatim twig3 Good ✓
with twig3 Good ✓

“Multitemplates” tags are forbidden, because they create dependencies in between components:

Tags Provider Status for UI Suite Notes
block twig3 Bad 🚫 Bad architecture. Component calling components.
embed twig3 Bad 🚫 Bad architecture. Component calling components.
extends twig3 Bad 🚫 Bad architecture. Component calling components.
from twig3 Bad 🚫 Macro related. Bad architecture because side effects
include twig3 Bad 🚫 Bad architecture. Component calling components.
import twig3 Bad 🚫 Macro related. Bad architecture because side effects
sandbox twig3/sandbox Bad 🚫 Bad architecture. Component calling components.
use twig3 Bad 🚫 Bad architecture. Component calling components.

Slots filters

Slots friendly filters: they don't break if not the expected value type.

Slots filters Provider Status for UI Suite Filter input Notes
add_suggestion drupal10 Bad 🚫 Mapping Bad architecture. Drupal legacy.
set_attribute ui_patterns Good ✓ Mapping, sequences
add_class ui_patterns Good ✓ Mapping, sequences
render drupal10 Bad 🚫 Mapping, sequences Early rendering is harmful

Props filters

They are applied to values to modify them.

Props filters Provider Status for UI Suite Filter input Notes
abs twig3 Good ✓ number
batch twig3 Good ✓ sequence
capitalize twig3 Good ✓ string
clean_class drupal10 Good ✓ string
clean_id drupal10 Good ✓ string
column twig3 Good ✓ sequence, mapping
convert_encoding twig3 Careful ⚠ string Needs specific PHP extension
date twig3 Bad 🚫 \DateTimeInterface|\DateInterval|string Manipulate objects
date_modify twig3 Bad 🚫 \DateTimeInterface|string Manipulate objects
default twig3 Good ✓ any
drupal_escape drupal10 Good ✓ mixed
e twig3/Escaper Careful ⚠ string Alias of escape
escape twig3/Escaper Careful ⚠ string
filter twig3 Careful ⚠ sequence, mapping Functional programming, so complex
first twig3 Good ✓ sequence, mapping, string
format twig3 Good ✓ string
format_date drupal10 Bad 🚫 number Business related. Load config entities.
join twig3 Good ✓ sequence
json_encode twig3 Good ✓ any
keys twig3 Good ✓ sequence, mapping
last twig3 Good ✓ sequence, mapping, string
length twig3 Good ✓ sequence, mapping, string
lower twig3 Good ✓ string
map twig3 Careful ⚠ sequence, mapping Functional programming, so complex
merge twig3 Good ✓ sequence, mapping
nl2br twig3 Good ✓ string
number_format twig3 Good ✓ number
placeholder drupal10 Good ✓ string
raw twig3/Escaper Careful ⚠ any May be harmful.
reduce twig3 Careful ⚠ sequence, mapping Functional programming, so complex
replace twig3 Good ✓ string
reverse twig3 Good ✓ sequence, mapping, string
round twig3 Good ✓ number, string
safe_join drupal10 Good ✓ sequence
slice twig3 Good ✓ sequence, mapping, string
sort twig3 Good ✓ sequence
spaceless twig3 Good ✓ string
split twig3 Good ✓ string
striptags twig3 Good ✓ string
t drupal10 Careful ⚠ string Stateful. Calls to database. But useful.
title twig3 Good ✓ string
trans drupal10 Careful ⚠ string Stateful. Calls to database. But useful.
trim twig3 Good ✓ string
upper twig3 Good ✓ string
url_encode twig3 Good ✓ string, mapping

Other filters are available in Twig extensions. Avoid them because we don’t know if the installations are installed, some may depend on PHP modules.

Functions

They can be called in any place where an expression is valid to generate data. Functions are not used a lot in UI Suite, to keep the components self-contained.

Functions Provider Status for UI Suite Notes
active_theme drupal10 Bad 🚫 Contextful. Related to Drupal installation.
active_theme_path drupal10 Bad 🚫 Contextful. Related to Drupal installation.
attach_library drupal10 Bad 🚫 Bad architecture. The attachment is not visible.
attribute twig3 Bad 🚫
constant twig3 Bad 🚫 Contextful. Related to Drupal installation.
create_attribute drupal10 Good ✓
cycle twig3 Good ✓
date twig3 Good ✓
file_url drupal10 Bad 🚫
link drupal10 Careful ⚠ Stateful. PHP URL object .
max twig3 Good ✓
min twig3 Good ✓
path drupal10 Bad 🚫 Contextful. Related to Drupal installation.
random twig3 Good ✓
range twig3 Good ✓
render_var drupal10 Bad 🚫 Early rendering is harmful
url drupal10 Bad 🚫 Contextful. Related to Drupal installation.

Of course, all functions calling templates from templates must be avoided:

Functions Provider Status for UI Suite Notes
block twig3 Bad 🚫 Bad architecture. Component calling components.
include twig3 Bad 🚫 Bad architecture. Component calling components.
parent twig3 Bad 🚫 Bad architecture. Component calling components.
pattern ui_patterns Careful ⚠ Bad architecture. Component calling components. But if needed, it is the best way to do it.
pattern_preview ui_patterns Bad 🚫 Not expected in "real" components.
source twig3 Bad 🚫 Bad architecture

Tests

They are like filters but a different syntax is used to invoke them and they have to return boolean values. For instance the expression if foo is odd check if the value is indeed an odd number.

Tests Provider Status Notes
constant twig3 Bad 🚫 Contextful. Related to Drupal instalaltion.
defined twig3 Bad 🚫 Not needed in Drupal because strict_variables = false
divisible by twig3 Good ✓
empty twig3 Bad 🚫 The exact same as just testing the variable
even twig3 Good ✓
iterable twig3 Careful ⚠ Return true for mapping and sequence
null twig3 Careful ⚠
odd twig3 Good ✓
same as twig3 Bad 🚫 equivalent to === in PHP, too strict

Preview templates

[soon] Use preview templates for library pages

Don’t use full namespaced inclusion

Sometimes, often for component preview templates, it is needed to “include” a component template (example with “include”, but also applies to “extends” and others). Do not use a fully namespaced path otherwise if you override the template in your sub-theme, your overridden template will not be used.

Note: this logic also applies to any kind of Twig template inclusion.

Do Don’t
{{ include('pattern-card-body.html.twig') }}
{{ include('@ui_suite_bootstrap/patterns/card_body/pattern-card-body.html.twig') }}

Presenter templates & Drupal default templates

Presenter templates are standard Drupal templates that:

Example, a “normal” template is:

{% if subtitle %}
 <h3 class="callout__subtitle">{{ subtitle }}</h3>
{% endif %}

And a presenter template is:

{{ pattern('menu', {
  'items': items,
  'attributes': attributes.addClass('ttl-menu--arborescent'),
})}}

pattern() Twig function is provided by the UI patterns module.

More about presenter templates: https://www.mediacurrent.com/blog/accommodating-drupal-your-components/

Don’t use presenter templates when site building is possible

Presenter templates are a clever trick, however they hurt the site building because everything which is normally set in the display settings by the site builder has to be done in a Twig file by the developer.

However, there are some cases when site building is not easily possible. For example, rendering a menu. Menu are config entities without configurable displays.

Or rendering a content entity where the configurable display has no admin UI:

Content entities with display mode UI Content entities without DM UI
  • node / Node
    /admin/structure/types/manage/{bundle}/display
  • block_content / BlockContent
    /admin/structure/block/block-content/manage/{bundle}/display
  • comment / Comment
    /admin/structure/comment/manage/{bundle}/display
  • media / Media
    /admin/structure/media/manage/{bundle}/display
  • taxonomy / Term
    /admin/structure/taxonomy/manage/{bundle}/overview/display
  • user / User
    /admin/config/people/accounts/display
  • aggregator / Feed
  • aggregator / Item
  • contact / Message
  • menu_link_content / MenuLinkContent
  • file / File
  • shortcut / Shortcut

Clean default templates

Template overriding is useful to clean some cruft from templates provided by core or contrib modules.

The node.html.twig template is a good example, because it carries a lot of legacy junk, like a title base field display condition based on view mode name (!) and a poor man submitted by.

It is better to keep those templates as lean as possible, and to push the complexity to layouts and other display modes plugins.

For example:

Provider

Template

Before

After

node node.html
<article{{ attributes }}>
 {{ title_prefix }}
 {% if not page %}
  <h2{{ title_attributes }}>
   <a href="{{ url }}" rel="bookmark">{{ label }}</a>
  </h2>
 {% endif %}
 {{ title_suffix }}
 {% if display_submitted %}
  <footer>
   {{ author_picture }}
   <div{{ author_attributes }}>
	{% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %}
	{{ metadata }}
   </div>
  </footer>
 {% endif %}
 <div{{ content_attributes }}>
  {{ content }}
 </div>
</article>
<div{{ attributes }}>
 {{ title_prefix }}
 {{ title_suffix }}
  {{ content }}
 <div{{ content_attributes }}>
  {{ content }}
 </div>
</div>
taxonomy taxonomy-term.html
<div{{ attributes }}>
 {{ title_prefix }}
 {% if not page %}
  <h2><a href="{{ url }}">{{ name }}</a></h2>
 {% endif %}
  {{ title_suffix }}
 {{ content }}
</div>
<div{{ attributes }}>
{{ title_prefix }}
{{ title_suffix }}
  {{ content }}
</div>
media media.html
<div{{ attributes }}>
  {{ title_suffix.contextual_links }}
  {{ content }}
</div>
<div{{ attributes }}>
  {{ content }}
</div>

No need to override that

comment comment.html
<article{{ attributes.addClass('js-comment') }}>
  <mark class="hidden" data-comment-timestamp="{{ new_indicator_timestamp }}"></mark>
  <footer>
	{{ user_picture }}
	<p>{{ submitted }}</p>
	{% if parent %}
  	<p class="visually-hidden">{{ parent }}</p>
	{% endif %}
	{{ permalink }}
  </footer>
  <div{{ content_attributes }}>
	{% if title %}
  	{{ title_prefix }}
  	<h3{{ title_attributes }}>{{ title }}</h3>
  	{{ title_suffix }}
	{% endif %}
	{{ content }}
  </div>
</article>
<div{{ attributes }}>
  {{ content }}
</div>

No idea yet, because currently not overridden on projects, will come on ui_suite_bootstrap. I guess same a node. Currently it removes too much.

Some markup of components or some utilities style expect specific markup with direct children like flex feature. Currently when trying to use those components in lists the wrappers inside templates like field.html.twig and node.html.twig will interfere with the expected markup. So sometimes even the wrappers need to be removed with templates override.

Use the Entity View Display Template Suggestions module to be able to remove the wrapper of some entity displays. If the theme provides templates with bare minimum markup like just the "content" variable printed, for content entities with the module you will be able to remove the wrapper with configuration.

Help improve this page

Page status: Needs review

You can: