小売EC · 新規リソース

Sainsbury's デザインシステム.

Sainsbury’sの公開デザインガイドラインを、注文枠選択の一連の操作に適用します。実際の配色とサイズの参照、アクセシブルなラジオボタンの選択肢、更新されるサマリー、ページ内で完結する操作の適切なフィードバックを紹介します。

OpenDesign ガイド7 章リファレンス項目 38 件オリジナルアプリケーションガイド
01

Sainsbury's デザインシステム インターフェースのプレビュー

日付と時刻を変更し、料金の例を確認してから、ローカルでの選択内容を確認します。

受け取り時間帯を選択完全な例を開く
公開されているSainsbury’sのガイドをもとにした、オリジナルのHTML解釈版です。· 実装リファレンス
02

Sainsbury's デザインシステム デザインリファレンス

公開されているSainsbury’sのカラー、サイズトークン、タイポグラフィ、コンポーネントのガイドです。公開情報のブレークポイント表に相違がある場合は、統合せずに明示しています。

Sainsbury’sのパレットのロール

ソース
名前 / ルール値 / 設定用途
Primary brand base#F06C00 この研究では、ブランドアクセントとボタンの塗りに、ダークカラーのテキストを組み合わせています。
Primary hover#E55000 ホバー時の塗り。白背景で小さな文字の色として使うことはできません。
Primary pressed#C15100 押下状態。
Primary light#FEF5F0 選択状態とサマリーの背景。
Secondary base#7F0442 装飾的なアクセントとして控えめに使用。
Monochrome darker#262626 主要コンテンツ。
Monochrome dark#404040 補足コンテンツ。
Monochrome light#D8D8D8 境界線。
Monochrome lighter#F2F2F2 選択できないオプションの背景。
Success lighter#E6F4ED ローカルの確認メッセージ用の背景。
Success darker#246127 メッセージのテキスト。
Info darker#135CA3 アプリケーションのフォーカスアウトライン。

公開されているサイズトークン

ソース
名前 / ルール値 / 設定用途
xs2px公開されているサイズトークン表のスペーシング値。
sm4px公開されているサイズトークン表のスペーシング値。
md8px公開されているサイズトークン表のスペーシング値。
lg16px公開されているサイズトークン表のスペーシング値。
xl24px公開されているサイズトークン表のスペーシング値。
2xl32px公開されているサイズトークン表のスペーシング値。
3xl40px公開されているサイズトークン表のスペーシング値。
4xl48px公開されているサイズトークン表のスペーシング値。
width-sm1pxボーダーのトークン。幅と角丸の名前を混同しないでください。
width-base2pxボーダーのトークン。幅と角丸の名前を混同しないでください。
radius-base2pxボーダーのトークン。幅と角丸の名前を混同しないでください。
radius-xl12pxボーダーのトークン。幅と角丸の名前を混同しないでください。
min-width-sm576pxトークンのブレークポイント。エディトリアルレイアウトのページとは異なります。
min-width-md768pxトークンのブレークポイント。エディトリアルレイアウトのページとは異なります。
min-width-lg992pxトークンのブレークポイント。エディトリアルレイアウトのページとは異なります。
min-width-xl1200pxトークンのブレークポイント。エディトリアルレイアウトのページとは異なります。

Sainsbury’sの文字サイズスケール

ソース
名前 / ルール値 / 設定用途
Body 11rem / 1.5 / Arial Regular本文。
Caption0.875rem / 1.571 / Arial Regular補足メモ。
Label 11rem / 1.5 / Arial Boldコントロールのラベル。
Display 11.25rem / 1.3公式ガイドに記載されたブランド用ディスプレイ書体。この例では代替書体を使用しています。
Display 21.5rem / 1.333レスポンシブ時の代替はDisplay 3です。
Display 31.875rem / 1.333レスポンシブ時の代替はDisplay 4です。
Display 42.375rem / 1.316レスポンシブ時の代替はDisplay 5です。
Display 53rem / 1.292レスポンシブ時の代替はDisplay 6です。
Display 63.75rem / 1.267ドキュメントに記載されたディスプレイ書体の最大サイズ。
Responsive type thresholdlarger than 720px768pxのサイズトークンのブレークポイントとは独立しています。
03

