Skip to content
HELiX

SDC Variants

apps/docs/src/content/docs/drupal/sdc/variants Click to copy
Copied! apps/docs/src/content/docs/drupal/sdc/variants

SDC variants let site builders and content editors choose between visual treatments of the same content pattern — an editorial card vs. a featured card, a compact staff profile vs. a full one. This guide documents three variant strategies that work together in HELiX-based SDCs.


The simplest variant mechanism passes a variant value directly to a HELiX component’s variant attribute. The component handles all visual changes internally through Shadow DOM styles and CSS custom properties.

components/article-teaser/article-teaser.component.yml
props:
type: object
properties:
card_variant:
type: string
title: Card Visual Style
enum: [default, featured, compact]
default: default
description: Passed to hx-card's variant attribute.
{# components/article-teaser/article-teaser.twig #}
<hx-card variant="{{ card_variant|default('default') }}">
{# ... #}
</hx-card>
{# node--article--featured.html.twig — uses featured card #}
{% include 'mytheme:article-teaser' with {
title: node.label,
url: url,
card_variant: "featured",
image: content.field_hero_image,
} only %}
{# node--article--teaser.html.twig — uses default card #}
{% include 'mytheme:article-teaser' with {
title: node.label,
url: url,
card_variant: 'default',
} only %}

Strategy 2: CSS Classes for Layout Variants

Section titled “Strategy 2: CSS Classes for Layout Variants”

When a variant changes layout (not just color/shadow), add a CSS class to the SDC’s wrapper element and define the layout variant in the SDC’s CSS file.

components/staff-profile/staff-profile.component.yml
props:
type: object
properties:
layout:
type: string
title: Card Layout
enum: [card, horizontal, minimal]
default: card
description: Controls card layout orientation.
{# components/staff-profile/staff-profile.twig #}
<div class="staff-profile staff-profile--{{ layout|default('card') }}">
<hx-card variant="default">
{# ... #}
</hx-card>
</div>
components/staff-profile/staff-profile.css
/* Default: stacked (photo above content) */
.staff-profile--card .staff-profile__photo {
display: flex;
justify-content: center;
padding-bottom: var(--hx-space-4);
}
/* Horizontal: photo beside content */
.staff-profile--horizontal {
display: grid;
grid-template-columns: 80px 1fr;
gap: var(--hx-space-4);
align-items: start;
}
.staff-profile--horizontal .staff-profile__photo {
grid-row: 1 / 3;
padding: 0;
}
/* Minimal: name and role only, no photo */
.staff-profile--minimal hx-avatar {
display: none;
}

Strategy 3: Variant-Specific Templates (Twig Template Suggestions)

Section titled “Strategy 3: Variant-Specific Templates (Twig Template Suggestions)”

For variants with substantially different markup structure, use Drupal’s template suggestion system to load a separate Twig file.

Add template suggestions in a preprocess function

Section titled “Add template suggestions in a preprocess function”
/**
* Implements hook_preprocess_node().
*/
function mytheme_preprocess_node(array &$variables): void {
$node = $variables['node'];
$view_mode = $variables['view_mode'];
// Add suggestion: node--[bundle]--[view-mode].html.twig
// Already provided by Drupal, but you can add SDC-specific suggestions:
$variables['theme_hook_suggestions'][] =
'node__' . $node->bundle() . '__' . $view_mode . '__helix';
}
Section titled “Separate template for a “featured” variant”
{# node--article--featured--helix.html.twig #}
{# Uses a full-bleed card layout with larger media area #}
{% include 'mytheme:article-featured' with {
title: node.label,
url: url,
summary: content.body[0]['#text']|striptags|trim|slice(0, 400),
category: node.field_category.entity.label,
image: content.field_hero_image,
card_variant: "default",
} only %}

Requires a separate article-featured SDC for the full-bleed layout.


Different editorial themes (healthcare, research, editorial) can override the variant prop values exposed by SDCs to produce context-appropriate styling without changing SDC code.

components/article-teaser/article-teaser.component.yml
props:
type: object
properties:
theme_context:
type: string
title: Theme Context
enum: [editorial, healthcare, research]
default: editorial

Derive component variant from theme context

Section titled “Derive component variant from theme context”
{# components/article-teaser/article-teaser.twig #}
{% set variant_map = {
'editorial': 'default',
'healthcare': 'default',
'research': 'featured',
} %}
<hx-card variant="{{ variant_map[theme_context]|default('default') }}">
{# ... #}
</hx-card>

Set theme context in a preprocess function

Section titled “Set theme context in a preprocess function”
/**
* Implements hook_preprocess_node().
*/
function mytheme_preprocess_node(array &$variables): void {
$node = $variables['node'];
// Map content type to theme context.
$context_map = [
'article' => 'editorial',
'clinical_resource' => 'healthcare',
'research_publication' => 'research',
];
$variables['helix_theme_context'] = $context_map[$node->bundle()] ?? 'editorial';
}
{# node--article--teaser.html.twig #}
{% include 'mytheme:article-teaser' with {
title: node.label,
url: url,
theme_context: helix_theme_context,
} only %}

Responsive variants change component presentation based on viewport. Use CSS custom properties and container queries rather than separate variant props.

components/article-teaser/article-teaser.css
/* Enable container queries on the SDC root */
.article-teaser-container {
container-type: inline-size;
container-name: article-teaser;
}
/* Compact layout in small containers */
@container article-teaser (max-width: 360px) {
hx-card {
--hx-card-padding: var(--hx-space-3);
}
.article-teaser__summary {
display: none;
}
}
/* Full layout in wider containers. hx-card's exposed image hook is
`--hx-card-image-aspect-ratio` (not a fixed `image-height` token).
For pixel-pinned image heights, style the slotted image yourself —
the slotted content lives in light DOM and inherits page CSS. */
@container article-teaser (min-width: 600px) {
hx-card {
--hx-card-image-aspect-ratio: 16 / 9;
}
hx-card [slot='image'] img {
height: 240px;
width: 100%;
object-fit: cover;
}
}
{# Wrap in a container-query root #}
<div class="article-teaser-container">
{% include 'mytheme:article-teaser' with { ... } only %}
</div>

For cases where server-side logic controls the variant (e.g., different layouts for mobile REST API responses vs. full page renders):

props:
type: object
properties:
compact:
type: boolean
title: Compact Mode
description: Reduce card density for narrow contexts.
default: false
<hx-card
variant="{{ compact ? 'default' : 'default' }}"
class="{{ compact ? 'article-teaser--compact' : '' }}"
>
{% if not compact and summary %}
<p>{{ summary|escape }}</p>
{% endif %}
</hx-card>

A production SDC typically combines all three strategies:

component.yml
props:
properties:
# Strategy 1: HELiX component variant
card_variant:
type: string
enum: [default, featured, compact]
default: default
# Strategy 2: Layout CSS class variant
layout:
type: string
enum: [card, horizontal]
default: card
# Strategy 3: Theme context (drives template selection or variant mapping)
theme_context:
type: string
enum: [editorial, healthcare, research]
default: editorial
# Responsive: compact flag
compact:
type: boolean
default: false
{% set resolved_variant = card_variant|default('default') %}
{% if theme_context == 'healthcare' and card_variant is not defined %}
{% set resolved_variant = 'default' %}
{% endif %}
<div class="article-teaser article-teaser--{{ layout|default('card') }}{% if compact %} article-teaser--compact{% endif %}">
<hx-card variant="{{ resolved_variant }}">
{# ... #}
</hx-card>
</div>

  • Prefer HELiX variant props for color/shadow/elevation changes — they are design-system governed and accessible by default.
  • Use CSS classes for layout changes — flex vs. grid, portrait vs. landscape, compact vs. full.
  • Use separate templates only when markup structure differs substantially (different slots, different element hierarchy).
  • Avoid more than 3–4 variant props per SDC — complexity grows exponentially. If you have 8 variants, consider splitting into two SDCs.
  • Document enum values in component.yml descriptions. Site builders using Layout Builder need to understand what each value does without reading the template.