From drupal-core
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.
npx claudepluginhub ajv009/drupal-devkitThis skill uses the workspace's default tool permissions.
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.
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.
Twig coding standards and conventions for Craft CMS 5 templates. ALWAYS load this skill when writing, editing, or reviewing any .twig file in a Craft CMS project — even for small edits. Covers: variable naming (camelCase, no abbreviations), null handling (?? operator, ??? with empty-coalesce plugin), whitespace control ({%- trimming, NOT {%- minify -%}), include isolation (always use 'only'), Craft Twig helpers ({% tag %}, tag(), attr(), |attr filter, |parseAttr, |append, svg()), collect() for props and class collections, .implode(), comment headers with ========= separators on component files, and common pitfalls (snake_case, macros as components, hardcoded colors). Triggers on: Twig template creation, editing, or review; .twig files; {% include %} with 'only'; {% tag %} and polymorphic elements; collect() and props.get(); class string building; attr() and |attr filter; svg() with styling and aria; ?? and ??? null coalescing; whitespace control and blank lines in output; minify alternatives; Twig file headers and comment blocks; variable naming conventions in Twig; currentSite, siteUrl, craft.entries, .eagerly(), .collect in template context. NOT for Twig architecture patterns, atomic design structure, or template routing (use craft-site). NOT for PHP code (use craft-php-guidelines). NOT for content modeling or field configuration (use craft-content-modeling).
Applies Symfony architecture and execution workflows with controlled scope and checkpoints for Twig components tasks. Use for refining architecture and safely executing medium/complex changes.
Share bugs, ideas, or general feedback.
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.