Sainsbury's デザインシステム 完全ガイド

実際のインターフェースで、このデザインシステムを選び、適用し、レビューするための実践ガイドです。

DESIGN-sainsburys.md

詳しいガイダンス、実装の出発点、ソースへのリンクをまとめています。

ガイドをダウンロード

Sainsbury's デザインシステム Sainsbury's デザインシステム マルチブランドシステムとアクセス

ブランドテーマを1つ選ぶ

Fableは、共通のコンポーネント構造とブランド固有の値を通じて、Sainsbury’s、Argos、Nectar、Tu、Habitatをサポートしています。このガイドではSainsbury’sのパレットのみを使用します。Sainsbury’sの操作にNectarの紫を混ぜたり、すべてのブランドで同じフォントが使われると想定したりすると、明示されたシステムの境界が曖昧になります。選択したテーマは、アプリケーションのエントリーポイントに関連付けてください。

この OpenDesign ガイドから

公開ガイドと非公開パッケージを区別する

公開されているパートナー向けガイドには、Reactのコンポーネント、Fable Styles、デザイントークンパッケージについて記載されていますが、コードベースへのアクセスにはSainsbury’sの担当者を通じて取得する認証情報が必要です。この例には必要ありません。公開ガイドに基づいて作成した、完全なオリジナルのHTMLです。Fableが動作すると装ったり、アクセスできないレジストリ向けのインストールコマンドを作り出したりしていません。

Sainsbury's デザインシステム Sainsbury's デザインシステム 配色とコントラスト

オレンジには適切な文字色を組み合わせる

資料によると、ベースオレンジとダークオレンジは、白地で通常の本文テキストに使う場合のコントラスト要件を満たしません。この例では、白地に小さなオレンジ文字を置いたり、オレンジ地に小さな白文字を置いたりせず、黒いボタン文字を添えた塗りつぶしとしてオレンジを使っています。ドキュメントに記載されたブランド上の例外を、あらゆる配色がアクセシブルであるという主張にすり替えてはいけません。

フィードバックには意味のある色を使う

確認結果には、成功を示す緑の背景と濃い緑の文字を使っています。利用できない枠にはニュートラルな背景と文字ラベルを使い、選択中の枠にはチェック済みラジオボタンとオレンジの枠線を併用しています。色は補助的な手がかりです。時刻、空き状況、選択状態を示すテキストの代わりにはなりません。

Sainsbury's デザインシステム Sainsbury's デザインシステム 余白、角丸、ブレークポイントの境界

公開されているサイズスケールに従う

この例では、選択肢の間に8px、各枠内に16px、主要なグループ間に24〜32pxの余白を設けています。コントロールの角丸は2px、概要欄の角丸はドキュメントに記載された12pxです。どこにでも同じ角丸のカードを使うのではなく、こうした違いでグループ分けを表現しています。

ドキュメント間の不一致を明記する

レイアウトガイドにはデバイスのしきい値として600pxと900pxが記載されていますが、サイズトークンの表では576px、768px、992pxが示されています。タイポグラフィガイドでは、720pxを超えるとディスプレイ用文字のサイズが別途変わります。これらは同じスケールを別の名前で呼んでいるわけではありません。この実装では、列の切り替えにサイズトークンの768pxのしきい値を採用し、その選択を明記しています。

Sainsbury's デザインシステム Sainsbury's デザインシステム タイポグラフィと役立つ情報

ブランドフォントの使用範囲を守る

Sainsbury’sの公式ディスプレイスタイルではMary Ann Extra Bold、通常の本文ではArialを使います。この公開サンプルにはブランドフォントのファイルが含まれていないため、全体をArialで組んでいます。フォールバックフォントが視覚的に同一だとは主張せず、文字のスケールと行の高さの関係を維持しています。見出しのセマンティックなレベルは、ディスプレイ文字のサイズとは独立しています。

受け取り方法を読み取りやすくする

Sainsbury's デザインシステム Sainsbury's デザインシステム ラジオボタンの選択肢とローカル状態

