{"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\nTwig 3.21.x (Craft 5) does not have the nullsafe operator (`?.`). That requires\nTwig 3.23+. Use `??` and ternaries instead:\n\n```twig\n{# Can't do this yet #}\n{{ entry?.author?.fullName }}\n\n{# Do this instead #}\n{{ entry.author.fullName ?? '' }}\n```\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\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## `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. **`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.469","qualityRationale":"deterministic score 0.47 from registry signals: · indexed on github topic:agent-skills · 39 github stars · SKILL.md body (10,040 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-01T18:56:48.960Z","embedding":null,"createdAt":"2026-04-18T22:19:36.178Z","updatedAt":"2026-05-01T18:56:48.960Z","lastSeenAt":"2026-05-01T18:56:48.960Z","tsv":"'-2':966 '-4':835,837 '/admin':1190,1221 '/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':864,1050 '10':1143 '11':1153 '12':1188 '2':1065 '3':163,1075 '3.21':380 '3.23':393 '3xl':221,249 '4':1083 '5':13,52,61,383,1089 '6':1099 '600':1126 '7':1108 '8':1117 '9':1131 'abbrevi':41,223,251,1090 'absent':696 'accent':980,1130 'accept':308,562,698,707 'access':118,899,933 'activ':796,798 'ad':868 'addit':944 'admin':1212 'alreadi':312 'also':84,687 'alt':743 'alway':15,77,301,303,333 'ambient':487 'append':865,877,897 'appli':64,442 'approach':436 'architectur':90,109,124 'aria':657,704,713,754,838,894 'array':699,816 'atom':69,126 'atoms/buttons/button--primary':479,492,553 'attr':780,793,820,832,890 'attribut':680,715,781,785,807,824,845,857,859,895,1001 'author':405 'automat':701,779 'base':942 'better':281 'bg':969,978,1124,1128 'bg-brand-acc':977,1127 'bg-brand-primari':968 'bg-yellow':1123 'blank':450 'block':1154,1158,1165,1181,1182 'blog':1039 'br':777 'brand':970,979,1129 'brief':589 'browser':996 'btn':231,257,1092 'build':784 'builder':73,103,129 'button':240,255,543 'buttontext':214 'camelcas':39,195,288,1070 'cannot':1172 'card':795,797 'case':238,917,1067 'center':964 'check':329,354,1059,1105,1217 'clariti':295 'class':245,643,697,730,749,794,815,833,860,892,952,958,984,1000,1121,1135 'classes.filter':1003 'classes.implode':644,985 'clear':274 'close':773 'cms':12,30,51,60 'coalesc':306,349 'code':6,48,53,68,113,138 'collect':904,906,913,919,923,953,959,989,1025,1027,1034,1041,1139 'color':967,1119 'combin':888 'comment':576,585 'common':1048 'compani':878 'companion':75 'compil':1168 'compile-tim':1167 'complex':724 'compon':70,92,99,279,501,517,523,541,579,587,594,1111 'composer.json':330,1060 'concaten':1133 'condit':1156,1174,1178,1185 'configur':1195 'contain':244 'containerclass':217 'content':106,108,726,762,767,869,927,928 'context':270,790 'control':412,425,444,1088 'convent':9,54,610,905,1148 'correct':197,334,422,475,548 'coupl':513,1082 'cover':36,178 'cp':1191,1198,1209 'cptrigger':1193,1203 'cpurl':1214 'craft':2,11,29,50,59,87,105,133,142,148,382,613,616,1197 'craft-content-model':104 'craft-php-guidelin':141,612 'craft-sit':86,132 'craft-twig-guidelin':1 'craft.entries.section':1038 '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,511 'ctr':1095 'data':706,714,799,862 'decis':121 'default':300,344,1063 'defin':368,378,1102,1175 'depend':504,630 'deprec':434,1085 'desc':263,1094 'descript':185,261,590,670 'design':127 'devmod':1013 'differ':533 'direct':1150 'div':792 'doc':164,172 'document':145 'e':1045 'e.featured':1046 'e.g':275,569 'eager':1040 'edit':21,35,98 'el':225,254,675,1091 'element':115,208,252,621,626,635,641,671,718,722,774,872 'em':46 'empti':305,348,355 'empty-coalesc':347 'encod':761 'endblock':1160,1187 'endif':432,1161,1186 'endmacro':547 'endminifi':441 'endtag':664 'entir':681 'entri':404,1022,1037 'entries.filter':1044 'entry.author.fullname':410 'entry.calltoaction':215,232,242 'entry.heading':337,361,366,370,375 'entry.heroimage.one':203,340 'entry.id':801 'entry.svgfield.one':887 'entry.title':200,428,440,482,495 'entry.url':484,497 'env':1200,1218 'even':32 'everi':455,467,578 'except':473 'exclud':693 'exist':826 'expand':711 'explicit':476,692 'extends/block':528,1115 'extra':992 'extraclass':1137 'extract':844 'fa':751 'fa-solid':750 'fals':648,652,656,661,677,690,810 'falsi':702 'featur':1043 'field':117 'file':26,580,597,599 'filter':703,817,821,842,866 'fine':291,1019 'first':331,1047 'flex':961,1136 'foo':861,1159,1183 'format':570 'fullnam':406 'function':158,565,719,882,1215 'gap':965 'general.php':1205 'get':581,603,935 'guidelin':4,144,615 'h':836 'handl':43,297,778,1106 'hardcod':1118,1189,1220 'harmless':991 'hash':709,852,910 'hd':676 'head':199,276,336,360,427,430,431,439,672,924,925,937 'header':577,584,604 'helper':571,618 'hero':1073 'heroimag':1071 'hidden':755,839 'hover':975,976 'href':645 'html':685,760,763,766,827,848 'html-encod':759 'icon':753,949,950 'id':800,863 'imag':202,339,1074 'image.title':744 'image.url':742 'img':740,775 'implod':1006,1017 'includ':465,468,478,491,535,549,552 'inlin':717 'inner':725 'input':769,776 'insid':277,901,1155,1179 'inspect':1014 'instal':317,324,353 'instead':397,409,1028,1210 'invalid':1163 'invis':512,1081 'involv':114 'isn':176 'isol':466,477,551 'item':205,963 'items-cent':962 'join':995 'key':758,764,955,1142 'label':343,658,660,900 'layout':72,100,601,960 'lazi':746 'leak':489,1080 'line':451 'load':16,78,85,745 'logo':879 'lowercas':186 'macro':515,520,524,538,542,560,1109,1147 'make':271 'mani':1206 'manipul':854 'match':607 'matter':464 'max':219,247 'max-w-3xl':218,246 'mean':273,812 'means-omit':811 'merg':823,943 'method':1035 'minif':435 'minifi':420,437,1084 'miss':1076 'model':107,532 'move':1176 'multi':190,286 'multi-word':189,285 'must':469,668 'name':38,181,268,588,629,667,954,1141,1152 'nav':234,260,1093 'navig':258 'navigation.links.all':206,235 'need':193,293,457,1033 'nest':708 'never':323,418,518,674,1219 'new':737 'non':788 'non-tag':787 'normal':997 'null':42,296,341,358,373,686,694,926,929,932,951,986,1104 'nullsaf':388 'nystudio107/craft-empty-coalesce':314,1056 'nystudio107/craft-seomatic':316,1058 'object':914 'ok':345 'old':1146 'omit':678,688,813 'onto':825 'open':735 'oper':44,321,389,1051 'options.x':1144 'output':453,462,1011 'page':173 'parent':509 'parseattr':841,858 'partial':74 'pattern':93,125,1145 'perfect':290 'php':137,143,609,614 'pitfal':1049 'plain':1016 'plugin':326,1054 'polymorph':620 'portabl':304 'prefer':187,264 'prefix':806 'primari':622,915,971 'pristin':1010 'produc':448,990 'product':1021 'project':31,62,311,1207 'prop':596,632,918,922,945,947 'props.get':209,226,342,636,646,650,654,659,662,936,938,982 'props.merge':948 'provid':319 'queri':116,1023 'raw':765 'rel':653,655 'render':574,625,684 'replac':880 'requir':94,110,391,1055 'return':567,802 'review':23 'rout':128 'rule':665 'safe':302 'scope':510,531,1113 'section':120,583 'sectionhead':283 'see':130,140 'self':772 'self-clos':771 'seomat':351 'separ':606 'set':198,201,204,207,213,216,224,230,233,239,243,335,338,359,426,438,634,856,921,946,957,1036,1042 'silent':503,1078 'simpl':721 'singl':183,266 'single-word':182,265 'site':88,134 'size':939 'skill':18,76,82,135 'skill-craft-twig-guidelines' 'small':34 'snake':237,1066 'solid':752 'someth':175 'source-michtio' 'space':805,993 'space-prefix':804 'span':212,229,639,729 'specif':171 'spell':1096 'sr':732 'sr-on':731 'src':741 'standard':7,49,139 'string':568,782,808,828,849,873,1122,1132 'structur':1170 'support':527,1116 'surgic':423 'svg':830,875,881,884,886,903 'tag':153,446,456,619,628,640,716,728,739,747,789,819 'target':649,651 'templat':14,57,89,112,152,157 'ternari':396 'text':241,481,494,544,546,555,556,663,734,757,941,973 'text-bas':940 'text-whit':972 'time':1169 'togeth':79 'tool':623 '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' 'trigger':83,1199 'trim':417 'true':756,840 'trust':768 'twig':3,5,25,47,56,67,123,146,162,196,332,379,392,398,421,474,536,586,617,633,727,791,829,855,874,883,909,920,956,1026,1164 'twig.symfony.com':166 'twig.symfony.com/doc/3.x/':165 'ui':522,575 'unnecessari':364 'unwant':449 'url':210,227,483,496,545,557,558,637,647,1192 'use':168,194,394,413,419,459,470,519,689,896,916,1002,1062,1069,1086,1138,1149,1208,1213 'util':564,930,931,981,983 'v':1004,1005 'valu':987 'var':1201 'variabl':37,180,488,506,666,1068,1079,1151 'variant':598 'verbos':363,1103 'via':1196 'view':71,101,600 'visibl':461 'w':220,248,834 'want':1009 'webfetch':169 'webroot/icons/check.svg':831 'webroot/icons/logo.svg':876,885 'white':974 'whitespac':411,416,424,443,463,998,1087 'whose':627 'window':738 'without':498,723,1052 'word':184,191,267,287 'wrap':907 'wrapper':673 'write':20 'wrong':222,236,362,433,486,537,1112 'x':381 'yellow':1125 'yet':403","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-01T18:56:48.960Z"}],"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":39,"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-04-30T21:00:38Z","description":"Production-ready Claude Code skills, agents, and project templates for Craft CMS 5 development","skill_md_sha":"ce522d54e4ce56b16b684360cddc7ce09ffab0ad","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-01T18:56:48.960Z"}}