Shopify theme development expertise — theme architecture (OS 2.0), Shopify CLI workflows, Dawn customization, settings schema, asset optimization, accessibility, deployment, and merchant UX. Use when building, customizing, or deploying Shopify themes.
├── assets/ # CSS, JS, images, fonts
├── config/ # settings_schema.json, settings_data.json
├── layout/ # theme.liquid (base layout)
├── locales/ # Translation files (en.default.json)
├── sections/ # Reusable section files
├── snippets/ # Reusable code fragments
├── templates/ # JSON templates (OS 2.0)
│ └── customers/ # Account templates
layout/theme.liquid
└── templates/product.json
└── sections/main-product.liquid
└── snippets/product-card.liquid
└── snippets/price.liquid
shopify theme dev # Start local dev server with hot reload
shopify theme dev --store=my-store # Specify store
shopify theme dev --theme-editor-sync # Sync editor changes to local
shopify theme push # Push to connected theme
shopify theme push --unpublished # Push as unpublished (preview)
shopify theme push --allow-live # Push to live theme (use carefully)
shopify theme pull # Pull remote changes to local
shopify theme check # Lint theme for best practices
shopify theme check --auto-correct # Auto-fix common issues
shopify theme list # List all themes on store
shopify theme info # Show current theme details
shopify theme open # Open theme in browser
shopify theme dev — local server at http://127.0.0.1:9292shopify theme check — lint before committingDawn is Shopify's reference theme. Extend it, don't fork it.
custom-hero.liquid)Global settings that apply site-wide. Organized in groups.
[
{
"name": "Colors",
"settings": [
{
"type": "color",
"id": "color_primary",
"label": "Primary color",
"default": "#1a1a1a",
"info": "Used for buttons, links, and accents"
},
{
"type": "color",
"id": "color_background",
"label": "Background color",
"default": "#ffffff"
}
]
},
{
"name": "Typography",
"settings": [
{
"type": "font_picker",
"id": "font_heading",
"label": "Heading font",
"default": "helvetica_n4"
},
{
"type": "range",
"id": "font_body_scale",
"label": "Body font size scale",
"min": 80,
"max": 130,
"step": 5,
"default": 100,
"unit": "%"
}
]
}
]
color_scheme over color for theme consistencyassets/
base.css # Reset, variables, typography
component-header.css # Per-component styles
component-footer.css
section-hero.css # Per-section styles
custom.css # Custom overrides (load last)
assets/
global.js # Shared utilities, event delegation
product-form.js # Component-specific JS
cart-drawer.js # Feature-specific JS
defer attribute on script tags{% if section.settings.enable_video %}
{{ 'component-video.css' | asset_url | stylesheet_tag }}
<script src="{{ 'video-player.js' | asset_url }}" defer></script>
{% endif %}
{{
product.featured_image | image_url: width: 1200 | image_tag:
loading: 'lazy',
decoding: 'async',
widths: '200,400,600,800,1000,1200',
sizes: '(min-width: 1200px) 50vw, (min-width: 750px) 33vw, 100vw'
}}
{{
section.settings.hero_image | image_url: width: 1920 | image_tag:
loading: 'eager',
fetchpriority: 'high',
sizes: '100vw'
}}
Rules:
loading: 'eager', fetchpriority: 'high'loading: 'lazy', decoding: 'async'sizes attribute for responsive imageswidths to generate multiple srcset entries| Metric | Target | How |
|---|---|---|
| Lighthouse Performance | 90+ | Optimize images, defer JS, minimize CSS |
| LCP (Largest Contentful Paint) | < 2.5s | Preload hero image, inline critical CSS |
| FID (First Input Delay) | < 100ms | Defer non-critical JS, no main-thread blocking |
| CLS (Cumulative Layout Shift) | < 0.1 | Set image dimensions, reserve space for dynamic content |
| TBT (Total Blocking Time) | < 200ms | Split JS, use requestIdleCallback for non-critical work |
defer or async<head>font-display: swap for custom fonts<header role="banner">
<nav aria-label="Main navigation">
<main id="main-content">
<section aria-labelledby="section-heading">
<footer role="contentinfo">
<a class="skip-to-content-link" href="#main-content">
{{ 'accessibility.skip_to_content' | t }}
</a>
/* Visible focus indicators */
:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
/* Remove outline only for mouse users */
:focus:not(:focus-visible) {
outline: none;
}
<button
aria-expanded="false"
aria-controls="cart-drawer"
data-cart-toggle
>
Cart ({{ cart.item_count }})
</button>
<div id="cart-drawer" aria-hidden="true" role="dialog" aria-label="Shopping cart">
...
</div>
feat/section-name, fix/cart-bug