グループ内で連動する選択肢を使う

日付コントロールを変更すると選べる時間帯が変わります。各時間帯は、名前を付けた1つのグループ内にあるネイティブのラジオボタンです。日付を変更すると、最初に利用可能な枠が選択され、概要欄も更新されます。利用不可の選択肢は表示されたままですが、選択できません。ネイティブのラジオボタンを使うことで、クリック可能なdiv要素を使ってキーボード操作を再現せずに、なじみのある操作性を提供します。

古い予約情報を残さない

確認すると、ローカルの確認メッセージが表示され、枠は予約されていないことが伝わります。日付か時間を変更すると、そのメッセージは非表示になります。本番フローで予約済みと表示するには、最新の空き状況確認とサーバーからの応答が必要です。この例では操作の仕様を切り分けて示し、バックエンドの未実装を予約完了と誤解されないようにしています。

Sainsbury's デザインシステム Sainsbury's デザインシステム タップ領域、フォーカス、レスポンシブ動作

選択肢に十分なスペースを確保する

公開されているボタンのガイドでは、48pxのタップ領域を推奨しています。日付ボタンと確認アクションはこの高さを満たし、各枠はラベルを含む全体をクリックできます。青いフォーカスアウトラインは、オレンジや淡い背景の上でも見える状態を保ちます。装飾用のバスケットマークは、支援技術から隠しています。

長い内容も折り返して表示する

画面幅が狭い場合、概要欄は選択肢一覧の後に表示され、装飾用のマークは非表示になります。枠のラベルや料金は通常のドキュメントフローに配置し、高さを柔軟にしています。翻訳後の場所名などが長くなっても、固定サイズのカード内で切り詰めないようにしてください。この英語のプロトタイプでは、後のローカライズに備えて余裕を持たせています。

Sainsbury's デザインシステム Sainsbury's デザインシステム 実装とAIエージェントによるレビュー

単体で動作するサンプルを実行する

HTMLの完全なサンプルを保存し、ローカルで配信してください。3日分と各日の3つの枠はサンプルデータとして記述されており、変更内容はすべてメモリ内に保持されます。ソースにはCSS、ラジオボタンの生成、概要欄の更新、古いフィードバックのクリア処理が含まれているため、プレビューの背後に未実装のアプリケーション層はありません。

  1. ファイル全体をindex.htmlとして保存します。
  2. 火曜日を選択し、午前中の利用不可の枠が選択できないことを確認します。
  3. 時間を変更し、概要欄を確認します。
  4. 確認を実行した後、日付を変更し、古い結果が消えることを確認します。

アクセスを確認してからFableに移行する

認可された社内連携を行う場合は、実際のパッケージアクセスを取得し、現在のテーマとコンポーネントAPIを確認してください。この例の動作を維持しながら、オリジナルのマークアップをサポート対象のコンポーネントに置き換えます。スクリーンショットを実装の証拠として扱う前に、インストール済みのトークンスケールが公開資料の内容と一致することを確認してください。

  • ブランドテーマを一貫して使う。
  • パッケージへのアクセスと現在のAPIを確認する。
  • テキストのコントラストとブランドカラーの再現性を個別に確認する。

Sainsbury's デザインシステム Sainsbury's デザインシステムのコンポーネント例

受け取り枠選択の一連の操作

日付選択、ラジオボタンの枠、選択不可の空き状況、動的な概要欄を備えた、独立したHTML/CSS/JavaScript。

