Skip to content

Best practices and anti patterns

Best Practices & Anti-Patterns

Component Naming

Do: Use business-agnostic names: Card, Hero, Accordion, Alert. Do not: Use business-specific names: EventCard, NewsSlideshow, ProductHero. Why: Components should be reusable across content types. Business-specific names create artificial coupling and reduce reuse.

Slot vs Prop Decision

Content Type Use Reason
Block output, rendered markup Slot Renderables need the render pipeline
Plain text, numbers, booleans Prop Strictly typed, validated by JSON Schema
Images with URL + alt + title Slot Complex renderables with wrapper elements should not be prop-drilled
Navigation links Prop (links type) Structured data with a defined schema
CSS classes, HTML attributes Prop (attributes type) Key-value data, not renderables

Avoid Prop Drilling

Anti-pattern: Defining props in a parent component solely to pass them to a child component.

# BAD: parent has image_url and image_alt just to pass to child
props:
  properties:
    image_url:
      type: "string"
    image_alt:
      type: "string"

Better: Use a slot and let the child component be configured independently:

# GOOD: image is a slot, filled by a nested image component
slots:
  image:
    title: "Image"

Why: Prop drilling couples parent and child component schemas. When the child changes, the parent must change too. Slots provide decoupling.

Avoid Prop Paradoxes

Design orthogonal props that do not create incompatible states:

# BAD: is_link and url create a paradox (what if is_link=true but url is empty?)
props:
  properties:
    is_link:
      type: "boolean"
    url:
      type: "string"

# GOOD: use a single optional URL prop (empty = not a link)
props:
  properties:
    url:
      "$ref": "ui-patterns://url"

Use |default() in Twig

Always handle empty/missing values in templates:

{# GOOD #}
<h2>{{ title|default('Untitled') }}</h2>
<div class="card card--{{ variant|default('default') }}">

{# BAD: breaks with empty values #}
<h2>{{ title }}</h2>
<div class="card card--{{ variant }}">

Use Drupal Attributes

Always apply the attributes prop to the component wrapper element:

<div{{ attributes }}>
  {{ content }}
</div>

Why: Drupal modules (contextual links, quick edit, translation, SEO tools) inject attributes into the wrapper. Without {{ attributes }}, these features break silently.

Mark Internal Components

Sub-components that only make sense as children of a parent should be clearly marked:

name: "Carousel Item"
group: "Carousel"
tags: ["Internal"]
# In description or metadata, indicate this is internal

When NOT to Use UI Patterns

Situation Better Approach
Component used only by developers in Twig templates Use SDC directly
Simple text replacement in a theme Use preprocess hooks or Twig
One-off page layout Use Layout Builder with core layouts
Content types with fixed, predictable structure Field formatters may be simpler than component wrappers

Why: UI Patterns adds an abstraction layer (source plugins, form generation, context resolution). This is valuable when site-builders need configurability, but overhead when developers control all rendering.

Common Mistakes

Mistake Why It Is Wrong
Creating one component per content type Components should be content-agnostic. A "Card" works for articles, events, and products.
Hardcoding component IDs in Twig templates Components should be nested through slots or render arrays, not Twig include(). Hardcoded IDs prevent replacement/override. If necessary, use include() with with_context = false.
Hardcoding HTML IDs in components If a component is used multiple times on a page, duplicate IDs break HTML validity and accessibility. Generate IDs with random() or accept them as props.

See Also