MCPInstall

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.

Installs
240
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/shopify/liquid-skills --skill liquid-theme-standards
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads liquid-theme-standards automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

CSS, JS & HTML Standards for Shopify Liquid Themes

Core Principles

  1. Progressive enhancement — semantic HTML first, CSS second, JS third
  2. No external dependencies — native browser APIs only for JavaScript
  3. Design tokens — never hardcode colors, spacing, or fonts
  4. BEM naming — consistent class naming throughout
  5. Defensive CSS — handle edge cases gracefully

CSS in Liquid Themes

Where CSS Lives

LocationLiquid?Use For
{% stylesheet %}NoComponent-scoped styles (one per file)
{% style %}YesDynamic values needing Liquid (e.g., color settings)
assets/*.cssNoShared/global styles

Critical: {% stylesheet %} does NOT process Liquid. Use inline style attributes for dynamic values:

{%- comment -%} Do: inline variables {%- endcomment -%}
<div
  class="hero"
  style="--bg-color: {{ section.settings.bg_color }}; --padding: {{ section.settings.padding }}px;"
>

{%- comment -%} Don't: Liquid inside stylesheet {%- endcomment -%}
{% stylesheet %}
  .hero { background: {{ section.settings.bg_color }}; } /* Won't work */
{% endstylesheet %}

BEM Naming Convention

.block                      → Component root: .product-card
.block__element             → Child: .product-card__title
.block--modifier            → Variant: .product-card--featured
.block__element--modifier   → Element variant: .product-card__title--large

Rules:

  • Hyphens separate words: .product-card, not .productCard
  • Single element level only: .block__element, never .block__el1__el2
  • Modifier always paired with base class: class="btn btn--primary", never class="btn--primary" alone
  • Start new BEM scope when a child could be standalone
<!-- Good: single element level -->
<div class="product-card">
  <h3 class="product-card__title">{{ product.title }}</h3>
  <span class="product-card__button-label">{{ 'add_to_cart' | t }}</span>
</div>

<!-- Good: new BEM scope for standalone component -->
<div class="product-card">
  <button class="button button--primary">
    <span class="button__label">{{ 'add_to_cart' | t }}</span>
  </button>
</div>

Specificity

  • Target 0 1 0 (single class) wherever possible
  • Maximum 0 4 0 for complex parent-child cases
  • Never use IDs as selectors
  • Never use !important (comment why if absolutely forced to)
  • Avoid element selectors — use classes

CSS Nesting

/* Do: media queries inside selectors */
.header {
  width: 100%;

  @media screen and (min-width: 750px) {
    width: auto;
  }
}

/* Do: state modifiers with & */
.button {
  background: var(--color-primary);

  &:hover { background: var(--color-primary-hover); }
  &:focus-visible { outline: 2px solid var(--color-focus); }
  &[disabled] { opacity: 0.5; }
}

/* Do: parent modifier affecting children (single level) */
.card--featured {
  .card__title { font-size: var(--font-size-xl); }
}

/* Don't: nested beyond first level */
.parent {
  .child {
    .grandchild { } /* Too deep */
  }
}

Design Tokens

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):

:root {
  /* Spacing — use a consistent scale */
  --space-2xs: 0.5rem;    --space-xs: 0.75rem;   --space-sm: 1rem;
  --space-md: 1.5rem;     --space-lg: 2rem;       --space-xl: 3rem;

  /* Typography — relative units */
  --font-size-sm: 0.875rem;  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;  --font-size-xl: 1.25rem;  --font-size-2xl: 1.5rem;
}

Key principles:

  • Use rem for spacing and typography (respects user font size preferences)
  • Name tokens semantically: --space-sm not --space-16
  • Define in :root for global tokens, on component root for scoped tokens

CSS Variable Scoping

Global — in :root for theme-wide values Component-scoped — on component root, namespaced:

/* Do: namespaced */
.facets {
  --facets-padding: var(--space-md);
  --facets-z-index: 3;
}

/* Don't: generic names that collide */
.facets {
  --padding: var(--space-md);
  --z-index: 3;
}

Override via inline style for section/block settings:

