From drupal-workflow
Provides Twig template patterns, filters, theme suggestions, and component architecture for Drupal 10/11. Useful for creating or modifying Twig templates, implementing theme hooks, or building front-end components.
How this skill is triggered — by the user, by Claude, or both
Slash command
/drupal-workflow:twig-templatingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Always use `{{ attach_library() }}` for assets — never inline CSS/JS. Always use BEM naming for custom classes. Avoid heavy logic in templates — prefer preprocess functions. Never use `|raw` on user input.
Always use {{ attach_library() }} for assets — never inline CSS/JS. Always use BEM naming for custom classes. Avoid heavy logic in templates — prefer preprocess functions. Never use |raw on user input.
Drupal uses suggestion-based template discovery:
node.html.twig # Base node template.
node--article.html.twig # Article content type.
node--article--teaser.html.twig # Article teaser view mode.
node--42.html.twig # Specific node by ID.
block.html.twig # Base block template.
block--system-branding-block.html.twig # Specific block.
field.html.twig # Base field template.
field--field-image.html.twig # Specific field.
field--node--field-image--article.html.twig # Field + bundle.
page.html.twig # Base page template.
page--front.html.twig # Front page.
page--node--42.html.twig # Specific node page.
{# node--article.html.twig #}
{% set classes = [
'node',
'node--' ~ node.bundle,
node.isPromoted() ? 'node--promoted',
node.isSticky() ? 'node--sticky',
view_mode ? 'node--' ~ view_mode,
] %}
<article{{ attributes.addClass(classes) }}>
{% if label %}
<h2{{ title_attributes }}>
<a href="{{ url }}" rel="bookmark">{{ label }}</a>
</h2>
{% endif %}
<div{{ content_attributes.addClass('node__content') }}>
{{ content }}
</div>
</article>
| Filter | Purpose | Example |
|---|---|---|
| ` | t` | Translate string |
| ` | raw` | Skip auto-escape (CAUTION) |
| ` | escape` | Explicit escape |
| ` | clean_class` | CSS-safe class |
| ` | clean_id` | HTML-safe ID |
| ` | date` | Format date |
| ` | length` | Array/string length |
| ` | without` | Render without fields |
| ` | safe_join` | Join array with separator |
{# Attach library assets. #}
{{ attach_library('my_theme/component') }}
{# Link to a route. #}
{{ path('entity.node.canonical', {'node': nid}) }}
{# Create a link. #}
{{ link('Click here', url) }}
{# Translate with context. #}
{{ 'Submit'|t({}, {'context': 'my_module'}) }}
{# Access Drupal URL. #}
{{ url('<front>') }}
{# File URL from URI. #}
{{ file_url(node.field_image.entity.fileuri) }}
/**
* Implements hook_preprocess_HOOK() for node templates.
*/
function my_theme_preprocess_node(array &$variables): void {
$node = $variables['node'];
// Add custom variables.
$variables['reading_time'] = ceil(str_word_count(strip_tags($node->body->value)) / 200);
// Add conditional classes.
if ($node->isPublished()) {
$variables['attributes']['class'][] = 'node--published';
}
}
/**
* Implements hook_theme_suggestions_HOOK_alter() for node templates.
*/
function my_theme_theme_suggestions_node_alter(array &$suggestions, array $variables): void {
$node = $variables['elements']['#node'];
// Add suggestion based on custom field value.
if ($node->hasField('field_layout') && !$node->get('field_layout')->isEmpty()) {
$layout = $node->get('field_layout')->value;
$suggestions[] = 'node__' . $node->bundle() . '__' . $layout;
}
}
{# components/card.html.twig #}
{% set card_classes = [
'card',
variant ? 'card--' ~ variant,
size ? 'card--' ~ size,
] %}
<div{{ attributes.addClass(card_classes) }}>
{% if image %}
<div class="card__image">
{{ image }}
</div>
{% endif %}
<div class="card__content">
{% if title %}
<h3 class="card__title">{{ title }}</h3>
{% endif %}
{% if body %}
<div class="card__body">{{ body }}</div>
{% endif %}
</div>
{% if actions %}
<div class="card__actions">{{ actions }}</div>
{% endif %}
</div>
block__element--modifier).{{ attach_library() }} for component assets.|t filter for all translatable strings.{{ content|without('field_name') }} to exclude specific fields.npx claudepluginhub gkastanis/drupal-workflow --plugin drupal-workflowBest practices for building Drupal Single Directory Components (SDC) with Twig covering props vs slots, attributes, include vs embed, escaping, accessibility, schema validation, and component overriding.
Twig coding standards and conventions for Craft CMS 5 templates. Covers variable naming, null handling, whitespace control, include isolation, Craft Twig helpers, and common pitfalls.
Loads Drupal conventions for translations (t(), PO files, Twig), CSS (BEM, libraries.yml, attach_library), and error handling (exceptions). Use for theming, i18n, or exceptions in Drupal 10+.