{"id":"61134df0-7aa3-487c-9c3e-def95b7f8167","shortId":"U9NF3e","kind":"skill","title":"craft-twig-guidelines","tagline":"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 em","description":"# Twig Coding Standards — Craft CMS 5\n\nCoding conventions for Twig templates in Craft CMS 5 projects. These apply to\nall Twig code — atomic components, views, layouts, builders, partials.\n\n## Companion Skills — Always Load Together\n\nWhen this skill triggers, also load:\n\n- **`craft-site`** — Template architecture and component patterns. Required when creating or editing components, layouts, views, or builders.\n- **`craft-content-modeling`** — Content architecture. Required when template code involves element queries, field access, or section decisions.\n\nFor Twig **architecture** patterns (atomic design, routing, builders), see the\n`craft-site` skill. For PHP coding standards, see `craft-php-guidelines`.\n\n## Documentation\n\n- Twig in Craft: https://craftcms.com/docs/5.x/development/twig.html\n- Template tags: https://craftcms.com/docs/5.x/reference/twig/tags.html\n- Template functions: https://craftcms.com/docs/5.x/reference/twig/functions.html\n- Twig 3 docs: https://twig.symfony.com/doc/3.x/\n\nUse `WebFetch` on specific doc pages when something isn't covered here.\n\n## Variable Naming\n\nSingle-word, descriptive, lowercase preferred. When multi-word is needed, use\ncamelCase.\n\n```twig\n{# Correct #}\n{% set heading = entry.title %}\n{% set image = entry.heroImage.one() %}\n{% set items = navigation.links.all() %}\n{% set element = props.get('url') ? 'a' : 'span' %}\n{% set buttonText = entry.callToAction %}\n{% set containerClass = 'max-w-3xl' %}\n\n{# Wrong — abbreviations #}\n{% set el = props.get('url') ? 'a' : 'span' %}\n{% set btn = entry.callToAction %}\n{% set nav = navigation.links.all() %}\n\n{# Wrong — snake_case #}\n{% set button_text = entry.callToAction %}\n{% set container_class = 'max-w-3xl' %}\n```\n\nNo abbreviations: `element` not `el`, `button` not `btn`, `navigation` not `nav`,\n`description` not `desc`.\n\nPrefer single-word names when context makes the meaning clear (e.g. `heading`\ninside a component is better than `sectionHeading`). But multi-word camelCase is\nperfectly fine when needed for clarity.\n\n## Null Handling\n\n`??` is the default. Always safe, always portable.\n\n`???` (empty coalesce) is acceptable if the project already has `nystudio107/craft-empty-coalesce` or `nystudio107/craft-seomatic` installed — both provide the operator. But never install a plugin just for `???`. Check `composer.json` first.\n\n```twig\n{# Always correct #}\n{% set heading = entry.heading ?? '' %}\n{% set image = entry.heroImage.one() ?? null %}\n{{ props.get('label') ?? 'Default' }}\n\n{# OK if empty-coalesce or SEOmatic is installed — checks empty, not just null #}\n{% set heading = entry.heading ??? '' %}\n\n{# Wrong — verbose, unnecessary #}\n{% if entry.heading is defined and entry.heading is not null %}\n{% if entry.heading is not defined %}\n```\n\nCraft 5.10 ships Twig 3.24, which supports the nullsafe operator (`?.`). Use it for\ndeep traversal through chains that may have null links — it propagates `null` cleanly\nwithout the verbose `is defined and is not null` dance:\n\n```twig\n{# Reach for ?. when any link in the chain may be null #}\n{{ entry?.author?.fullName ?? 'Anonymous' }}\n\n{# ?? alone is enough when only the leaf is in question #}\n{{ entry.title ?? '' }}\n```\n\n`??` stays the right tool for simple \"value or fallback\" cases; `?.` is for chains\nwhere intermediate links may be missing. Don't reach for `?.` on a single property\naccess — it adds noise without adding safety.\n\n## Whitespace Control\n\nUse `{%-` and `{{-` for whitespace trimming. Never use `{%- minify -%}`.\n\n```twig\n{# Correct — surgical whitespace control #}\n{%- set heading = entry.title -%}\n{%- if heading -%}\n    {{- heading -}}\n{%- endif -%}\n\n{# Wrong — deprecated minification approach #}\n{%- minify -%}\n    {% set heading = entry.title %}\n{%- endminify -%}\n```\n\nApply whitespace control on tags that produce unwanted blank lines in output.\nNot every tag needs it — use where visible output whitespace matters.\n\n## Include Isolation\n\nEvery `{% include %}` MUST use `only`. No exceptions.\n\n```twig\n{# Correct — explicit, isolated #}\n{%- include '_atoms/buttons/button--primary' with {\n    text: entry.title,\n    url: entry.url,\n} only -%}\n\n{# Wrong — ambient variables leak in #}\n{%- include '_atoms/buttons/button--primary' with {\n    text: entry.title,\n    url: entry.url,\n} -%}\n```\n\nWithout `only`, a component can silently depend on variables from its parent\nscope, creating invisible coupling.\n\n## No Macros for Components\n\nNever use `{% macro %}` for UI components. Macros don't support extends/block\nand their scoping model differs from includes.\n\n```twig\n{# Wrong — macro for a component #}\n{% macro button(text, url) %}\n    <a href=\"{{ url }}\">{{ text }}</a>\n{% endmacro %}\n\n{# Correct — include with isolation #}\n{%- include '_atoms/buttons/button--primary' with {\n    text: text,\n    url: url,\n} only -%}\n```\n\nMacros are acceptable for utility functions that return strings (e.g., formatting\nhelpers), not for rendering UI.\n\n## Comment Headers\n\nEvery component file gets a section header comment:\n\n```twig\n{# =========================================================================\n   Component Name\n   Brief description of what this component does.\n   ========================================================================= #}\n```\n\nProps files, variant files, views, layouts — all get headers. The `=========`\nseparator matches the PHP convention from `craft-php-guidelines`.\n\n## Craft Twig Helpers\n\n### `{% tag %}` — Polymorphic Elements\n\nPrimary tool for rendering elements whose tag name depends on props.\n\n```twig\n{%- set element = props.get('url') ? 'a' : 'span' -%}\n\n{%- tag element with {\n    class: classes.implode(' '),\n    href: props.get('url') ?? false,\n    target: props.get('target') ?? false,\n    rel: props.get('rel') ?? false,\n    aria: {\n        label: props.get('label') ?? false,\n    },\n} -%}\n    {{ props.get('text') }}\n{%- endtag -%}\n```\n\nRules:\n- Variable name must be descriptive: `element`, `heading`, `wrapper`. Never `el`, `hd`.\n- `false` omits an attribute entirely from the rendered HTML.\n- `null` also omits. Use `false` when explicitly excluding, `null` when absent.\n- `class` accepts arrays with automatic falsy filtering.\n- `aria` and `data` accept nested hashes that expand to `aria-*` / `data-*` attributes.\n\n### `tag()` — Inline Element Function\n\nFor simple elements without complex inner content:\n\n```twig\n{{ tag('span', { class: 'sr-only', text: '(opens in new window)' }) }}\n{{ tag('img', { src: image.url, alt: image.title, loading: 'lazy' }) }}\n{{ tag('i', { class: ['fa-solid', icon], aria: { hidden: 'true' } }) }}\n\n{# Craft 5.10+: pass a string as the second arg as a text-only shortcut #}\n{{ tag('span', 'Read more') }}\n```\n\n- `text:` key = HTML-encoded content.\n- `html:` key = raw HTML content (trusted input only).\n- Self-closing elements (`img`, `input`, `br`) handled automatically.\n\n### `attr()` — Attribute Strings\n\nFor building attributes in non-tag contexts:\n\n```twig\n<div{{ attr({ class: ['card', active ? 'card--active'], data: { id: entry.id } }) }}>\n```\n\nReturns a space-prefixed attribute string. Same `false`-means-omit and class\narray filtering as `{% tag %}`.\n\n### `|attr` Filter\n\nFor merging attributes onto existing HTML strings:\n\n```twig\n{{ svg('@webroot/icons/check.svg')|attr({ class: 'w-4 h-4', aria: { hidden: 'true' } }) }}\n```\n\n### `|parseAttr` Filter\n\nFor extracting attributes from an HTML string into a hash for manipulation:\n\n```twig\n{% set attributes = '<div class=\"foo\" data-id=\"1\">'|parseAttr %}\n{# attributes = { class: 'foo', data: { id: '1' } } #}\n```\n\n### `|append` Filter\n\nFor adding content to an element string:\n\n```twig\n{{ svg('@webroot/icons/logo.svg')|append('<title>Company Logo</title>', 'replace') }}\n```\n\n### `svg()` Function\n\n```twig\n{{ svg('@webroot/icons/logo.svg') }}\n{{ svg(entry.svgField.one()) }}\n```\n\nCombine with `|attr` for classes and aria attributes. Use `|append` for\naccessible labels inside the SVG.\n\n### `heading()` / `h()` / `h1()`…`h6()` — Programmatic Headings (Craft 5.10+)\n\nBuild heading tags from a dynamic level without string-concatenation. Useful in\ncomponents that receive a `level` prop and need to render the matching tag\nwithout doing `tag('h' ~ level, text)` manually.\n\n```twig\n{# heading(level, text-or-attributes) — level is an int 1-6 #}\n{{ heading(2, 'Section title') }}                     {# <h2>Section title</h2> #}\n{{ heading(3, { class: 'text-xl', text: 'Subsection' }) }}\n\n{# h() is the short alias for heading() #}\n{{ h(2, 'Section title') }}\n\n{# Bound-level shortcuts — only attributes/text needed #}\n{{ h1('Page title') }}\n{{ h2('Section') }}\n{{ h6({ class: 'sr-only', text: 'Hidden heading' }) }}\n```\n\nThese are stateless tag builders — there's no auto-incrementing or current-level\ntracking. Components that need to thread a level across nested contexts still\npass it as a prop. `heading()` throws `InvalidArgumentException` when level is\noutside 1-6.\n\n### Filter Additions (Craft 5.10+)\n\n```twig\n{{ price|number(locale: 'de-DE') }}                   {# locale arg #}\n{{ entry.postDate|datetime('long', withTimeZone: true) }}\n{{ deadline|time('short', withTimeZone: true) }}\n{{ maybeNull|timestamp }}                              {# returns \"now\" for null/empty #}\n```\n\n## `collect()` Conventions\n\n`collect()` wraps a Twig hash into a Collection object. Primary use cases:\n\n### Props collection\n\n```twig\n{%- set props = collect({\n    heading: heading ?? null,\n    content: content ?? null,\n    utilities: utilities ?? null,\n}) -%}\n\n{# Access with get() #}\n{{ props.get('heading') }}\n{{ props.get('size', 'text-base') }}\n\n{# Merge additional props #}\n{%- set props = props.merge({ icon: icon ?? null }) -%}\n```\n\n### Class collection (named keys)\n\n```twig\n{%- set classes = collect({\n    layout: 'flex items-center gap-2',\n    color: 'bg-brand-primary text-white',\n    hover: 'hover:bg-brand-accent',\n    utilities: props.get('utilities'),\n}) -%}\n\nclass=\"{{ classes.implode(' ') }}\"\n```\n\nNull values in `collect()` produce harmless extra spaces when joined — browsers\nnormalize whitespace in class attributes. Use `classes.filter(v => v).implode(' ')`\nif you want pristine output for devMode inspection, but plain `implode(' ')`\nis fine for production.\n\n### Entry queries as Collections\n\n```twig\n{# .collect instead of .all() when you need Collection methods #}\n{%- set entries = craft.entries.section('blog').eagerly().collect -%}\n{%- set featured = entries.filter(e => e.featured).first -%}\n```\n\n## Common Pitfalls\n\n1. **`???` operator without the plugin** — requires `nystudio107/craft-empty-coalesce` or `nystudio107/craft-seomatic`. Check `composer.json` before using. Default to `??`.\n2. **snake_case variables** — use camelCase: `heroImage` not `hero_image`.\n3. **Missing `only`** — silent variable leaking, invisible coupling.\n4. **`{%- minify -%}`** — deprecated. Use `{%-` whitespace control.\n5. **Abbreviations** — `el`, `btn`, `nav`, `desc`, `ctr` → spell it out.\n6. **`is not defined`** — verbose null checking. `??` handles it.\n7. **Macros as components** — wrong scoping, no extends/block support.\n8. **Hardcoded colors in class strings** — `bg-yellow-600` → `bg-brand-accent`.\n9. **String concatenation for classes** — `'flex ' ~ extraClass` → use `collect({})` with named keys.\n10. **`is empty` / `|default` on Craft Models (5.10+)** — any `yii\\base\\Model` (entries, settings, custom models) is now treated as non-empty regardless of its property values. Means `{{ user|default('Guest') }}` always renders the user object; `{% if entry is empty %}` always false. Check the specific property you care about: `{% if entry.title is empty %}`.\n10. **`options.x` pattern** — old macro convention. Use direct variable names.\n11. **Blocks inside conditionals** — `{% if %}{% block foo %}{% endblock %}{% endif %}` is invalid Twig. Blocks are compile-time structures and cannot be conditionally defined. Move the conditional inside the block: `{% block foo %}{% if condition %}...{% endif %}{% endblock %}`.\n12. **Hardcoded `/admin` CP URL** — `cpTrigger` is configurable via `CRAFT_CP_TRIGGER` env var or `cpTrigger` in general.php. Many projects use `cp` instead of `admin`. Use `cpUrl()` function or check `.env` — never hardcode `/admin/`.","tags":["craft","twig","guidelines","craftcms","claude","skills","michtio","agent-skills","claude-code","claude-code-plugin","claude-code-skills","claude-skills"],"capabilities":["skill","source-michtio","skill-craft-twig-guidelines","topic-agent-skills","topic-claude-code","topic-claude-code-plugin","topic-claude-code-skills","topic-claude-skills","topic-content-modeling","topic-craft-cms","topic-craft-cms-5","topic-craftcms","topic-ddev","topic-php","topic-twig"],"categories":["craftcms-claude-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/michtio/craftcms-claude-skills/craft-twig-guidelines","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add michtio/craftcms-claude-skills","source_repo":"https://github.com/michtio/craftcms-claude-skills","install_from":"skills.sh"}},"qualityScore":"0.471","qualityRationale":"deterministic score 0.47 from registry signals: · indexed on github topic:agent-skills · 42 github stars · SKILL.md body (12,200 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:58:17.005Z","embedding":null,"createdAt":"2026-04-18T22:19:36.178Z","updatedAt":"2026-05-18T18:58:17.005Z","lastSeenAt":"2026-05-18T18:58:17.005Z","tsv":"'-2':1219 '-4':919,921 '-6':1041,1127 '/admin':1497,1528 '/doc/3.x/':167 '/docs/5.x/development/twig.html':151 '/docs/5.x/reference/twig/functions.html':161 '/docs/5.x/reference/twig/tags.html':156 '1':948,1040,1126,1303 '10':1396,1450 '11':1460 '12':1495 '2':1043,1064,1318 '3':163,1049,1328 '3.24':383 '3xl':221,249 '4':1336 '5':13,52,61,1342 '5.10':380,823,995,1131,1403 '6':1352 '600':1379 '7':1361 '8':1370 '9':1384 'abbrevi':41,223,251,1343 'absent':761 'accent':1233,1383 'accept':308,627,763,772 'access':118,469,983,1186 'across':1110 'activ':880,882 'ad':474,952 'add':471 'addit':1129,1197 'admin':1519 'alia':1060 'alon':431 'alreadi':312 'also':84,752 'alt':808 'alway':15,77,301,303,333,1428,1437 'ambient':552 'anonym':430 'append':949,961,981 'appli':64,507 'approach':501 'architectur':90,109,124 'arg':830,1140 'aria':722,769,778,819,922,978 'array':764,900 'atom':69,126 'atoms/buttons/button--primary':544,557,618 'attr':864,877,904,916,974 'attribut':745,780,865,869,891,908,929,941,943,979,1035,1254 'attributes/text':1072 'author':428 'auto':1096 'auto-incr':1095 'automat':766,863 'base':1195,1406 'better':281 'bg':1222,1231,1377,1381 'bg-brand-acc':1230,1380 'bg-brand-primari':1221 'bg-yellow':1376 'blank':515 'block':1461,1465,1472,1488,1489 'blog':1292 'bound':1068 'bound-level':1067 'br':861 'brand':1223,1232,1382 'brief':654 'browser':1249 'btn':231,257,1345 'build':868,996 'builder':73,103,129,1091 'button':240,255,608 'buttontext':214 'camelcas':39,195,288,1323 'cannot':1479 'card':879,881 'care':1444 'case':238,451,1170,1320 'center':1217 'chain':395,423,454 'check':329,354,1312,1358,1439,1524 'clariti':295 'class':245,708,762,795,814,878,899,917,944,976,1050,1080,1205,1211,1237,1253,1374,1388 'classes.filter':1256 'classes.implode':709,1238 'clean':404 'clear':274 'close':857 'cms':12,30,51,60 'coalesc':306,349 'code':6,48,53,68,113,138 'collect':1157,1159,1166,1172,1176,1206,1212,1242,1278,1280,1287,1294,1392 'color':1220,1372 'combin':972 'comment':641,650 'common':1301 'compani':962 'companion':75 'compil':1475 'compile-tim':1474 'complex':789 'compon':70,92,99,279,566,582,588,606,644,652,659,1009,1103,1364 'composer.json':330,1313 'concaten':1006,1386 'condit':1463,1481,1485,1492 'configur':1502 'contain':244 'containerclass':217 'content':106,108,791,846,851,953,1180,1181 'context':270,874,1112 'control':477,490,509,1341 'convent':9,54,675,1158,1455 'correct':197,334,487,540,613 'coupl':578,1335 'cover':36,178 'cp':1498,1505,1516 'cptrigger':1500,1510 'cpurl':1521 'craft':2,11,29,50,59,87,105,133,142,148,379,678,681,822,994,1130,1401,1504 'craft-content-model':104 'craft-php-guidelin':141,677 'craft-sit':86,132 'craft-twig-guidelin':1 'craft.entries.section':1291 'craftcms.com':150,155,160 'craftcms.com/docs/5.x/development/twig.html':149 'craftcms.com/docs/5.x/reference/twig/functions.html':159 'craftcms.com/docs/5.x/reference/twig/tags.html':154 'creat':96,576 'ctr':1348 'current':1100 'current-level':1099 'custom':1410 'danc':414 'data':771,779,883,946 'datetim':1142 'de':1137,1138 'de-d':1136 'deadlin':1146 'decis':121 'deep':392 'default':300,344,1316,1399,1426 'defin':368,378,409,1355,1482 'depend':569,695 'deprec':499,1338 'desc':263,1347 'descript':185,261,655,735 'design':127 'devmod':1266 'differ':598 'direct':1457 'div':876 'doc':164,172 'document':145 'dynam':1001 'e':1298 'e.featured':1299 'e.g':275,634 'eager':1293 'edit':21,35,98 'el':225,254,740,1344 'element':115,208,252,686,691,700,706,736,783,787,858,956 'em':46 'empti':305,348,355,1398,1418,1436,1449 'empty-coalesc':347 'encod':845 'endblock':1467,1494 'endif':497,1468,1493 'endmacro':612 'endminifi':506 'endtag':729 'enough':433 'entir':746 'entri':427,1275,1290,1408,1434 'entries.filter':1297 'entry.calltoaction':215,232,242 'entry.heading':337,361,366,370,375 'entry.heroimage.one':203,340 'entry.id':885 'entry.postdate':1141 'entry.svgfield.one':971 'entry.title':200,441,493,505,547,560,1447 'entry.url':549,562 'env':1507,1525 'even':32 'everi':520,532,643 'except':538 'exclud':758 'exist':910 'expand':776 'explicit':541,757 'extends/block':593,1368 'extra':1245 'extraclass':1390 'extract':928 'fa':816 'fa-solid':815 'fallback':450 'fals':713,717,721,726,742,755,894,1438 'falsi':767 'featur':1296 'field':117 'file':26,645,662,664 'filter':768,901,905,926,950,1128 'fine':291,1272 'first':331,1300 'flex':1214,1389 'foo':945,1466,1490 'format':635 'fullnam':429 'function':158,630,784,966,1522 'gap':1218 'general.php':1512 'get':646,668,1188 'guest':1427 'guidelin':4,144,680 'h':920,989,1025,1056,1063 'h1':990,1074 'h2':1077 'h6':991,1079 'handl':43,297,862,1359 'hardcod':1371,1496,1527 'harmless':1244 'hash':774,936,1163 'hd':741 'head':199,276,336,360,492,495,496,504,737,988,993,997,1030,1042,1048,1062,1086,1119,1177,1178,1190 'header':642,649,669 'helper':636,683 'hero':1326 'heroimag':1324 'hidden':820,923,1085 'hover':1228,1229 'href':710 'html':750,844,847,850,911,932 'html-encod':843 'icon':818,1202,1203 'id':884,947 'imag':202,339,1327 'image.title':809 'image.url':807 'img':805,859 'implod':1259,1270 'includ':530,533,543,556,600,614,617 'increment':1097 'inlin':782 'inner':790 'input':853,860 'insid':277,985,1462,1486 'inspect':1267 'instal':317,324,353 'instead':1281,1517 'int':1039 'intermedi':456 'invalid':1470 'invalidargumentexcept':1121 'invis':577,1334 'involv':114 'isn':176 'isol':531,542,616 'item':205,1216 'items-cent':1215 'join':1248 'key':842,848,1208,1395 'label':343,723,725,984 'layout':72,100,666,1213 'lazi':811 'leaf':437 'leak':554,1333 'level':1002,1013,1026,1031,1036,1069,1101,1109,1123 'line':516 'link':400,420,457 'load':16,78,85,810 'local':1135,1139 'logo':963 'long':1143 'lowercas':186 'macro':580,585,589,603,607,625,1362,1454 'make':271 'mani':1513 'manipul':938 'manual':1028 'match':672,1020 'matter':529 'max':219,247 'max-w-3xl':218,246 'may':397,424,458 'maybenul':1151 'mean':273,896,1424 'means-omit':895 'merg':907,1196 'method':1288 'minif':500 'minifi':485,502,1337 'miss':460,1329 'model':107,597,1402,1407,1411 'move':1483 'multi':190,286 'multi-word':189,285 'must':534,733 'name':38,181,268,653,694,732,1207,1394,1459 'nav':234,260,1346 'navig':258 'navigation.links.all':206,235 'need':193,293,522,1016,1073,1105,1286 'nest':773,1111 'never':323,483,583,739,1526 'new':802 'nois':472 'non':872,1417 'non-empti':1416 'non-tag':871 'normal':1250 'null':42,296,341,358,373,399,403,413,426,751,759,1179,1182,1185,1204,1239,1357 'null/empty':1156 'nullsaf':387 'number':1134 'nystudio107/craft-empty-coalesce':314,1309 'nystudio107/craft-seomatic':316,1311 'object':1167,1432 'ok':345 'old':1453 'omit':743,753,897 'onto':909 'open':800 'oper':44,321,388,1304 'options.x':1451 'output':518,527,1264 'outsid':1125 'page':173,1075 'parent':574 'parseattr':925,942 'partial':74 'pass':824,1114 'pattern':93,125,1452 'perfect':290 'php':137,143,674,679 'pitfal':1302 'plain':1269 'plugin':326,1307 'polymorph':685 'portabl':304 'prefer':187,264 'prefix':890 'price':1133 'primari':687,1168,1224 'pristin':1263 'produc':513,1243 'product':1274 'programmat':992 'project':31,62,311,1514 'prop':661,697,1014,1118,1171,1175,1198,1200 'propag':402 'properti':468,1422,1442 'props.get':209,226,342,701,711,715,719,724,727,1189,1191,1235 'props.merge':1201 'provid':319 'queri':116,1276 'question':440 'raw':849 'reach':416,463 'read':839 'receiv':1011 'regardless':1419 'rel':718,720 'render':639,690,749,1018,1429 'replac':964 'requir':94,110,1308 'return':632,886,1153 'review':23 'right':444 'rout':128 'rule':730 'safe':302 'safeti':475 'scope':575,596,1366 'second':829 'section':120,648,1044,1046,1065,1078 'sectionhead':283 'see':130,140 'self':856 'self-clos':855 'seomat':351 'separ':671 'set':198,201,204,207,213,216,224,230,233,239,243,335,338,359,491,503,699,940,1174,1199,1210,1289,1295,1409 'ship':381 'short':1059,1148 'shortcut':836,1070 'silent':568,1331 'simpl':447,786 'singl':183,266,467 'single-word':182,265 'site':88,134 'size':1192 'skill':18,76,82,135 'skill-craft-twig-guidelines' 'small':34 'snake':237,1319 'solid':817 'someth':175 'source-michtio' 'space':889,1246 'space-prefix':888 'span':212,229,704,794,838 'specif':171,1441 'spell':1349 'sr':797,1082 'sr-on':796,1081 'src':806 'standard':7,49,139 'stateless':1089 'stay':442 'still':1113 'string':633,826,866,892,912,933,957,1005,1375,1385 'string-concaten':1004 'structur':1477 'subsect':1055 'support':385,592,1369 'surgic':488 'svg':914,959,965,968,970,987 'tag':153,511,521,684,693,705,781,793,804,812,837,873,903,998,1021,1024,1090 'target':714,716 'templat':14,57,89,112,152,157 'text':241,546,559,609,611,620,621,728,799,834,841,1027,1033,1052,1054,1084,1194,1226 'text-bas':1193 'text-on':833 'text-or-attribut':1032 'text-whit':1225 'text-xl':1051 'thread':1107 'throw':1120 'time':1147,1476 'timestamp':1152 'titl':1045,1047,1066,1076 'togeth':79 'tool':445,688 'topic-agent-skills' 'topic-claude-code' 'topic-claude-code-plugin' 'topic-claude-code-skills' 'topic-claude-skills' 'topic-content-modeling' 'topic-craft-cms' 'topic-craft-cms-5' 'topic-craftcms' 'topic-ddev' 'topic-php' 'topic-twig' 'track':1102 'travers':393 'treat':1414 'trigger':83,1506 'trim':482 'true':821,924,1145,1150 'trust':852 'twig':3,5,25,47,56,67,123,146,162,196,332,382,415,486,539,601,651,682,698,792,875,913,939,958,967,1029,1132,1162,1173,1209,1279,1471 'twig.symfony.com':166 'twig.symfony.com/doc/3.x/':165 'ui':587,640 'unnecessari':364 'unwant':514 'url':210,227,548,561,610,622,623,702,712,1499 'use':168,194,389,478,484,524,535,584,754,980,1007,1169,1255,1315,1322,1339,1391,1456,1515,1520 'user':1425,1431 'util':629,1183,1184,1234,1236 'v':1257,1258 'valu':448,1240,1423 'var':1508 'variabl':37,180,553,571,731,1321,1332,1458 'variant':663 'verbos':363,407,1356 'via':1503 'view':71,101,665 'visibl':526 'w':220,248,918 'want':1262 'webfetch':169 'webroot/icons/check.svg':915 'webroot/icons/logo.svg':960,969 'white':1227 'whitespac':476,481,489,508,528,1251,1340 'whose':692 'window':803 'without':405,473,563,788,1003,1022,1305 'withtimezon':1144,1149 'word':184,191,267,287 'wrap':1160 'wrapper':738 'write':20 'wrong':222,236,362,498,551,602,1365 'xl':1053 'yellow':1378 'yii':1405","prices":[{"id":"1c90d62a-f99c-401f-8ae3-fa4a95111c11","listingId":"61134df0-7aa3-487c-9c3e-def95b7f8167","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"michtio","category":"craftcms-claude-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T22:19:36.178Z"}],"sources":[{"listingId":"61134df0-7aa3-487c-9c3e-def95b7f8167","source":"github","sourceId":"michtio/craftcms-claude-skills/craft-twig-guidelines","sourceUrl":"https://github.com/michtio/craftcms-claude-skills/tree/main/skills/craft-twig-guidelines","isPrimary":false,"firstSeenAt":"2026-04-18T22:19:36.178Z","lastSeenAt":"2026-05-18T18:58:17.005Z"}],"details":{"listingId":"61134df0-7aa3-487c-9c3e-def95b7f8167","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"michtio","slug":"craft-twig-guidelines","github":{"repo":"michtio/craftcms-claude-skills","stars":42,"topics":["agent-skills","claude-code","claude-code-plugin","claude-code-skills","claude-skills","content-modeling","craft-cms","craft-cms-5","craftcms","ddev","php","twig"],"license":"mit","html_url":"https://github.com/michtio/craftcms-claude-skills","pushed_at":"2026-05-18T16:55:33Z","description":"Production-ready Claude Code skills, agents, and project templates for Craft CMS 5 development","skill_md_sha":"59f84808d9e7d5bfceae3cfb01eef219aba11299","skill_md_path":"skills/craft-twig-guidelines/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/michtio/craftcms-claude-skills/tree/main/skills/craft-twig-guidelines"},"layout":"multi","source":"github","category":"craftcms-claude-skills","frontmatter":{"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. 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)."},"skills_sh_url":"https://skills.sh/michtio/craftcms-claude-skills/craft-twig-guidelines"},"updatedAt":"2026-05-18T18:58:17.005Z"}}