npx skills add ...
npx skills add benjaminsehl/liquid-skills --skill liquid-theme-standards
CSS, JavaScript, and HTML coding standards for Shopify Liquid themes. Covers BEM naming inside stylesheet tags, design tokens, CSS custom properties, Web Components for themes, defensive CSS, and progressive enhancement. Use when writing CSS/JS/HTML in .liquid files or theme asset files.
npx skills add benjaminsehl/liquid-skills --skill liquid-theme-standards
| Location | Liquid? | Use For |
|---|---|---|
{% stylesheet %} | No | Component-scoped styles (one per file) |
{% style %} | Yes | Dynamic values needing Liquid (e.g., color settings) |
assets/*.css | No | Shared/global styles |
Critical: {% stylesheet %} does NOT process Liquid. Use inline style attributes for dynamic values:
Rules:
.product-card, not .productCard.block__element, never .block__el1__el2class="btn btn--primary", never class="btn--primary" alone0 1 0 (single class) wherever possible0 4 0 for complex parent-child cases!important (comment why if absolutely forced to)Use CSS custom properties for all values — never hardcode colors, spacing, or fonts. Define a consistent scale and reference it everywhere.
Example scale (adapt to your theme's needs):
Key principles:
rem for spacing and typography (respects user font size preferences)--space-sm not --space-16:root for global tokens, on component root for scoped tokensGlobal — in :root for theme-wide values
Component-scoped — on component root, namespaced:
Override via inline style for section/block settings:
position, display, flex-direction, grid-template-columnswidth, margin, padding, borderfont-family, font-size, line-height, colorbackground, opacity, border-radiustransition, animationtransform and opacity (never layout properties)will-change sparingly — remove after animationcontain: content for isolated renderingdvh instead of vh on mobile| Location | Liquid? | Use For |
|---|---|---|
{% javascript %} | No | Component-specific scripts (one per file) |
assets/*.js | No | Shared utilities, Web Components |
| Rule | Do | Don't |
|---|---|---|
| Loops | for (const item of items) | items.forEach() |
| Async | async/await | .then() chains |
| Variables | const by default | let unless reassigning |
| Conditionals | Early returns | Nested if/else |
| URLs | new URL() + URLSearchParams | String concatenation |
| Dependencies | Native browser APIs | External libraries |
| Private methods | #methodName() | _methodName() |
| Types | JSDoc @typedef, @param, @returns | Untyped |
Parent → Child: Call public methods
Child → Parent: Dispatch custom events
| Need | Use | Not |
|---|---|---|
| Expandable | <details>/<summary> | Custom accordion with JS |
| Dialog/modal | <dialog> | Custom overlay div |
| Tooltip/popup | popover attribute | Custom positioned div |
| Search form | <search> | <div class="search"> |
| Form results | <output> | <span class="result"> |
loading="lazy" on all below-fold imageswidth and height to prevent layout shiftalt text; empty alt="" for decorative imagesTheme templates (templates/*.json), section groups (sections/*.json), and config files (config/settings_data.json) are all JSON. Use jq via the bash tool to make surgical edits — it's safer and more reliable than string-based find-and-replace for structured data.
Prefer jq over edit for any .json file modification — it validates structure, handles escaping, and avoids whitespace/formatting issues.