<section
  class="hero"
  style="
    --hero-bg: {{ section.settings.bg_color }};
    --hero-padding: {{ section.settings.padding }}px;
  "
>

CSS Property Order

  1. Layout — position, display, flex-direction, grid-template-columns
  2. Box model — width, margin, padding, border
  3. Typography — font-family, font-size, line-height, color
  4. Visual — background, opacity, border-radius
  5. Animation — transition, animation

Logical Properties (RTL Support)

/* Do: logical properties */
padding-inline: 2rem;
padding-block: 1rem;
margin-inline: auto;
border-inline-end: 1px solid var(--color-border);
text-align: start;
inset: 0;

/* Don't: physical properties */
padding-left: 2rem;
text-align: left;
top: 0; right: 0; bottom: 0; left: 0;

Defensive CSS

.component {
  overflow-wrap: break-word;        /* Prevent text overflow */
  min-width: 0;                     /* Allow flex items to shrink */
  max-width: 100%;                  /* Constrain images/media */
  isolation: isolate;               /* Create stacking context */
}

.image-container {
  aspect-ratio: 4 / 3;             /* Prevent layout shift */
  background: var(--color-surface); /* Fallback for missing images */
}

Modern CSS Features

/* Container queries for responsive components */
.product-grid { container-type: inline-size; }
@container (min-width: 400px) {
  .product-card { grid-template-columns: 1fr 1fr; }
}

/* Fluid spacing */
.section { padding: clamp(1rem, 4vw, 3rem); }

/* Intrinsic sizing */
.content { width: min(100%, 800px); }

Performance

  • Animate only transform and opacity (never layout properties)
  • Use will-change sparingly — remove after animation
  • Use contain: content for isolated rendering
  • Use dvh instead of vh on mobile

Reduced Motion

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

JavaScript in Liquid Themes

Where JS Lives

