mcpField
L
liquid-theme-standards

liquid-theme-standards

github.com/benjaminsehl開発1212.9k 回インストール15 週間前に更新
リポジトリ

liquid-theme-standards について

Shopify Liquidテーマ向けに、CSS、JavaScript、HTMLの実装規約をまとめたリファレンススキルです。テーマ開発者が一貫した規約やプログレッシブエンハンスメントを適用し、JSONファイルを安全に編集する際に役立ちます。

主な機能

  1. 01CSS構成とBEM命名
  2. 02デザイントークンと防御的スタイル
  3. 03ネイティブブラウザーAPIとWeb Components
  4. 04セマンティックHTMLとプログレッシブエンハンスメント
  5. 05画像のアクセシビリティとレイアウト安定性
  6. 06jqによるJSONテンプレート編集

活用シーン

  1. 01LiquidテーマのCSS構成
  2. 02ネイティブJavaScriptでテーマコンポーネントを作成
  3. 03RTLと動きを減らす設定への対応
  4. 04Shopify JSONテンプレートの安全な編集

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/benjaminsehl/liquid-skills --skill liquid-theme-standards

インストール後、Claude Code で/liquid-theme-standardsを入力して読み込まれたか確認します。

同梱ファイルreferences/css-patterns.mdreferences/javascript-patterns.md

詳細

Shopify Liquid テーマ向け CSS、JS、HTML の標準

基本原則

  1. プログレッシブエンハンスメント — まずセマンティック HTML、次に CSS、最後に JS
  2. 外部依存なし — JavaScript にはブラウザー標準 API のみを使用
  3. デザイントークン — 色、余白、フォントをハードコードしない
  4. BEM 命名規則 — 一貫したクラス命名を全体で使用
  5. 防御的な CSS — エッジケースにも適切に対応

Liquid テーマの CSS

CSS の配置場所
場所Liquid 対応用途
{% stylesheet %}いいえコンポーネントにスコープされたスタイル(ファイルごとに 1 つ)
{% style %}はいLiquid が必要な動的な値(例:色の設定)
assets/*.cssいいえ共有/グローバルスタイル

重要: {% stylesheet %} では Liquid は処理されません。動的な値にはインラインの style 属性を使用してください。

{%- 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 命名規則
.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

ルール:

  • 単語の区切りにはハイフンを使用します:.product-card とし、.productCard は使用しません
  • 要素の階層は 1 つだけにします:.block__element とし、.block__el1__el2 は使用しません
  • 修飾子には常にベースクラスを併記します:class="btn btn--primary" とし、class="btn--primary" のみにはしません
  • 子要素を単独で使用できる場合は、新しい BEM スコープを開始します

<div class="product-card">
  <h3 class="product-card__title">{{ product.title }}</h3>
  <span class="product-card__button-label">{{ 'add_to_cart' | t }}</span>
</div>


<div class="product-card">
  <button class="button button--primary">
    <span class="button__label">{{ 'add_to_cart' | t }}</span>
  </button>
</div>
詳細度
  • 可能な限り 0 1 0(単一クラス)を対象にします
  • 複雑な親子関係の場合でも最大 0 4 0 にします
  • セレクターに ID を決して使用しないでください
  • !important は決して使用しないでください(どうしても必要な場合は理由をコメントに記載します)
  • 要素セレクターは避け、クラスを使用します
CSS のネスト
/* 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 */
  }
}
デザイントークン

すべての値に CSS カスタムプロパティを使用し、色、余白、フォントをハードコードしないでください。一貫したスケールを定義し、あらゆる場所で参照します。

スケールの例(テーマの要件に合わせて調整してください):

: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;
}

主な原則:

  • 余白とタイポグラフィには rem を使用します(ユーザーのフォントサイズ設定が反映されます)
  • トークンには意味に基づいた名前を付けます:--space-16 ではなく --space-sm
  • グローバルトークンは :root に、スコープ付きトークンはコンポーネントのルートに定義します
CSS 変数のスコープ

グローバル — テーマ全体に適用する値は :root に定義します コンポーネントスコープ — コンポーネントのルートに、名前空間を付けて定義します:

/* 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;
}

インラインスタイルで上書きして、セクション/ブロックの設定に対応します:

<section
  class="hero"
  style="
    --hero-bg: {{ section.settings.bg_color }};
    --hero-padding: {{ section.settings.padding }}px;
  "
>
CSS プロパティの順序
  1. レイアウト — position、display、flex-direction、grid-template-columns
  2. ボックスモデル — width、margin、padding、border
  3. タイポグラフィ — font-family、font-size、line-height、color
  4. 視覚効果 — background、opacity、border-radius
  5. アニメーション — transition、animation
論理プロパティ(RTL 対応)
/* 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;
防御的な 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 */
}
最新の CSS 機能
/* 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); }
パフォーマンス
  • アニメーションには transform と opacity のみを使用します(レイアウトプロパティは使用しません)
  • will-change は必要最小限に使用し、アニメーション後に削除します
  • 描画を分離するには contain: content を使用します
  • モバイルでは vh の代わりに dvh を使用します
動きを抑制する設定
@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;
  }
}

Liquid テーマの JavaScript

JS の配置場所
場所Liquid 対応用途
{% javascript %}いいえコンポーネント固有のスクリプト(ファイルごとに 1 つ)
assets/*.jsいいえ共有ユーティリティ、Web コンポーネント
Web コンポーネントのパターン
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 のルール
ルール推奨非推奨
ループfor (const item of items)items.forEach()
非同期処理async/await.then() チェーン
変数デフォルトで const再代入しない限り let は使用しない
条件分岐早期リターンネストした if/else
URLnew URL() + URLSearchParams文字列連結
依存関係ブラウザー標準 API外部ライブラリ
プライベートメソッド#methodName()_methodName()
型JSDoc の @typedef、@param、@returns型のないコード
Fetch での AbortController
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();
  }
}
コンポーネント間の通信

親 → 子: パブリックメソッドを呼び出します

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

子 → 親: カスタムイベントをディスパッチします

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

HTML の標準

ネイティブ要素を優先
必要なもの使用するもの使用しないもの
展開可能な要素<details>/<summary>JavaScript を使ったカスタムアコーディオン
ダイアログ/モーダル<dialog>カスタムオーバーレイの div
ツールチップ/ポップアップpopover 属性位置を指定したカスタム div
検索フォーム<search><div class="search">
フォームの結果<output><span class="result">
プログレッシブエンハンスメント
{%- 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 %}
画像
{{ image | image_url: width: 800 | image_tag:
  loading: 'lazy',
  alt: image.alt | escape,
  width: image.width,
  height: image.height
}}
  • ファーストビューより下に表示されるすべての画像に loading="lazy" を設定します
  • レイアウトシフトを防ぐため、常に width と height を設定します
  • 説明的な alt テキストを設定します。装飾画像には空の alt="" を設定します

JSON テンプレートと設定ファイル

テーマテンプレート(templates/*.json)、セクショングループ(sections/*.json)、設定ファイル(config/settings_data.json)はすべて JSON です。構造化データを編集する場合、文字列ベースの検索・置換より安全で信頼性の高い jq を、bash ツール経由で使用して慎重に編集してください。

よくあるパターン
# 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

.json ファイルを変更する場合は、edit より jq を優先してください。構造を検証し、エスケープを処理して、空白や書式設定に関する問題を回避できます。

参考資料

コメント