Skip to content
Development
Skill

/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

From plugin
craftcms-claude-skills
8013 skills6 agents
Install
$ npx -y skills add michtio/craftcms-claude-skills --skill craft-twig-guidelines --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/craft-twig-guidelines

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

craft-twig-guidelines.SKILL.md
name: craft-twig-guidelines
description: "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; |e('js') aggressive escaping (spaces become \uXXXX), js escaper, grepping escaped translated strings. 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)."

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

Read more
Ships withcraftcms-claude-skills

Production-ready Claude Code skills, agents, and project templates for Craft CMS 5 development. Built and maintained by michtio.

Get the whole plugin

Other skills on craftcms-claude-skills.