LocationLiquid?Use For
{% javascript %}NoComponent-specific scripts (one per file)
assets/*.jsNoShared utilities, Web Components

Web Component Pattern

class ProductCard extends HTMLElement {
  connectedCallback() {
    this.button = this.querySelector('[data-add-to-cart]');
    this.button?.addEventListener('click', this.#handleClick.bind(this));
  }

  disconnectedCallback() {
    // Clean up event listeners, abort controllers
  }

  async #handleClick(event) {
    event.preventDefault();
    this.button.disabled = true;

    try {
      const formData = new FormData();
      formData.append('id', this.dataset.variantId);
      formData.append('quantity', '1');

      const response = await fetch('/cart/add.js', {
        method: 'POST',
        body: formData
      });

      if (!response.ok) throw new Error('Failed');

      this.dispatchEvent(new CustomEvent('cart:item-added', {
        detail: await response.json(),
        bubbles: true
      }));
    } catch (error) {
      console.error('Add to cart error:', error);
    } finally {
      this.button.disabled = false;
    }
  }
}

customElements.define('product-card', ProductCard);
<product-card data-variant-id="{{ product.selected_or_first_available_variant.id }}">
  <button data-add-to-cart>{{ 'products.add_to_cart' | t }}</button>
</product-card>

JavaScript Rules

RuleDoDon't
Loopsfor (const item of items)items.forEach()
Asyncasync/await.then() chains
Variablesconst by defaultlet unless reassigning
ConditionalsEarly returnsNested if/else
URLsnew URL() + URLSearchParamsString concatenation
DependenciesNative browser APIsExternal libraries
Private methods#methodName()_methodName()
TypesJSDoc @typedef, @param, @returnsUntyped

AbortController for Fetch

class DataLoader extends HTMLElement {
  #controller = null;

  async load(url) {
    this.#controller?.abort();
    this.#controller = new AbortController();

    try {
      const response = await fetch(url, { signal: this.#controller.signal });
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return await response.json();
    } catch (error) {
      if (error.name !== 'AbortError') throw error;
      return null;
    }
  }

  disconnectedCallback() {
    this.#controller?.abort();
  }
}

Component Communication

Parent → Child: Call public methods

this.querySelector('child-component')?.publicMethod(data);

Child → Parent: Dispatch custom events

this.dispatchEvent(new CustomEvent('child:action', {
  detail: { value },
  bubbles: true
}));

HTML Standards

Native Elements First

NeedUseNot
Expandable<details>/<summary>Custom accordion with JS
Dialog/modal<dialog>Custom overlay div
Tooltip/popuppopover attributeCustom positioned div
Search form<search><div class="search">
Form results<output><span class="result">

Progressive Enhancement

{%- comment -%} Works without JS {%- endcomment -%}
<details class="accordion">
  <summary>{{ block.settings.heading }}</summary>
  <div class="accordion__content">
    {{ block.settings.content }}
  </div>
</details>

{%- comment -%} Enhanced with JS {%- endcomment -%}
{% javascript %}
  // Optional: smooth animation, analytics tracking
{% endjavascript %}

Images

{{ image | image_url: width: 800 | image_tag:
  loading: 'lazy',
  alt: image.alt | escape,
  width: image.width,
  height: image.height
}}
  • loading="lazy" on all below-fold images
  • Always set width and height to prevent layout shift
  • Descriptive alt text; empty alt="" for decorative images

JSON Template & Config Files

Theme 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.

Common patterns

# Add a section to a template
jq '.sections.new_section = {"type": "hero", "settings": {"heading": "Welcome"}}' templates/index.json > /tmp/out && mv /tmp/out templates/index.json

# Update a setting value
jq '.current.sections.header.settings.logo_width = 200' config/settings_data.json > /tmp/out && mv /tmp/out config/settings_data.json

# Reorder sections
jq '.order += ["new_section"]' templates/index.json > /tmp/out && mv /tmp/out templates/index.json

# Remove a section
jq 'del(.sections.old_banner) | .order -= ["old_banner"]' templates/index.json > /tmp/out && mv /tmp/out templates/index.json

# Read a nested value
jq '.sections.header.settings' templates/index.json

Prefer jq over edit for any .json file modification — it validates structure, handles escaping, and avoids whitespace/formatting issues.

References

  • CSS patterns and examples
  • JavaScript patterns and examples

FAQ

What does the liquid-theme-standards skill do?

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.

How do I install liquid-theme-standards?

Run npx skills add https://github.com/shopify/liquid-skills --skill liquid-theme-standards in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/liquid-theme-standards/ (personal) or .claude/skills/liquid-theme-standards/ (project).

Which AI agents can use liquid-theme-standards?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is liquid-theme-standards safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains liquid-theme-standards?

It is published by shopify in shopify/liquid-skills, and it is an official skill from the maker of the technology it covers. It has 240 recorded installs.

More from shopify/liquid-skills

View all →

shopify-liquid-themes

shopify/liquid-skills

Open the skill to read its SKILL.md instructions.

OfficialBusiness & Finance275 installs

liquid-theme-a11y

shopify/liquid-skills

Open the skill to read its SKILL.md instructions.

OfficialFrontend & Design230 installs

Popular frontend & design skills

View category →

setup-matt-pocock-skills

mattpocock/skills

Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills.

Frontend & Design1.1M installs

frontend-design

anthropics/skills

Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.

OfficialFrontend & Design1M installs

hyperframes-animation

heygen-com/hyperframes

All animation knowledge for HyperFrames — atomic motion rules, multi-phase scene blueprints, scene transitions, broader motion-design techniques, AND the seven runtime adapters (GSAP default, plus Lottie, Three.js, Anime.js, CSS keyframes, Web Animations API, TypeGPU). Use for any motion or animation task: pick 2-4 rules and compose, or load a blueprint, or look up runtime-specific API (e.g. GSAP eases / Lottie player / Three.js mixer). Also covers auditing an existing composition's choreography (animation map) and 24 named text-animation effects. HyperFrames-native: single paused timeline, seek-safe, deterministic.

prototype

mattpocock/skills

Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.

Frontend & Design999.2K installs

hyperframes-core

heygen-com/hyperframes

The HyperFrames composition contract — build one renderable project. Use for composition structure, the data- timing attributes, class="clip", tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Read before writing composition HTML.

Frontend & Design930.5K installs

vercel-react-best-practices

vercel-labs/agent-skills

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.

OfficialFrontend & Design843.1K installs