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…
Install
Quick install
npx skills add https://github.com/shopify/liquid-skills/tree/HEAD/plugins/liquid-skills/skills/liquid-theme-standardsnpx skills add shopify/liquid-skills --skill liquid-theme-standards --agent claude-codenpx skills add shopify/liquid-skills --skill liquid-theme-standards --agent cursornpx skills add shopify/liquid-skills --skill liquid-theme-standards --agent codexnpx skills add shopify/liquid-skills --skill liquid-theme-standards --agent opencodenpx skills add shopify/liquid-skills --skill liquid-theme-standards --agent github-copilotnpx skills add shopify/liquid-skills --skill liquid-theme-standards --agent windsurfMore install options
Shorthand — useful for multi-skill repos:
npx skills add shopify/liquid-skills --skill liquid-theme-standardsManual — clone the repo and drop the folder into your agent's skills directory:
git clone https://github.com/shopify/liquid-skills.gitcp -r liquid-skills/plugins/liquid-skills/skills/liquid-theme-standards ~/.claude/skills/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…
liquid-theme-standardsby shopify
CSS, JavaScript, and HTML coding standards for Shopify Liquid themes. Covers BEM naming inside stylesheet tags, design tokens, CSS custom properties, Web…npx skills add https://github.com/shopify/liquid-skills --skill liquid-theme-standardsDownload ZIPGitHub
CSS, JS & HTML Standards for Shopify Liquid Themes
Core Principles
- Progressive enhancement — semantic HTML first, CSS second, JS third
- No external dependencies — native browser APIs only for JavaScript
- Design tokens — never hardcode colors, spacing, or fonts
- BEM naming — consistent class naming throughout
- 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", neverclass="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 0for 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
remfor spacing and typography (respects user font size preferences)
- Name tokens semantically:
--space-smnot--space-16
- Define in
:rootfor 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
- Layout —
position,display,flex-direction,grid-template-columns
- Box model —
width,margin,padding,border
- Typography —
font-family,font-size,line-height,color
- Visual —
background,opacity,border-radius
- 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
transformandopacity(never layout properties)
- Use
will-changesparingly — remove after animation
- Use
contain: contentfor isolated rendering
- Use
dvhinstead ofvhon 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'tLoopsfor (const item of items)items.forEach()Asyncasync/await.then() chainsVariablesconst by defaultlet unless reassigningConditionalsEarly returnsNested if/elseURLsnew URL() + URLSearchParamsString concatenationDependenciesNative browser APIsExternal librariesPrivate 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
NeedUseNotExpandable<details>/<summary>Custom accordion with JSDialog/modal<dialog>Custom overlay divTooltip/popuppopover attributeCustom positioned divSearch 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
widthandheightto prevent layout shift
- Descriptive
alttext; emptyalt=""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
More skills from shopify
agent-deviceby shopifyInteract with iOS simulator or Android emulator/device using snapshot-based coordinates. Uses accessibility tree snapshots for precise element targeting, with…analyze-feedbackby shopifyAnalyze agent feedback artifacts from GitHub Actions workflow runs, extract actionable learnings, and incorporate them into skill files and CLAUDE.md. Tracks…fix-github-issueby shopifyFull workflow for fixing a GitHub issue - understand the problem, reproduce, diagnose root cause, fix, test on iOS/Android simulators, review, and raise a PRraise-prby shopifyCreate a GitHub PR for FlashList. Ensures no AI/Claude attribution in commits or PR body, follows repo conventions for title, description, and test plan.review-and-testby shopifyReview a FlashList PR or branch, run unit tests, test on iOS simulator, and verify RTL/LTR behavior. Shared context with fix-github-issue skill.triage-issueby shopifyTriage a GitHub issue — classify priority (P0/P1/P2), search for duplicates, and apply labels.upgrade-react-nativeby shopifyUpgrade the React Native fixture app to a new version. Covers JS deps, Android (Gradle, Kotlin, SDK), iOS (Podfile, pbxproj), Metro config, and third-party…liquid-theme-a11yby shopifyImplement WCAG 2.2 accessibility patterns in Shopify Liquid themes. Covers e-commerce-specific components including product cards, carousels, cart drawers,…---
Source: https://github.com/shopify/liquid-skills/tree/HEAD/plugins/liquid-skills/skills/liquid-theme-standards
Author: shopify
Discovered via: mcpservers.org
SKILL.md source
---
name: liquid-theme-standards
description: CSS, JavaScript, and HTML coding standards for Shopify Liquid themes. Covers BEM naming inside stylesheet tags, design tokens, CSS custom properties, Web…
---
# 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…
# liquid-theme-standardsby shopify
CSS, JavaScript, and HTML coding standards for Shopify Liquid themes. Covers BEM naming inside stylesheet tags, design tokens, CSS custom properties, Web…
`npx skills add https://github.com/shopify/liquid-skills --skill liquid-theme-standards`Download ZIPGitHub
## CSS, JS & HTML Standards for Shopify Liquid Themes
## Core Principles
* Progressive enhancement — semantic HTML first, CSS second, JS third
* No external dependencies — native browser APIs only for JavaScript
* Design tokens — never hardcode colors, spacing, or fonts
* BEM naming — consistent class naming throughout
* 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/*.css`NoShared/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
* Layout — `position`, `display`, `flex-direction`, `grid-template-columns`
* Box model — `width`, `margin`, `padding`, `border`
* Typography — `font-family`, `font-size`, `line-height`, `color`
* Visual — `background`, `opacity`, `border-radius`
* 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/*.js`NoShared 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'tLoops`for (const item of items)``items.forEach()`Async`async`/`await``.then()` chainsVariables`const` by default`let` unless reassigningConditionalsEarly returnsNested `if/else`URLs`new URL()` + `URLSearchParams`String concatenationDependenciesNative browser APIsExternal librariesPrivate methods`#methodName()``_methodName()`TypesJSDoc `@typedef`, `@param`, `@returns`Untyped
### 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
NeedUseNotExpandable`<details>/<summary>`Custom accordion with JSDialog/modal`<dialog>`Custom overlay divTooltip/popup`popover` attributeCustom positioned divSearch 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
## More skills from shopify
agent-deviceby shopifyInteract with iOS simulator or Android emulator/device using snapshot-based coordinates. Uses accessibility tree snapshots for precise element targeting, with…analyze-feedbackby shopifyAnalyze agent feedback artifacts from GitHub Actions workflow runs, extract actionable learnings, and incorporate them into skill files and CLAUDE.md. Tracks…fix-github-issueby shopifyFull workflow for fixing a GitHub issue - understand the problem, reproduce, diagnose root cause, fix, test on iOS/Android simulators, review, and raise a PRraise-prby shopifyCreate a GitHub PR for FlashList. Ensures no AI/Claude attribution in commits or PR body, follows repo conventions for title, description, and test plan.review-and-testby shopifyReview a FlashList PR or branch, run unit tests, test on iOS simulator, and verify RTL/LTR behavior. Shared context with fix-github-issue skill.triage-issueby shopifyTriage a GitHub issue — classify priority (P0/P1/P2), search for duplicates, and apply labels.upgrade-react-nativeby shopifyUpgrade the React Native fixture app to a new version. Covers JS deps, Android (Gradle, Kotlin, SDK), iOS (Podfile, pbxproj), Metro config, and third-party…liquid-theme-a11yby shopifyImplement WCAG 2.2 accessibility patterns in Shopify Liquid themes. Covers e-commerce-specific components including product cards, carousels, cart drawers,…
---
**Source**: https://github.com/shopify/liquid-skills/tree/HEAD/plugins/liquid-skills/skills/liquid-theme-standards
**Author**: shopify
**Discovered via**: mcpservers.org