Receber por email
← Voltar para o catálogo

craft-twig-guidelines

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'), Craf

56estrelas
Atualizado há 4 meses

Ver no GitHub ↗ Licença: MIT

Como adicionar

/plugin marketplace add michtio/craftcms-claude-skills

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/craft-twig-guidelines/svg)](https://www.skillteca.com.br/skills/craft-twig-guidelines)

Skills relacionadas

Ver todas de Desenvolvimento →

Alerta por categoria

Receba novas skills de Desenvolvimento toda segunda

1 email curto, só com as skills novas de Desenvolvimento. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Twig Coding Standards — Craft CMS 5

Coding conventions for Twig templates in Craft CMS 5 projects. These apply to all Twig code — atomic components, views, layouts, builders, partials.

Companion Skills — Always Load Together

When this skill triggers, also load:

  • craft-site — Template architecture and component patterns. Required when creating or editing components, layouts, views, or builders.
  • craft-content-modeling — Content architecture. Required when template code involves element queries, field access, or section decisions.

For Twig architecture patterns (atomic design, routing, builders), see the craft-site skill. For PHP coding standards, see craft-php-guidelines.

Documentation

  • Twig in Craft: https://craftcms.com/docs/5.x/development/twig.html
  • Template tags: https://craftcms.com/docs/5.x/reference/twig/tags.html
  • Template functions: https://craftcms.com/docs/5.x/reference/twig/functions.html
  • Twig 3 docs: https://twig.symfony.com/doc/3.x/

Use WebFetch on specific doc pages when something isn't covered here.

Variable Naming

Single-word, descriptive, lowercase preferred. When multi-word is needed, use camelCase.

{# Correct #}
{% set heading = entry.title %}
{% set image = entry.heroImage.one() %}
{% set items = navigation.links.all() %}
{% set element = props.get('url') ? 'a' : 'span' %}
{% set buttonText = entry.callToAction %}
{% set containerClass = 'max-w-3xl' %}

{# Wrong — abbreviations #}
{% set el = props.get('url') ? 'a' : 'span' %}
{% set btn = entry.callToAction %}
{% set nav = navigation.links.all() %}

{# Wrong — snake_case #}
{% set button_text = entry.callToAction %}
{% set container_class = 'max-w-3xl' %}

No abbreviations: element not el, button not btn, navigation not nav, description not desc.

Prefer single-word names when context makes the meaning clear (e.g. heading inside a component is better than sectionHeading). But multi-word camelCase is perfectly fine when needed for clarity.

Null Handling

?? is the default. Always safe, always portable.

??? (empty coalesce) is acceptable if the project already has nystudio107/craft-emptycoalesce or nystudio107/craft-seomatic installed — both provide the operator. But never install a plugin just for ???. Check composer.json first.

{# Always correct #}
{% set heading = entry.heading ?? '' %}
{% set image = entry.heroImage.one() ?? null %}
{{ props.get('label') ?? 'Default' }}

{# OK if empty-coalesce or SEOmatic is installed — checks empty, not just null #}
{% set heading = entry.heading ??? '' %}

{# Wrong — verbose, unnecessary #}
{% if entry.heading is defined and entry.heading is not null %}
{% if entry.heading is not defined %}

Craft 5 supports the nullsafe operator (?.). Use it for deep traversal through chains that may have null links — it propagates null cleanly without the verbose is defined and is not null dance:

{# Reach for ?. when any link in the chain may be null #}
{{ entry?.author?.fullName ?? 'Anonymous' }}

{# ?? alone is enough when only the leaf is in question #}
{{ entry.title ?? '' }}

?? stays the right tool for simple "value or fallback" cases; ?. is for chains where intermediate links may be missing. Don't reach for ?. on a single property access — it adds noise without adding safety.

Whitespace Control

Use {%- and {{- for whitespace trimming. Never use {%- minify -%}.

{# Correct — surgical whitespace control #}
{%- set heading = entry.title -%}
{%- if heading -%}
    {{- heading -}}
{%- endif -%}

{# Wrong — deprecated minification approach #}
{%- minify -%}
    {% set heading = entry.title %}
{%- endminify -%}

Apply whitespace control on tags that produce unwanted blank lines in output. Not every tag needs it — use where visible output whitespace matters.

Include Isolation

Every {% include %} MUST use only. No exceptions.

{# Correct — explicit, isolated #}
{%- include '_atoms/buttons/button--primary' with {
    text: entry.title,
    url: entry.url,
} only -%}

{# Wrong — ambient variables leak in #}
{%- include '_atoms/buttons/button--primary' with {
    text: entry.title,
    url: entry.url,
} -%}

Without only, a component can silently depend on variables from its parent scope, creating invisible coupling.

No Macros for Components

Never use {% macro %} for UI components. Macros don't support extends/block and their scoping model differs from includes.

{# Wrong — macro for a component #}
{% macro button(text, url) %}
    <a href="{{ url }}">{{ text }}</a>
{% endmacro %}

{# Correct — include with isolation #}
{%- include '_atoms/buttons/button--primary' with {
    text: text,
    url: url,
} only -%}

Macros are acceptable for utility functions that return strings (e.g., formatting helpers), not for rendering UI.

Comment Headers

Every component file gets a section header comment:

{# =========================================================================
   Component Name
   Brief description of what this component does.
   ========================================================================= #}

Props files, variant files, views, layouts — all get headers. The ========= separator matches the PHP convention from craft-php-guidelines.

Craft Twig Helpers

{% tag %} — Polymorphic Elements

Primary tool for rendering elements whose tag name depends on props.

{%- set element = props.get('url') ? 'a' : 'span' -%}

{%- tag element with {
    class: classes.implode(' '),
    href: props.get('url') ?? false,
    target: props.get('target') ?? false,
    rel: props.get('rel') ?? false,
    aria: {
        label: props.get('label') ?? false,
    },
} -%}
    {{ props.get('text') }}
{%- endtag -%}

Rules:

  • Variable name must be descriptive: element, heading, wrapper. Never el, hd.
  • false omits an attribute entirely from the rendered HTML.
  • null also omits. Use false when explicitly excluding, null when absent.
  • class accepts arrays with automatic falsy filtering.
  • aria and data accept nested hashes that expand to aria- / data- attributes.

tag() — Inline Element Function

For simple elements without complex inner content:

{{ tag('span', { class: 'sr-only', text: '(opens in new window)' }) }}
{{ tag('img', { src: image.url, alt: image.title, loading: 'lazy' }) }}
{{ tag('i', { class: ['fa-solid', icon], aria: { hidden: 'true' } }) }}

{# Craft 5.10+: pass a string as the second arg as a text-only shortcut #}
{{ tag('span', 'Read more') }}
  • text: key = HTML-encoded content.
  • html: key = raw HTML content (trusted input only).
  • Self-closing elements (img, input, br) handled automatically.

attr() — Attribute Strings

For building attributes in non-tag contexts:

<div{{ attr({ class: ['card', active ? 'card--active'], data: { id: entry.id } }) }}>

Returns a space-prefixed attribute string. Same false-means-omit and class array filtering as {% tag %}.

|attr Filter

For merging attributes onto existing HTML strings:

{{ svg('@webroot/icons/check.svg')|attr({ class: 'w-4 h-4', aria: { hidden: 'true' } }) }}

|parseAttr Filter

For extracting attributes from an HTML string into a hash for manipulation:

{% set attributes = '<div class="foo" data-id="1">'|parseAttr %}
{# attributes = { class: 'foo', data: { id: '1' } } #}

|append Filter

For adding content to an element string:

{{ svg('@webroot/icons/logo.svg')|append('<title>Company Logo</title>', 'replace') }}

svg() Function

{{ svg('@webroot/icons/logo.svg') }}
{{ svg(entry.svgField.one()) }}

Combine with |attr for classes and aria attributes. Use |append for accessible labels inside the SVG.

heading() / h() / h1()…h6() — Programmatic Headings (Craft 5.10+)

Build heading tags from a dy

Compartilhar X LinkedIn