craft-twig-guidelines
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTwig 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
skill. For PHP coding standards, see .
craft-sitecraft-php-guidelines本规范是Craft CMS 5项目中Twig模板的编码约定,适用于所有Twig代码——原子组件、视图、布局、构建器、局部模板。
如需了解Twig架构模式(原子设计、路由、构建器),请查看技能。如需了解PHP编码标准,请查看。
craft-sitecraft-php-guidelinesDocumentation
参考文档
- 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 on specific doc pages when something isn't covered here.
web_fetch- Twig in Craft: https://craftcms.com/docs/5.x/development/twig.html
- 模板标签: https://craftcms.com/docs/5.x/reference/twig/tags.html
- 模板函数: https://craftcms.com/docs/5.x/reference/twig/functions.html
- Twig 3 官方文档: https://twig.symfony.com/doc/3.x/
如果本规范没有覆盖相关内容,可以使用获取特定文档页面的信息。
web_fetchVariable 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: not , not , not ,
not .
elementelbuttonbtnnavigationnavdescriptiondescIf you need a multi-word variable, reconsider whether you're over-specifying.
If truly unavoidable, use snake_case over camelCase: over .
hero_imageheroImage采用单字、描述性、全小写的命名方式。除非万不得已,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' %}禁止使用缩写:应该用而非,而非,而非,而非。
elementelbuttonbtnnavigationnavdescriptiondesc如果需要使用多词变量,请先反思是否存在过度定义的情况。如果确实无法避免,优先使用蛇形命名(snake_case)而非驼峰命名:用而非。
hero_imageheroImageNull Handling
空值处理
??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 MUST use . No exceptions.
{% include %}onlytwig
{# 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 , a component can silently depend on variables from its parent
scope, creating invisible coupling.
only所有必须使用关键字,没有例外。
{% include %}onlytwig
{# 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,
} -%}如果不添加,组件可能会隐式依赖父作用域的变量,产生不可见的耦合。
onlyNo Macros for Components
禁止使用宏定义组件
Never use for UI components. Macros don't support extends/block
and their scoping model differs from includes.
{% macro %}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.
永远不要使用定义UI组件。宏不支持extends/block,且其作用域模型与include不同。
{% macro %}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.
========================================================================= #}属性文件、变体文件、视图、布局——所有文件都需要添加头部注释。这里的分隔符与中的PHP规范保持一致。
=========craft-php-guidelinesCraft Twig Helpers
Craft Twig辅助工具
{% tag %}
— Polymorphic Elements
{% tag %}{% tag %}
— 多态元素
{% 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. Neverwrapper,el.hd - omits an attribute entirely from the rendered HTML.
false - also omits. Use
nullwhen explicitly excluding,falsewhen absent.null - accepts arrays with automatic falsy filtering.
class - and
ariaaccept nested hashes that expand todata/aria-*attributes.data-*
用于渲染标签名称依赖属性的元素的首选工具。
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 - 会完全省略渲染后HTML中的对应属性。
false - 也会省略属性。显式排除时使用
null,属性不存在时使用false。null - 支持数组,会自动过滤假值。
class - 和
aria支持嵌套哈希,会自动展开为data/aria-*属性。data-*
tag()
— Inline Element Function
tag()tag()
— 内联元素函数
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' } }) }}- key = HTML-encoded content.
text: - key = raw HTML content (trusted input only).
html: - Self-closing elements (,
img,input) handled automatically.br
适用于没有复杂内部内容的简单元素:
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' } }) }}- 键对应HTML编码后的内容。
text: - 键对应原始HTML内容(仅适用于可信输入)。
html: - 自闭合元素(、
img、input)会被自动处理。br
attr()
— Attribute Strings
attr()attr()
— 属性字符串
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 -means-omit and class
array filtering as .
false{% tag %}用于在非标签场景下构建属性:
twig
<div{{ attr({ class: ['card', active ? 'card--active'], data: { id: entry.id } }) }}>返回带空格前缀的属性字符串,和一样遵循即省略、class数组自动过滤的规则。
{% tag %}false|attr
Filter
|attr|attr
过滤器
|attrFor 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|parseAttr
过滤器
|parseAttrFor 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|append
过滤器
|appendFor 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()svg()
函数
svg()twig
{{ svg('@webroot/icons/logo.svg') }}
{{ svg(entry.svgField.one()) }}Combine with for classes and aria attributes. Use for
accessible labels inside the SVG.
|attr|appendtwig
{{ svg('@webroot/icons/logo.svg') }}
{{ svg(entry.svgField.one()) }}可以和结合添加类名和aria属性,使用为SVG内部添加可访问性标签。
|attr|appendcollect()
Conventions
collect()collect()
约定
collect()collect()collect()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 produce harmless extra spaces when joined — browsers
normalize whitespace in class attributes. Use
if you want pristine output for devMode inspection, but plain
is fine for production.
collect()classes.filter(v => v).implode(' ')implode(' ')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()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
常见误区
- operator — custom Twig extension from older projects. Not portable. Use
???.?? - camelCase variables — → just
iconPosition. Single descriptive words.position - Missing — silent variable leaking, invisible coupling.
only - — deprecated. Use
{%- minify -%}whitespace control.{%- - Abbreviations — ,
el,btn,nav,desc→ spell it out.ctr - — verbose null checking.
is not definedhandles it.?? - Macros as components — wrong scoping, no extends/block support.
- Hardcoded colors in class strings — →
bg-yellow-600.bg-brand-accent - String concatenation for classes — → use
'flex ' ~ extraClasswith named keys.collect({}) - pattern — old macro convention. Use direct variable names.
options.x
- 运算符 — 旧项目中的自定义Twig扩展,不具备可移植性。请使用
???。?? - 驼峰命名变量 — → 直接用
iconPosition,使用单个描述性单词。position - 缺失关键字 — 隐式变量泄漏,产生不可见耦合。
only - — 已废弃,请使用
{%- minify -%}空格控制。{%- - 缩写命名 — 、
el、btn、nav、desc→ 请使用完整拼写。ctr - 判断 — 繁琐的空值检查,
is not defined已经可以处理。?? - 使用宏定义组件 — 作用域错误,不支持extends/block特性。
- 类名字符串中硬编码颜色 — → 使用
bg-yellow-600。bg-brand-accent - 字符串拼接类名 — → 使用带命名键的
'flex ' ~ extraClass。collect({}) - 模式 — 旧的宏约定,请使用直接变量名。
options.x