craft-twig-guidelines

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Twig Coding Standards — Craft CMS 5

Twig编码规范 — 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.
For Twig architecture patterns (atomic design, routing, builders), see the
craft-site
skill. For PHP coding standards, see
craft-php-guidelines
.
本规范是Craft CMS 5项目中Twig模板的编码约定,适用于所有Twig代码——原子组件、视图、布局、构建器、局部模板。
如需了解Twig架构模式(原子设计、路由、构建器),请查看
craft-site
技能。如需了解PHP编码标准,请查看
craft-php-guidelines

Documentation

参考文档

Use
web_fetch
on specific doc pages when something isn't covered here.
如果本规范没有覆盖相关内容,可以使用
web_fetch
获取特定文档页面的信息。

Variable Naming

变量命名

Single-word, descriptive, lowercase. No camelCase in Twig unless absolutely unavoidable.
twig
{# Correct #}
{% set heading = entry.title %}
{% set image = entry.heroImage.one() %}
{% set items = navigation.links.all() %}
{% set element = props.get('url') ? 'a' : 'span' %}

{# Wrong #}
{% set heroHeading = entry.title %}
{% set heroImg = entry.heroImage.one() %}
{% set navItems = navigation.links.all() %}
{% set el = props.get('url') ? 'a' : 'span' %}
No abbreviations:
element
not
el
,
button
not
btn
,
navigation
not
nav
,
description
not
desc
.
If you need a multi-word variable, reconsider whether you're over-specifying. If truly unavoidable, use snake_case over camelCase:
hero_image
over
heroImage
.
采用单字、描述性、全小写的命名方式。除非万不得已,Twig中禁止使用驼峰命名(camelCase)。
twig
{# Correct #}
{% set heading = entry.title %}
{% set image = entry.heroImage.one() %}
{% set items = navigation.links.all() %}
{% set element = props.get('url') ? 'a' : 'span' %}

{# Wrong #}
{% set heroHeading = entry.title %}
{% set heroImg = entry.heroImage.one() %}
{% set navItems = navigation.links.all() %}
{% set el = props.get('url') ? 'a' : 'span' %}
禁止使用缩写:应该用
element
而非
el
button
而非
btn
navigation
而非
nav
description
而非
desc
如果需要使用多词变量,请先反思是否存在过度定义的情况。如果确实无法避免,优先使用蛇形命名(snake_case)而非驼峰命名:用
hero_image
而非
heroImage

Null Handling

空值处理

??
only. Always. No exceptions.
twig
{# Correct #}
{% set heading = entry.heading ?? '' %}
{% set image = entry.heroImage.one() ?? null %}
{{ props.get('label') ?? 'Default' }}

{# Wrong — custom Twig extension, not portable #}
{% set heading = entry.heading ??? '' %}

{# Wrong — verbose, unnecessary #}
{% if entry.heading is defined and entry.heading is not null %}
{% if entry.heading is not defined %}
Twig 3.21.x (Craft 5) does not have the nullsafe operator (
?.
). That requires Twig 3.23+. Use
??
and ternaries instead:
twig
{# Can't do this yet #}
{{ entry?.author?.fullName }}

{# Do this instead #}
{{ entry.author.fullName ?? '' }}
必须统一使用
??
运算符,没有例外。
twig
{# Correct #}
{% set heading = entry.heading ?? '' %}
{% set image = entry.heroImage.one() ?? null %}
{{ props.get('label') ?? 'Default' }}

{# Wrong — custom Twig extension, not portable #}
{% set heading = entry.heading ??? '' %}

{# Wrong — verbose, unnecessary #}
{% if entry.heading is defined and entry.heading is not null %}
{% if entry.heading is not defined %}
Twig 3.21.x(Craft 5内置版本)暂不支持空安全运算符(
?.
),该特性需要Twig 3.23+版本支持。请使用
??
和三元运算符替代:
twig
{# Can't do this yet #}
{{ entry?.author?.fullName }}

{# Do this instead #}
{{ entry.author.fullName ?? '' }}

Whitespace Control

空格控制

Use
{%-
and
{{-
for whitespace trimming. Never use
{%- minify -%}
.
twig
{# 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.
使用
{%-
{{-
进行空格修剪,禁止使用
{%- minify -%}
twig
{# Correct — surgical whitespace control #}
{%- set heading = entry.title -%}
{%- if heading -%}
    {{- heading -}}
{%- endif -%}

{# Wrong — deprecated minification approach #}
{%- minify -%}
    {% set heading = entry.title %}
{%- endminify -%}
在输出时会产生不必要空行的标签上应用空格控制,不需要给所有标签都添加——仅在输出可见空格会产生影响的场景下使用即可。

Include Isolation

Include隔离

Every
{% include %}
MUST use
only
. No exceptions.
twig
{# 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.
所有
{% include %}
必须使用
only
关键字,没有例外。
twig
{# 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,
} -%}
如果不添加
only
,组件可能会隐式依赖父作用域的变量,产生不可见的耦合。

No Macros for Components

禁止使用宏定义组件

Never use
{% macro %}
for UI components. Macros don't support extends/block and their scoping model differs from includes.
twig
{# 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.
永远不要使用
{% macro %}
定义UI组件。宏不支持extends/block,且其作用域模型与include不同。
twig
{# 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 -%}
宏适用于返回字符串的工具函数(例如格式化辅助工具),但不适用于渲染UI。

Comment Headers

注释头部

Every component file gets a section header comment:
twig
{# =========================================================================
   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
.
每个组件文件都需要添加分段头部注释:
twig
{# =========================================================================
   Component Name
   Brief description of what this component does.
   ========================================================================= #}
属性文件、变体文件、视图、布局——所有文件都需要添加头部注释。这里的
=========
分隔符与
craft-php-guidelines
中的PHP规范保持一致。

Craft Twig Helpers

Craft Twig辅助工具

{% tag %}
— Polymorphic Elements

{% tag %}
— 多态元素

Primary tool for rendering elements whose tag name depends on props.
twig
{%- 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.
用于渲染标签名称依赖属性的元素的首选工具。
twig
{%- 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 -%}
规则:
  • 变量名必须具备描述性:
    element
    heading
    wrapper
    ,禁止使用
    el
    hd
  • false
    会完全省略渲染后HTML中的对应属性。
  • null
    也会省略属性。显式排除时使用
    false
    ,属性不存在时使用
    null
  • class
    支持数组,会自动过滤假值。
  • aria
    data
    支持嵌套哈希,会自动展开为
    aria-*
    /
    data-*
    属性。

tag()
— Inline Element Function

tag()
— 内联元素函数

For simple elements without complex inner content:
twig
{{ 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' } }) }}
  • text:
    key = HTML-encoded content.
  • html:
    key = raw HTML content (trusted input only).
  • Self-closing elements (
    img
    ,
    input
    ,
    br
    ) handled automatically.
适用于没有复杂内部内容的简单元素:
twig
{{ 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' } }) }}
  • text:
    键对应HTML编码后的内容。
  • html:
    键对应原始HTML内容(仅适用于可信输入)。
  • 自闭合元素(
    img
    input
    br
    )会被自动处理。

attr()
— Attribute Strings

attr()
— 属性字符串

For building attributes in non-tag contexts:
twig
<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 %}
.
用于在非标签场景下构建属性:
twig
<div{{ attr({ class: ['card', active ? 'card--active'], data: { id: entry.id } }) }}>
返回带空格前缀的属性字符串,和
{% tag %}
一样遵循
false
即省略、class数组自动过滤的规则。

|attr
Filter

|attr
过滤器

For merging attributes onto existing HTML strings:
twig
{{ svg('@webroot/icons/check.svg')|attr({ class: 'w-4 h-4', aria: { hidden: 'true' } }) }}
用于将属性合并到现有HTML字符串上:
twig
{{ svg('@webroot/icons/check.svg')|attr({ class: 'w-4 h-4', aria: { hidden: 'true' } }) }}

|parseAttr
Filter

|parseAttr
过滤器

For extracting attributes from an HTML string into a hash for manipulation:
twig
{% set attributes = '<div class="foo" data-id="1">'|parseAttr %}
{# attributes = { class: 'foo', data: { id: '1' } } #}
用于从HTML字符串中提取属性到哈希中以便操作:
twig
{% set attributes = '<div class="foo" data-id="1">'|parseAttr %}
{# attributes = { class: 'foo', data: { id: '1' } } #}

|append
Filter

|append
过滤器

For adding content to an element string:
twig
{{ svg('@webroot/icons/logo.svg')|append('<title>Company Logo</title>', 'replace') }}
用于向元素字符串中添加内容:
twig
{{ svg('@webroot/icons/logo.svg')|append('<title>Company Logo</title>', 'replace') }}

svg()
Function

svg()
函数

twig
{{ 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.
twig
{{ svg('@webroot/icons/logo.svg') }}
{{ svg(entry.svgField.one()) }}
可以和
|attr
结合添加类名和aria属性,使用
|append
为SVG内部添加可访问性标签。

collect()
Conventions

collect()
约定

collect()
wraps a Twig hash into a Collection object. Primary use cases:
collect()
将Twig哈希包装为Collection对象,主要使用场景:

Props collection

Props集合

twig
{%- set props = collect({
    heading: heading ?? null,
    content: content ?? null,
    utilities: utilities ?? null,
}) -%}

{# Access with get() #}
{{ props.get('heading') }}
{{ props.get('size', 'text-base') }}

{# Merge additional props #}
{%- set props = props.merge({ icon: icon ?? null }) -%}
twig
{%- set props = collect({
    heading: heading ?? null,
    content: content ?? null,
    utilities: utilities ?? null,
}) -%}

{# Access with get() #}
{{ props.get('heading') }}
{{ props.get('size', 'text-base') }}

{# Merge additional props #}
{%- set props = props.merge({ icon: icon ?? null }) -%}

Class collection (named keys)

类名集合(命名键)

twig
{%- set classes = collect({
    layout: 'flex items-center gap-2',
    color: 'bg-brand-primary text-white',
    hover: 'hover:bg-brand-accent',
    utilities: props.get('utilities'),
}) -%}

class="{{ classes.implode(' ') }}"
Null values in
collect()
produce harmless extra spaces when joined — browsers normalize whitespace in class attributes. Use
classes.filter(v => v).implode(' ')
if you want pristine output for devMode inspection, but plain
implode(' ')
is fine for production.
twig
{%- set classes = collect({
    layout: 'flex items-center gap-2',
    color: 'bg-brand-primary text-white',
    hover: 'hover:bg-brand-accent',
    utilities: props.get('utilities'),
}) -%}

class="{{ classes.implode(' ') }}"
collect()
中的空值在拼接时会产生无害的多余空格——浏览器会自动标准化class属性中的空格。如果希望在devMode下查看整洁的输出,可以使用
classes.filter(v => v).implode(' ')
,但生产环境直接使用
implode(' ')
即可。

Entry queries as Collections

作为集合的入口查询

twig
{# .collect instead of .all() when you need Collection methods #}
{%- set entries = craft.entries.section('blog').eagerly().collect -%}
{%- set featured = entries.filter(e => e.featured).first -%}
twig
{# .collect instead of .all() when you need Collection methods #}
{%- set entries = craft.entries.section('blog').eagerly().collect -%}
{%- set featured = entries.filter(e => e.featured).first -%}

Common Pitfalls

常见误区

  1. ???
    operator
    — custom Twig extension from older projects. Not portable. Use
    ??
    .
  2. camelCase variables
    iconPosition
    → just
    position
    . Single descriptive words.
  3. Missing
    only
    — silent variable leaking, invisible coupling.
  4. {%- minify -%}
    — deprecated. Use
    {%-
    whitespace control.
  5. Abbreviations
    el
    ,
    btn
    ,
    nav
    ,
    desc
    ,
    ctr
    → spell it out.
  6. is not defined
    — verbose null checking.
    ??
    handles it.
  7. Macros as components — wrong scoping, no extends/block support.
  8. Hardcoded colors in class strings
    bg-yellow-600
    bg-brand-accent
    .
  9. String concatenation for classes
    'flex ' ~ extraClass
    → use
    collect({})
    with named keys.
  10. options.x
    pattern
    — old macro convention. Use direct variable names.
  1. ???
    运算符
    — 旧项目中的自定义Twig扩展,不具备可移植性。请使用
    ??
  2. 驼峰命名变量
    iconPosition
    → 直接用
    position
    ,使用单个描述性单词。
  3. 缺失
    only
    关键字
    — 隐式变量泄漏,产生不可见耦合。
  4. {%- minify -%}
    — 已废弃,请使用
    {%-
    空格控制。
  5. 缩写命名
    el
    btn
    nav
    desc
    ctr
    → 请使用完整拼写。
  6. is not defined
    判断
    — 繁琐的空值检查,
    ??
    已经可以处理。
  7. 使用宏定义组件 — 作用域错误,不支持extends/block特性。
  8. 类名字符串中硬编码颜色
    bg-yellow-600
    → 使用
    bg-brand-accent
  9. 字符串拼接类名
    'flex ' ~ extraClass
    → 使用带命名键的
    collect({})
  10. options.x
    模式
    — 旧的宏约定,请使用直接变量名。