html
<!doctype html>
<html lang="en">
  <meta charset="utf-8" /><meta
    name="viewport"
    content="width=device-width,initial-scale=1"
  /><title>Collection slot study</title
  ><style>
    * {
      box-sizing: border-box;
    }
    body {
      margin: 0;
      background: #fff;
      color: #262626;
      font:
        16px/1.5 Arial,
        sans-serif;
      padding: 32px;
    }
    header {
      border-top: 8px solid #f06c00;
      padding-top: 24px;
      display: flex;
      justify-content: space-between;
      gap: 24px;
    }
    h1 {
      font-size: 38px;
      line-height: 1.316;
      margin: 8px 0 16px;
    }
    h2 {
      font-size: 24px;
      line-height: 1.333;
      margin: 0 0 16px;
    }
    .eyebrow,
    small {
      font-size: 14px;
      color: #404040;
    }
    p {
      margin: 8px 0 24px;
    }
    .basket {
      font-size: 48px;
      line-height: 1;
      background: #fef5f0;
      border-radius: 50%;
      width: 96px;
      height: 96px;
      display: grid;
      place-items: center;
      color: #7f0442;
    }
    main {
      display: grid;
      grid-template-columns: 2fr 1fr;
      gap: 32px;
      margin-top: 24px;
    }
    fieldset {
      border: 0;
      padding: 0;
      margin: 0 0 24px;
    }
    legend {
      font-weight: bold;
      margin-bottom: 16px;
    }
    .dates {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
    }
    button,
    select {
      font: inherit;
      min-height: 48px;
      padding: 12px 16px;
      border: 1px solid #737373;
      border-radius: 2px;
      background: white;
      color: #262626;
      cursor: pointer;
    }
    .dates button[aria-pressed="true"] {
      border: 2px solid #f06c00;
      background: #fef5f0;
      padding: 11px 15px;
    }
    .slots {
      display: grid;
      gap: 8px;
    }
    .slot {
      display: flex;
      gap: 12px;
      align-items: center;
      padding: 16px;
      border: 1px solid #d8d8d8;
      border-radius: 2px;
      cursor: pointer;
    }
    .slot:has(input:checked) {
      border: 2px solid #f06c00;
      padding: 15px;
      background: #fef5f0;
    }
    .slot:has(input:disabled) {
      background: #f2f2f2;
      cursor: default;
    }
    .slot input {
      accent-color: #c15100;
      width: 20px;
      height: 20px;
    }
    .slot strong {
      margin-left: auto;
    }
    .unavailable {
      color: #404040;
    }
    .primary {
      background: #f06c00;
      border: 2px solid #f06c00;
      font-weight: bold;
      color: #000;
    }
    .primary:hover {
      background: #e55000;
      border-color: #e55000;
    }
    .primary:active {
      background: #c15100;
      border-color: #c15100;
    }
    .summary {
      background: #fef5f0;
      border-radius: 12px;
      padding: 24px;
      align-self: start;
    }
    .summary dl {
      margin: 0;
    }
    .summary dt {
      font-size: 14px;
      margin-top: 16px;
    }
    .summary dd {
      margin: 4px 0;
      font-weight: bold;
    }
    #result {
      padding: 16px;
      background: #e6f4ed;
      color: #246127;
      margin-top: 24px;
    }
    button:focus-visible,
    input:focus-visible {
      outline: 3px solid #135ca3;
      outline-offset: 3px;
    }
    footer {
      border-top: 1px solid #d8d8d8;
      margin-top: 32px;
      padding-top: 16px;
      font-size: 14px;
      color: #404040;
    }
    [hidden] {
      display: none !important;
    }
    @media (max-width: 767px) {
      body {
        padding: 16px;
      }
      main {
        grid-template-columns: 1fr;
        gap: 24px;
      }
      h1 {
        font-size: 30px;
      }
      .basket {
        display: none;
      }
      .dates button {
        flex: 1;
      }
      .summary {
        padding: 16px;
      }
    }
  </style>
  <body>
    <header>
      <div>
        <span class="eyebrow">GROCERIES / ORIGINAL COLLECTION STUDY</span>
        <h1>Choose a collection slot</h1>
        <p>
          Select a sample day and time. No order or real availability is
          connected.
        </p>
      </div>
      <div class="basket" aria-hidden="true">▥</div>
    </header>
    <main>
      <section>
        <fieldset>
          <legend>Collection day</legend>
          <div class="dates" id="dates"></div>
        </fieldset>
        <fieldset>
          <legend id="slotLabel">Available times</legend>
          <div id="slots" class="slots"></div>
        </fieldset>
      </section>
      <aside class="summary">
        <h2>Collection details</h2>
        <dl>
          <dt>Sample location</dt>
          <dd>Riverside collection point</dd>
          <dt>Day and time</dt>
          <dd id="selection"></dd>
          <dt>Sample fee</dt>
          <dd id="fee"></dd>
        </dl>
        <p><small>All fees and slots are illustrative.</small></p>
        <button class="primary" id="review">Review collection</button>
        <div id="result" role="status" hidden></div>
      </aside>
    </main>
    <footer>
      Independent HTML interpretation of Sainsbury’s public guidance. Arial
      fallback; no private Fable runtime or brand font assets.
    </footer>
    <script>
      const days = [
        {
          label: "Monday",
          slots: [
            ["09:00–10:00", 1, true],
            ["12:00–13:00", 0, true],
            ["17:00–18:00", 2, false],
          ],
        },
        {
          label: "Tuesday",
          slots: [
            ["09:00–10:00", 0, false],
            ["12:00–13:00", 1, true],
            ["17:00–18:00", 1, true],
          ],
        },
        {
          label: "Wednesday",
          slots: [
            ["09:00–10:00", 0, true],
            ["12:00–13:00", 0, true],
            ["17:00–18:00", 2, true],
          ],
        },
      ];
      let day = 0,
        slot = 0;
      const $ = (id) => document.getElementById(id);
      function update() {
        const choice = days[day].slots[slot];
        $("selection").textContent = days[day].label + ", " + choice[0];
        $("fee").textContent =
          choice[1] === 0 ? "No collection fee" : "£" + choice[1].toFixed(2);
        $("result").hidden = true;
      }
      function render() {
        $("dates").replaceChildren(
          ...days.map((d, i) => {
            let b = document.createElement("button");
            b.textContent = d.label;
            b.setAttribute("aria-pressed", i === day);
            b.onclick = () => {
              day = i;
              slot = d.slots.findIndex((s) => s[2]);
              render();
            };
            return b;
          }),
        );
        $("slotLabel").textContent = days[day].label + " collection times";
        $("slots").replaceChildren(
          ...days[day].slots.map((s, i) => {
            let l = document.createElement("label");
            l.className = "slot";
            let input = document.createElement("input");
            input.type = "radio";
            input.name = "time";
            input.checked = i === slot;
            input.disabled = !s[2];
            input.onchange = () => {
              slot = i;
              update();
            };
            let text = document.createElement("span");
            text.textContent = s[0];
            let fee = document.createElement("strong");
            fee.textContent = s[2]
              ? s[1] === 0
                ? "No fee"
                : "£" + s[1].toFixed(2)
              : "Unavailable";
            l.append(input, text, fee);
            return l;
          }),
        );
        update();
      }
      $("review").onclick = () => {
        $("result").hidden = false;
        $("result").textContent =
          "Local selection ready: " +
          $("selection").textContent +
          ". No slot has been reserved.";
      };
      render();
    </script>
  </body>
</html>

コードリファレンス

  • 非公開パッケージや認証情報は不要です。
04

Sainsbury's デザインシステム エージェントのセットアップ

実装の出発点

Fableパッケージへのアクセスは、公開されているこの単体のサンプルとは別途必要です。

シェル
# Save the complete HTML example as index.html
python3 -m http.server 8080
# Open http://localhost:8080

あなたのプロジェクト向けプロンプト

AI エージェント用プロンプト
このHTMLの完全なサンプルをもとに、Sainsbury’sのガイダンスに沿った受取枠選択画面を作成してください。ブランドカラーは一貫させ、オレンジ色のアクションには黒いテキストを使用します。ラベル付きのネイティブラジオボタン、48pxのタップ領域、明示的な768pxのレイアウト切り替えを採用してください。選択が変わるたびに、確認フィードバックをクリアします。利用可能な枠は架空のものとし、Fableランタイムの使用や予約の完了を示唆しないでください。
05

Sainsbury's デザインシステム ソースと対象範囲

Sainsbury’sの公開ガイダンスをもとにしたOpenDesignアプリケーションの検討例です。これは独自のHTMLであり、非公開のFableランタイムではありません。再配布されたMary AnnのアセットではなくArialを使用し、受取枠と手数料は架空のものです。