SDC Rendering Decision
When to Use
Use UI Patterns wiring when block types are few and simple — it's the intended use case for
ui_patterns_component_per_itemand requires no template files. Use Twiginclude()when you have many block types, need per-instance variants via Layout Builder block configuration, or have components reading 5+ sibling fields.
Decision
| If your project... | Use... | Why |
|---|---|---|
| Has 1-3 simple block types, single variant each | UI Patterns wiring | Native, no template files; matches the formatter's intended use case |
| Has 5+ block types with varied complexity | Twig include() pattern |
Less repetition, per-instance variants supported, clearer diffs |
Needs per-instance variants (LB block configuration via Plus Suite or BlockPropertiesEvent) |
Twig include() pattern |
UI Patterns formatters cannot read LB block configuration |
Generated by daisyui-sdc-generator from React+Tailwind |
Twig include() pattern |
Cleaner generator output, easier to validate, easier to hand-tune |
| Single SDC with one prop, used in field formatter context | UI Patterns wiring | The intended use case for ui_patterns_component_per_item |
| Complex composition (many sibling fields → one component, many slots) | Twig include() pattern |
Avoids 3-level nested entity_field config per slot |
| Must NOT have per-block-type templates (pure config-only shop) | UI Patterns wiring | No alternative |
Pattern
Trade-offs Matrix
| Aspect | UI Patterns wiring | Twig include() to SDC |
|---|---|---|
| Config complexity | High — 3-level nesting via entity_field for sibling fields |
Low — single Twig file per block |
| Per-block-type maintenance | Each block type = deep YAML edit | Each block type = ~10-line Twig template |
| Per-instance variants | Hard — requires entity field OR custom source plugin | Trivial — configuration.variant\|default('default') |
| Inline editing (Plus Suite Edit+) | Native — fields render through formatters | Requires preserving field render arrays ({{ content.field_x }} works; raw strings don't) |
| Diff readability | Poor — dynamic colon-keyed nested keys | Good — clear field-to-prop mapping |
| Learning curve | Steep — source plugins, context switchers, scoping | Low — one {% include %} syntax |
Twig include() Pattern
{# templates/block--inline-block--hero.html.twig #}
{% set variant = configuration.hero_variant|default('default') %}
{% include 'mytheme:hero' with {
variant: variant,
heading: content.field_heading[0]['#context'].value,
subheading: content.field_subheading[0]['#context'].value,
body: content.field_body|render,
image: content.field_image,
cta_primary_text: content.field_cta_primary[0]['#title'],
cta_primary_url: content.field_cta_primary[0]['#url'],
} only %}
UI Patterns Wiring Pattern
# core.entity_view_display.block_content.hero.default.yml
content:
field_heading:
type: ui_patterns_component_per_item
settings:
ui_patterns:
component_id: 'mytheme:hero'
variant_id:
source_id: select
source:
value: default
slots:
heading:
sources:
- source_id: 'field_property:block_content:field_heading:value'
source:
type: value
_weight: '0'
subheading:
sources:
- source_id: entity_field
source:
derivable_context: 'field:block_content:hero:field_subheading'
'field:block_content:hero:field_subheading':
value:
sources:
- source_id: 'field_property:block_content:field_subheading:value'
source:
type: value
_weight: '0'
_weight: '0'
# ...repeat entity_field nesting for each additional sibling slot
Recommended Default
For new projects with multiple block content types and any per-instance variant requirement: default to the Twig include() pattern. Use UI Patterns wiring for genuine field-formatter use cases (a single field rendered as a single component instance, e.g., a tags field rendered as badges).
For projects already running pure UI Patterns wiring at scale and willing to live with the trade-offs: stay consistent. Mixing both patterns inconsistently is worse than either alone.
Common Mistakes
- Wrong: Defaulting to UI Patterns wiring without considering scale → Right: Works fine for 2 block types; becomes unmaintainable at 10+. Each new block type adds deeply-nested view display config; PR diffs are hard to review.
- Wrong: Twig include() but passing rendered strings instead of field render arrays → Right: Pass
content.field_x(render array), notnode.field_x.value(string) — Plus Suite Edit+ inline editing attachesdata-edit-plus-*attributes to render arrays only. - Wrong: Mixing both patterns within the same block type → Right: Pick one path per block type. Half props from view display, half from template is hard to debug and extend.
- Wrong: Using UI Patterns wiring for blocks that need LB configuration variants → Right: Pure-wiring path cannot read
configuration.{prop}from LB block config. Switch to template pattern or store the variant as an entity field.
See Also
- drupal/ui-patterns/field-formatters —
ui_patterns_component_per_itemreference and source scoping - drupal/ui-patterns/source-plugins —
entity_fieldcontext switcher - drupal/ui-patterns/variants — Per-instance variants in Layout Builder
- drupal/plus-suite/custom-block-types —
BlockPropertiesEventfor adding LB configuration fields