> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# カスタムテンプレート

> Aftersell Cart の任意のブロックのレンダリングを独自の JSX で上書きします。テンプレートが置き換えるもの、スコープ内で使えるもの、スタイルの当て方、各ブロックの props の参照先を解説します。

**カスタムテンプレート**を使うと、個々のブロックのレンダリング方法を上書きできます。ブロックの組み込み UI の代わりに、カートは、そのブロックが通常使うのと同じデータを使って、あなた自身の JSX をレンダリングします。これは特定のブロックではなく横断的な機能で、ほとんどのブロックが **Code** タブからこの機能を公開しています。

このページでは、**すべての**ブロックに当てはまる内容を扱います。特定のブロックが渡してくる props については、[そのブロック自身のリファレンス](#props-for-each-block)を参照してください。

<div id="custom-template-vs-custom-code-block">
  ## カスタムテンプレートと Custom code ブロックの違い
</div>

似ているように聞こえますが、役割は異なります:

* **カスタムテンプレート**は、*既存ブロックのレンダリングを*独自のマークアップで*置き換え*、そのブロック自身のデータ（Header のタイトルとアイテム数、Summary の合計金額など）を渡してくれます。新しいものを追加するわけではなく、1 つのブロックの見た目を作り直すものです。
* **[Custom code](/ja/aftersell/cart/custom-code-blocks)** ブロックは、任意の HTML または React の*新しいブロックを*カート内のどこにでも*追加*します。

組み込みブロックがほぼ望みどおりだけれどレイアウトやマークアップを変えたい場合はカスタムテンプレートを、組み込みブロックではカバーできないものを追加したい場合は Custom code ブロックを使ってください。

<div id="using-a-custom-template">
  ## カスタムテンプレートの使い方
</div>

1. エディタでブロックを選択し、**Code** タブを開きます。
2. デフォルトのテンプレートを編集します。カスタムテンプレートは **JSX のみ**です（HTML か JSX かの選択肢があるのは Custom code ブロックだけです）。
3. **Compile** をクリックします。コンパイルは型を取り除き JSX をトランスパイルするため、**構文**エラーを検出します。型エラーはコンパイルを止めません。エディタが入力中にインラインで指摘し、ブロックの props を自動補完するのと同じ IntelliSense が使われます。
4. テンプレートをオンにすると、カートは組み込みのレンダリングの代わりにそれを使用します。
5. **Reset to default** で、いつでもブロックの元のテンプレートを復元できます。

<div id="writing-a-template-with-ai">
  ## AI でテンプレートを書く
</div>

Code タブには **Copy AI prompt** ボタン（✦ ワンドアイコン）があります。クリックすると、AI チャットセッション（Claude、ChatGPT など）にそのまま貼り付けられる自己完結型のブリーフがクリップボードにコピーされます。

このプロンプトには、その特定のブロック向けに有効なテンプレートを書くために AI が必要とするすべてが含まれています:

* コンパイルのルール（単一の式、`export default` なし、import なし）
* エディタの IntelliSense に表示されるものと一致する、ブロックが受け取る正確な props
* エディタが強制するロックされた関数シグネチャ
* ブロック固有のルール（金額のフォーマット、接続すべきハンドラ、アクセシビリティ要件）
* 現在のテンプレートを貼り付け、望む変更を記述する記入セクション

コピーしたら AI セッションを開き、プロンプトを貼り付け、末尾の 2 つの空欄（現在のテンプレートと望む変更）を埋めて送信します。AI は、エディタに貼り戻してコンパイルできる完全なテンプレートを返します。

<Tip>
  記入セクションは空欄のままにせず、既存のテンプレートを貼り付けてください。AI はそれを出発点として使うため、すでに行ったカスタマイズがデフォルトに置き換えられることなく引き継がれます。
</Tip>

<Note>
  プロンプトはブロックごとに固有です。**Copy AI prompt** ボタンは、カスタムテンプレートをサポートするブロックにのみ表示されます。
</Note>

<Tip>
  出発点となるデフォルトのテンプレートは、**ブロックの組み込みマークアップの動作するコピー**なので、白紙のページからではなく、正しくレンダリングされるリファレンスを修正する形で始められます。そのリファレンスを取り戻したくなったら、いつでも **Reset to default** を使ってください。

  必ずしもバイト単位で一致するわけではありません。Header のデフォルトテンプレートは `logoUrl` もレンダリングしますが、組み込みマークアップにはその配置場所がないため、アップロードしたヘッダー画像が最初に表示されるのは、そのテンプレートをオンにしたときです。
</Tip>

<div id="what-your-template-replaces">
  ## テンプレートが置き換えるもの
</div>

テンプレートはブロックのレンダリングを**完全に**置き換えます。あなたの JSX の周りにラッパーは残らないため、削除を始める前に知っておくべき影響があります:

| 失うもの                   | その意味                                                                                             |
| ---------------------- | ------------------------------------------------------------------------------------------------ |
| ブロックのラッパー要素            | マークアップを包むものが何もなくなります。ブロックが提供していたパディング、配置、レイアウトは、すべて自分で用意することになります。                               |
| **ブロックの Design タブの設定** | デザイン設定は組み込みラッパーにインラインスタイルとして適用されますが、そのラッパーがなくなります。Design タブで設定した色、余白、角丸は、このブロックには**適用されなくなります**。 |
| 組み込みのアクセシビリティ対応        | `aria-label`、フォーカス処理、セマンティックな要素は、あなたの JSX に含めた場合にのみ存在します。                                        |

<Warning>
  **Design タブは特に見落とされがちなポイントです。** カスタムテンプレートが有効な間、Design タブのフィールドは無効化され、「Design」見出しの横に警告アイコンが表示されます。アイコンにカーソルを合わせると理由が表示されます。代わりに、[インラインまたは独自の CSS](#styling-a-custom-template) を使ってテンプレートからブロックにスタイルを当ててください。カスタムテンプレートをオフにすると、フィールドはすぐに再び有効になります。
</Warning>

保持されるもの: カート内でのブロックの位置、表示切り替え、設定（受け取る props には引き続き反映されます）、カートの [Custom CSS](/ja/aftersell/cart/custom-css) パネル、そして**組み込みのローディングスケルトン**です。

最後の項目は意外に思われがちです。ブロックはテンプレートに到達する*前に*カートがまだ読み込み中かどうかを確認するため、読み込み中は組み込みのスケルトンがレンダリングされ、テンプレートはカートの準備が整ってから実行されます。ローディング状態を自作する必要はありません。

<div id="whats-available-inside-a-template">
  ## テンプレート内で利用できるもの
</div>

テンプレートは単一の関数コンポーネントです。**TSX** からコンパイルされるため、型注釈は許可され、コンパイル時に取り除かれます。デフォルトのテンプレートが型注釈付きで書かれているのはそのためです:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function Header(props: HeaderProps) {
  return <div>{/* … */}</div>;
}
```

**シグネチャの行と閉じ括弧はロックされています。** エディタはどちらも編集させず、カーソルを合わせると「Locked — this line can't be edited.」と表示されます。本体はその間に記述します。これらを置き換えられる唯一の手段が **Reset to default** です。

その他に重要な点:

* **使えるフックは 5 つです:** `useState`、`useEffect`、`useMemo`、`useRef`、`useCallback`。加えて `<>…</>` のための `Fragment`。
* **import はできません。** 何も `import` できず、スコープ内に `React` オブジェクトも存在しないため、`React.useReducer` も `React.Children` も使えません。上のリストにないフックは利用できません。
* **props は読み取り専用です。** props を書き換えても意味のある結果にはなりません。カートを変更するには、props に直接書き込むのではなく、ブロックが提供するハンドラの props（`onClose`、`increment`、`selectPlan` など）を使ってください。
* **`window` にはアクセスできます。** そのため、ブロックの props でカバーされないものが必要な場合、テンプレートは `window.aftersell.cart` 経由で [Cart SDK](/ja/aftersell/cart/sdk-overview) を呼び出せます。

<div id="conventions-across-every-block">
  ## すべてのブロックに共通する規約
</div>

3 つのルールがどこでも成り立ち、これを知っておけば推測作業のほとんどがなくなります:

* **`*Html` という props はサニタイズ済みのリッチテキストです。** `dangerouslySetInnerHTML` でレンダリングしてください。すでにカートのサニタイザーを通過しており、`{{total_price}}` のようなマーチャントトークンも解決済みです。
* **`string` で渡される価格は、ショップの金額フォーマットですでに整形済みです。** `number` の価格はセント単位です。ブロックはどちらか一方を渡し、各ブロックの表にどちらかが記載されています。
* **テンプレート内では `isLoading` は常に `false` です。** ブロックは組み込みのスケルトンをレンダリングし、カートの読み込みが完了してからテンプレートを呼び出すため、この prop は分岐のためではなく網羅性のために渡されています。

<Note>
  一部のブロックは特定の状態では何もレンダリングしないため、テンプレートが空のデータで呼び出されることはありません。Rewards のテンプレートが空の `milestones` を受け取ることはなく、Subscription upgrade のテンプレートが null の `view` を受け取ることもありません。各ブロックのリファレンスに該当箇所が記載されているため、空状態の分岐は省略できます。
</Note>

<div id="styling-a-custom-template">
  ## カスタムテンプレートのスタイリング
</div>

出発点となるデフォルトのテンプレートには、ブロックのクラス名が付いています。編集にどうスタイルを当てるかは、その出発点からどれだけ離れるかによって変わります。

<div id="the-two-class-families">
  ### 2 つのクラスファミリー
</div>

デフォルトテンプレートのすべての要素には対になるクラス名が付いており、それぞれの役割は大きく異なります:

| ファミリー             | 役割                                                                         | これに対して CSS を書くべきか                                       |
| ----------------- | -------------------------------------------------------------------------- | ------------------------------------------------------- |
| `cart-internal-*` | **ブロックの組み込みスタイルを担います。** カートのスタイルシートのすべてのルールはこのファミリーを対象にしています。              | いいえ。カート自身の内部機構であり、Custom CSS エディタはこれを対象とするセレクタに警告を出します。 |
| `cart-external-*` | **それ自体にスタイルを持たないフックです。** カートのスタイルシートのどのルールも対象にしておらず、あなたの CSS がつかむために存在します。 | はい。これがブロックの見た目を変更するためのサポートされた方法です。                      |

つまり、`cart-internal-header__title` はタイトルを組み込みのタイトルらしく*見せている*ものであり、`cart-external-header__title` は見た目を変えたいときにつかむべきハンドルです。

<div id="small-changes-keep-both-classnames">
  ### 小さな変更: 両方のクラス名を残す
</div>

要素の並べ替え、ラベルの変更、既存の構造の中への追加を行う場合は、クラス名には手を付けないでください。組み込みの見た目をそのまま維持でき、`cart-external-*` フックを対象にした [Custom CSS](/ja/aftersell/cart/custom-css) でスタイルを変更できます。

<div id="restructuring-drop-both-classnames">
  ### 構造の変更: 両方のクラス名を外す
</div>

微調整ではなく DOM 構造そのものを変える段階になったら、マークアップから**両方の**ファミリーを外し、代わりに[独自のクラス名](#option-1-your-own-classnames-plus-custom-css)を使ってください。それぞれに別の理由があります。

**`cart-internal-*` を外すのは、組み込みの CSS が組み込みの DOM のために書かれているからです。** 構造を変えたマークアップにこれらのクラスを残すと、もう存在しない要素を前提としたレイアウトルールを継承してしまいます。異なる子要素を期待する flex コンテナ、移動した要素間の余白、削除したものを基準にした位置指定などです。これは通常、組み込みのルールが勝ってしまうことで、自分の CSS が「効かない」という形で表面化します。

<Warning>
  **`cart-external-*` を外すのは、それが共有された名前であって、あなたのものではないからです。** これらのクラス名は組み込みのマークアップ上で特定の意味を持ち、あなたの Custom CSS はカート全体に対して一度だけ書かれます。構造を変えたテンプレートがそれらを再利用すると、書いたルールはあなたの構造と組み込みの構造の両方を対象にしてしまいます。

  問題が起きるのは、カスタムテンプレートをオフにした瞬間です。ブロックは組み込みのマークアップに戻りますが、CSS は依然としてそれを指したままで、本来想定していなかった DOM にスタイルを当ててしまいます。独自のプレフィックスを使えば両者がきれいに分離され、テンプレートのオフがクリーンな復元になります。
</Warning>

作ったものにスタイルを当てる方法は 2 つあります:

<div id="option-1-your-own-classnames-plus-custom-css">
  #### オプション 1: 独自のクラス名 + Custom CSS
</div>

保守や再利用をするものに最適です。通常はストア名やブランド名など、他と衝突しないプレフィックスをクラスに付けてください:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function Header(props) {
  return (
    <div className="northwind-custom-header">
      <div className="northwind-custom-header__title" dangerouslySetInnerHTML={{ __html: props.title }} />
      <button type="button" className="northwind-custom-header__close" onClick={props.onClose}>
        &times;
      </button>
    </div>
  );
}
```

次に、カートエディタで左パネルの **Cart settings** を選択し、右側の **Custom CSS** タブを開きます:

```css theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
.northwind-custom-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px;
}

.northwind-custom-header__title {
  font-size: 18px;
  font-weight: 600;
}

.northwind-custom-header__close:hover {
  opacity: 0.6;
}
```

プレフィックスは見た目以上に重要です。プレフィックスがないと、`.header` や `.title` のようなクラスは、カート自身のクラス、他のアプリのテンプレート、将来のブロックと衝突するおそれがあります。

<div id="option-2-inline-styles">
  #### オプション 2: インラインスタイル
</div>

CSS パネルとの行き来が不要で、すべてが 1 か所に収まります:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
```

レイアウトの骨組みや一度きりの用途に向いています。制約はよくあるものです: `:hover` などの疑似クラスは使えず、メディアクエリも使えず、ブロック間での再利用もできません。これらのいずれかが必要になったら、オプション 1 に切り替えてください。

<div id="picking-an-approach">
  ### アプローチの選び方
</div>

| 状況                      | 推奨                                                    |
| ----------------------- | ----------------------------------------------------- |
| 構造は同じで、文言や順序が異なる        | 両方のクラス名を残し、`cart-external-*` に対する Custom CSS でスタイルを変更 |
| 新しい構造で、保守していくスタイリング     | 独自のプレフィックス付きクラスを使い、カートの両ファミリーを外す                      |
| 新しい構造で、簡単なレイアウトルールが少しだけ | インラインスタイルを使い、カートの両ファミリーを外す                            |
| 複数のブロックにまたがる大量のカスタムコード  | すべてに独自のプレフィックス付きクラスを使い、どのテンプレートもクリーンにオフにできるようにする      |

<Note>
  カートは shadow root 内でレンダリングされるため、テーマのスタイルシートはその内部に届きません。カスタムテンプレートのスタイルは、テーマからではなく、カート自身の **Custom CSS** パネルまたはインラインスタイルから当てる必要があります。[Custom CSS](/ja/aftersell/cart/custom-css) を参照してください。
</Note>

<div id="when-a-template-fails">
  ## テンプレートが失敗したとき
</div>

壊れたテンプレートがカートを壊すことはありません。ブロックは**何も**レンダリングせず、その周りのすべては動作し続けます。安全ではありますが見落としやすく、ブロックがあるはずの場所の空白がその症状です。

| 失敗            | 気付くタイミング              | 報告される場所                                                      |
| ------------- | --------------------- | ------------------------------------------------------------ |
| 型エラー          | 入力中                   | エディタ内のインラインの波線。コンパイルは**ブロックされません**。コンパイラは型をチェックするのではなく取り除きます |
| 構文エラー         | **Compile** をクリックしたとき | ストアフロントに到達する前に、エディタ内                                         |
| レンダリング中のクラッシュ | 公開後、ストアフロント上          | `console.error('[aftersell-cart] module crashed: …')`        |

ブロックは目に見えるエラーを出さずに静かに消えるため、公開前に必ず[プレビュー](/ja/aftersell/cart/previewing-carts)でテンプレートを確認してください。ブロックが消えている場合は、まずブラウザのコンソールを開きます。

そうでないと想定したテンプレートはどちらもクラッシュするため、次の 2 点には注意が必要です:

* **null になり得る props。** 多くの props は通常の状況で `null` になります（ロゴがない場合の `logoUrl`、画像がない場合の `imageUrl`、単一バリアント商品の `variantTitle` など）。使う前に確認してください。
* **空になり得る配列。** `discountTags` と `discountCodes` は、むしろ `[]` であることの方が多いです。

<div id="limitations">
  ## 制限事項
</div>

* **カスタムテンプレートは表示の上書きです。** カートに対してロジックを実行する（イベントを購読する、アイテムを追加する、変更に反応する）には、[カスタムスクリプト](/ja/aftersell/cart/custom-scripts)と [Cart SDK](/ja/aftersell/cart/sdk-overview) を使ってください。
* **ほぼすべてのブロックがカスタムテンプレートをサポートしています。** 例外は、Shopify 自身の決済ボタンをホストする **[Express payments](/ja/aftersell/cart/express-payments-block)** ブロックと、**[Cart items](/ja/aftersell/cart/cart-items-block)** コンテナ自体です。ただし、その中の **Product** 行はカスタムテンプレートをサポートしています。
* **テンプレートはブロックの本質的な動作を変えることはできません。** 変わるのはブロックのデータの見せ方であって、その背後にあるデータや動作ではありません。

<div id="props-for-each-block">
  ## 各ブロックの props
</div>

すべてのブロックはそれぞれ独自のデータを渡します。型と実例付きの完全な props の表は、各ブロックのページにあります:

| ブロック                                                                                  | 受け取る props                                                                                                                  |
| ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| [Header](/ja/aftersell/cart/header-block#custom-template)                             | `title`、`logoUrl`、`leftSection`、`rightSection`、`itemCount`、`onClose`、`isLoading`                                            |
| [Banner](/ja/aftersell/cart/banner-block#custom-template)                             | `text`、`shouldUseTimer`、`isTimerExpiredAndShouldHide`、`isLoading`                                                           |
| [Rewards](/ja/aftersell/cart/rewards-block#custom-template)                           | `milestones`、`rewardsMessageHtml`、`showIcons`、`isLoading`                                                                   |
| [Cart items · Product](/ja/aftersell/cart/cart-items-block#custom-template)           | 25 個の props: 行ごとのコンテンツ、識別子、数量コントロール                                                                                         |
| [Subscription upgrade](/ja/aftersell/cart/subscription-upgrade-block#custom-template) | `view`、`selectPlan`、`onChange`、`oneTimeValue` など                                                                            |
| [Summary](/ja/aftersell/cart/summary-block#custom-template)                           | `leftHtml`、`rightHtml`、`discountCodes`、`totalPrice`、`savings` など                                                            |
| [Checkout button](/ja/aftersell/cart/checkout-button-block#custom-template)           | `label`、`href`、`isLoading`                                                                                                  |
| [Discount code](/ja/aftersell/cart/discount-code-block#custom-template)               | `discountCodeInput`、`placeholder`、`buttonText`、`isValidating`、`isInvalid`、`setDiscountCodeInput`、`handleSubmit`、`isLoading` |
| [Empty cart](/ja/aftersell/cart/empty-cart-block#custom-template)                     | `text`、`cta`、`href`                                                                                                         |
| [Image](/ja/aftersell/cart/image-block#custom-template)                               | `imageUrl`、`altText`、`maxHeight`、`fullWidth`                                                                                |
| [Notes](/ja/aftersell/cart/notes-block#custom-template)                               | `titleHtml`、`placeholder`、`noteInput`、`status`、`isExpanded`、`onNoteChange`、`onNoteBlur`、`onToggle` など                       |
| [Product add-on](/ja/aftersell/cart/product-add-on-block#custom-template)             | `addonTitleHtml`、`descriptionHtml`、`priceHtml`、`imageUrl`、`format`、`isEnabled`、`handleAdd`、`handleToggle` など                |
| [Shipping protection](/ja/aftersell/cart/shipping-protection-block#custom-template)   | `titleHtml`、`descriptionHtml`、`priceHtml`、`imageUrl`、`format`、`isEnabled`、`handleAdd`、`handleToggle` など                     |
| [Upsells](/ja/aftersell/cart/upsells-block#custom-template)                           | `title`、`addButtonText`、`layout`、`upsells`、`selectVariant`、`handleAdd`、およびカルーセルのコントロール                                      |

[Custom code](/ja/aftersell/cart/custom-code-blocks) ブロックは、ブロックのレンダリングを置き換えるのではなくマークアップを**追加**する唯一の場所なので、props も異なります: カート全体と、カートへの追加アクションです。[Custom code ブロック → Props](/ja/aftersell/cart/custom-code-blocks#props) を参照してください。
