On this page
- Introduction
- Definitions: Components and variants
- Stay business agnostic while naming component
- Use variants when needed
- Always set a default variant
- If possible, name variants according to markup
- [soon] Add layout icon maps
- Definitions: Slots & props
- Use noun or adjectives in slots and props names
- Keep components flat by avoiding nested slots
- Make sub components easily spottable in library
- Don’t put renderables in props
- Use slots instead of props any time possible.
- Keep slots independent from each others
- Pick the props types according to the data
- Avoid props paradoxes by leveraging orthogonality
- Use token with props
- [soon] For menus, breadcrumbs, links and pager, use the menu type
- Definitions: Previews
- [soon] Avoid link render element in slots
- No useless lists of renderables in slots
- Components templates: Generic Twig advices
- Be careful with looping
- [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
- Always use the attribute object
- Don’t rely on slots typing
- Don’t rely on props typing
- Slots values are totally free and optional
- Use default values when props are mandatory
- Keep it stateless
- Don’t use complex objects or Drupal API in a template
- Keep components independent of each other
- [soon] For unavoidable hardcoded dependencies, use pattern() function
- Don’t hardcode ID values
- Don't use attribute() function
- Only string literals should be passed to t()
- Components templates: What is allowed? Forbidden?
- Tags
- Slots filters
- Props filters
- Functions
- Tests
- Preview templates
- [soon] Use preview templates for library pages
- Don’t use full namespaced inclusion
- Presenter templates & Drupal default templates
- Don’t use presenter templates when site building is possible
- Clean default templates
Best Practices
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 🚫 |
|
|
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 ✓ |
|
And then add the class in the Twig template based on the variant name:
| Do ✓ |
|
If a variant name must be split in many HTML class names:
| Do ✓ |
|
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 ✓ |
|
|
| Don't 🚫 |
|
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 ✓ |
|
| Do, in carousel_item.ui_patterns.yml ✓ |
|
| Don’t, in carousel.ui_patterns.yml Usable with custom PHP code, but not for site building. 🚫 |
|
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) 🚫 |
|
|
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 🚫 |
|
|
|
|
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) 🚫 |
|
|
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 🚫 |
|
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 ✓ |
|
In UI Patterns 2.x, it will be possible to map Drupal API to props without the needs of tokens.
[soon] For menus, breadcrumbs, links and pager, use the menu type
UI Patterns Settings 2.x
| Do, keep the recursive structure and the variables names |
|
Definitions: Previews
[soon] Avoid link render element in slots
No useless lists of renderables in slots
| Do ✓ | Do ✓ | Don’t 🚫 |
|
|
|
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 %}
</divIt 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 ✓ |
|
| Do ✓ |
|
| Don’t (missing attributes object and the BEM modifier is hardcoded) 🚫 |
|
| Don’t (using attribute object twice, or outside the wrapper) 🚫 |
|
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 🚫 |
|
|
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 🚫 |
|
| Don’t 🚫 |
|
| Do ✓ |
|
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 🚫 |
|
|
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) 🚫 |
|
[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 🚫 |
|
|
|
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 |
|
|
Presenter templates & Drupal default templates
Presenter templates are standard Drupal templates that:
- transform data received from Drupal
- use Twig include and embed tags to include one or more components and pass the transformed data.
- should be totally free of markup, and this can be achieved by using the Twig extends tag.
- use theme suggestions to plug itself to data model: https://www.drupal.org/docs/8/theming/twig/twig-template-naming-conventions
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 |
|
|
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 |
|
|
| taxonomy | taxonomy-term.html |
|
|
| media | media.html |
|
No need to override that |
| comment | comment.html |
|
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
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion