> ## 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.

# Modèles personnalisés

> Remplacez le rendu de n'importe quel bloc du panier Aftersell avec votre propre JSX : ce qu'un modèle remplace, ce qui est disponible dans la portée, comment le styliser et où trouver les props de chaque bloc.

Un **modèle personnalisé** vous permet de remplacer le rendu d'un bloc individuel. Au lieu de l'interface intégrée du bloc, le panier affiche votre propre JSX, en utilisant les mêmes données que le bloc utiliserait normalement. Il s'agit d'une capacité transversale plutôt que d'un bloc à part entière : la plupart des blocs l'exposent depuis leur onglet **Code**.

Cette page couvre ce qui s'applique à **tous** les blocs. Pour les props qu'un bloc spécifique vous fournit, consultez [la référence du bloc en question](#props-for-each-block).

<div id="custom-template-vs-custom-code-block">
  ## Modèle personnalisé vs bloc Custom code
</div>

Ces deux notions se ressemblent mais font des choses différentes :

* Un **modèle personnalisé** *remplace le rendu d'un bloc existant* par votre propre balisage, et vous fournit les données propres à ce bloc (le titre et le nombre d'articles du Header, les totaux du Summary, etc.). Il n'ajoute rien de nouveau ; il restyle un bloc.
* Le bloc **[Custom code](/fr/aftersell/cart/custom-code-blocks)** *ajoute un nouveau bloc* de HTML ou de React arbitraire n'importe où dans le panier.

Optez pour un modèle personnalisé lorsque le bloc intégré est presque adapté mais que vous avez besoin d'une mise en page ou d'un balisage différent. Optez pour un bloc Custom code lorsque vous voulez ajouter quelque chose que les blocs intégrés ne couvrent pas.

<div id="using-a-custom-template">
  ## Utiliser un modèle personnalisé
</div>

1. Sélectionnez un bloc dans l'éditeur et ouvrez son onglet **Code**.
2. Modifiez le modèle par défaut. Les modèles personnalisés sont **uniquement en JSX** (le choix HTML-ou-JSX est exclusif au bloc Custom code).
3. Cliquez sur **Compile**. La compilation supprime les types et transpile le JSX, elle attrape donc les erreurs de **syntaxe**. Les erreurs de type n'empêchent pas la compilation : l'éditeur les signale en ligne pendant que vous tapez, avec le même IntelliSense qui autocomplète les props du bloc.
4. Activez le modèle pour que le panier l'utilise à la place du rendu intégré.
5. **Reset to default** restaure à tout moment le modèle d'origine du bloc.

<div id="writing-a-template-with-ai">
  ## Écrire un modèle avec l'IA
</div>

L'onglet Code comprend un bouton **Copy AI prompt** (icône de baguette ✦). Cliquer dessus copie dans votre presse-papiers un brief autonome que vous pouvez coller directement dans une session de chat avec une IA (Claude, ChatGPT ou similaire).

Le prompt contient tout ce dont l'IA a besoin pour écrire un modèle valide pour ce bloc spécifique :

* Les règles de compilation (expression unique, pas de `export default`, pas d'imports)
* Les props exactes que le bloc reçoit, correspondant à ce qu'affiche l'IntelliSense de l'éditeur
* La signature de fonction verrouillée imposée par l'éditeur
* Les règles propres au bloc (formats monétaires, gestionnaires à connecter, exigences d'accessibilité)
* Une section à compléter où vous collez votre modèle actuel et décrivez la modification souhaitée

Après la copie, ouvrez une session d'IA, collez le prompt, complétez les deux champs vides en bas (votre modèle actuel et la modification souhaitée), puis envoyez. L'IA renvoie un modèle complet que vous pouvez coller à nouveau dans l'éditeur et compiler.

<Tip>
  Collez votre modèle existant dans la section à compléter plutôt que de la laisser vide. L'IA l'utilise comme point de départ, de sorte que toute personnalisation déjà effectuée est conservée au lieu d'être remplacée par le modèle par défaut.
</Tip>

<Note>
  Le prompt est spécifique à chaque bloc. Le bouton **Copy AI prompt** n'apparaît que sur les blocs qui prennent en charge les modèles personnalisés.
</Note>

<Tip>
  Le modèle par défaut à partir duquel vous démarrez est une **copie fonctionnelle du balisage intégré du bloc**, vous disposez donc toujours d'une référence correcte et fonctionnelle à modifier plutôt que d'une page vierge. Utilisez **Reset to default** chaque fois que vous voulez retrouver cette référence.

  Ce n'est pas toujours une correspondance octet par octet. Le modèle par défaut du Header affiche également `logoUrl`, pour lequel le balisage intégré ne prévoit aucun emplacement, donc l'activation de ce modèle est ce qui fait apparaître pour la première fois une image d'en-tête téléversée.
</Tip>

<div id="what-your-template-replaces">
  ## Ce que votre modèle remplace
</div>

Un modèle remplace **entièrement** le rendu du bloc. Il ne reste aucun wrapper autour de votre JSX, ce qui a des conséquences à connaître avant de commencer à supprimer des éléments :

| Vous perdez                                   | Ce que cela signifie                                                                                                                                                                                                |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| L'élément wrapper du bloc                     | Rien n'entoure votre balisage. Tout padding, alignement ou mise en page que le bloc fournissait est désormais à votre charge.                                                                                       |
| **Les paramètres de l'onglet Design du bloc** | Les paramètres de design sont appliqués comme styles inline sur le wrapper intégré, et ce wrapper a disparu. Les couleurs, espacements et rayons définis dans l'onglet Design **cessent de s'appliquer** à ce bloc. |
| Les aides d'accessibilité intégrées           | Les `aria-label`, la gestion du focus et les éléments sémantiques n'existent que si votre JSX les inclut.                                                                                                           |

<Warning>
  **L'onglet Design est le piège le plus fréquent.** Tant qu'un modèle personnalisé est actif, les champs de l'onglet Design sont désactivés et une icône d'avertissement apparaît à côté de l'en-tête « Design ». Survolez l'icône pour en connaître la raison. Stylisez plutôt le bloc depuis votre modèle, soit [en inline, soit avec votre propre CSS](#styling-a-custom-template). Les champs sont réactivés dès que vous désactivez le modèle personnalisé.
</Warning>

Ce que vous conservez : la position du bloc dans le panier, son interrupteur de visibilité, ses paramètres (qui alimentent toujours les props que vous recevez), le panneau [Custom CSS](/fr/aftersell/cart/custom-css) du panier et **le squelette de chargement intégré**.

Ce dernier point surprend souvent. Le bloc vérifie si le panier est encore en cours de chargement *avant* d'atteindre votre modèle, donc le squelette intégré s'affiche pendant le chargement et votre modèle ne s'exécute qu'une fois le panier prêt. Vous n'avez pas à construire d'état de chargement.

<div id="whats-available-inside-a-template">
  ## Ce qui est disponible dans un modèle
</div>

Votre modèle est un composant fonction unique. Il est compilé à partir de **TSX**, donc les annotations de type sont autorisées et supprimées à la compilation. C'est pourquoi les modèles par défaut sont écrits avec :

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

**La ligne de signature et l'accolade fermante sont verrouillées.** L'éditeur ne vous laisse modifier ni l'une ni l'autre, et le survol affiche "Locked — this line can't be edited." Vous écrivez le corps entre les deux. **Reset to default** est la seule chose qui peut les remplacer.

Ce qui compte aussi :

* **Vous disposez de cinq hooks :** `useState`, `useEffect`, `useMemo`, `useRef` et `useCallback`. Plus `Fragment`, pour `<>…</>`.
* **Il n'y a aucun import.** Vous ne pouvez rien `import`, et il n'y a pas d'objet `React` dans la portée, donc pas de `React.useReducer`, pas de `React.Children`. Si un hook n'est pas dans la liste ci-dessus, il n'est pas disponible.
* **Les props sont en lecture seule.** Muter une prop ne servira à rien. Pour modifier le panier, utilisez les props gestionnaires que le bloc vous fournit (`onClose`, `increment`, `selectPlan`, etc.) plutôt que d'écrire directement dans les props.
* **`window` est accessible**, donc un modèle peut appeler le [Cart SDK](/fr/aftersell/cart/sdk-overview) via `window.aftersell.cart` lorsqu'il a besoin de quelque chose que les props du bloc ne couvrent pas.

<div id="conventions-across-every-block">
  ## Conventions communes à tous les blocs
</div>

Trois règles s'appliquent partout, et les connaître élimine la majeure partie des tâtonnements :

* **Les props `*Html` sont du texte enrichi pré-assaini.** Affichez-les avec `dangerouslySetInnerHTML`. Elles sont déjà passées par l'assainisseur du panier, et les jetons marchands comme `{{total_price}}` sont déjà résolus.
* **Les prix qui arrivent en tant que `string` sont déjà formatés** dans le format monétaire de la boutique. Les prix en tant que `number` sont en centimes. Un bloc vous donne l'un ou l'autre, et le tableau de chaque bloc précise lequel.
* **`isLoading` est toujours `false` dans un modèle.** Le bloc affiche son squelette intégré et n'appelle votre modèle qu'une fois le panier chargé, donc la prop est transmise par souci d'exhaustivité plutôt que pour que vous fassiez des branchements dessus.

<Note>
  Quelques blocs ne renvoient rien du tout dans certains états, de sorte que votre modèle n'est jamais appelé avec des données vides. Le modèle Rewards ne voit jamais un `milestones` vide, et le modèle Subscription upgrade ne voit jamais une `view` nulle. La référence de chaque bloc indique où cela s'applique, pour que vous puissiez ignorer la branche d'état vide.
</Note>

<div id="styling-a-custom-template">
  ## Styliser un modèle personnalisé
</div>

Le modèle par défaut à partir duquel vous démarrez porte les classnames du bloc. La façon de styliser vos modifications dépend de la distance que vous prenez par rapport à ce point de départ.

<div id="the-two-class-families">
  ### Les deux familles de classes
</div>

Chaque élément d'un modèle par défaut porte un classname apparié, et ils ont des rôles très différents :

| Famille           | Ce qu'elle fait                                                                                                                                     | Écrire du CSS dessus ?                                                                                    |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `cart-internal-*` | **Porte le style intégré du bloc.** Chaque règle de la feuille de style du panier cible cette famille.                                              | Non. C'est la plomberie interne du panier, et l'éditeur Custom CSS signale les sélecteurs qui la ciblent. |
| `cart-external-*` | **Un point d'accroche sans style propre.** Rien dans la feuille de style du panier ne le cible ; il existe pour que votre CSS puisse s'y accrocher. | Oui. C'est la méthode prise en charge pour restyler un bloc.                                              |

Ainsi, `cart-internal-header__title` est ce qui donne au titre *l'apparence* du titre intégré, et `cart-external-header__title` est la poignée que vous êtes censé saisir lorsque vous voulez changer son apparence.

<div id="small-changes-keep-both-classnames">
  ### Petites modifications : conservez les deux classnames
</div>

Si vous réordonnez des éléments, changez des libellés ou ajoutez quelque chose à l'intérieur de la structure existante, laissez les classnames tels quels. Vous conservez gratuitement l'apparence intégrée, et vous restylez via [Custom CSS](/fr/aftersell/cart/custom-css) en ciblant les points d'accroche `cart-external-*`.

<div id="restructuring-drop-both-classnames">
  ### Restructuration : supprimez les deux classnames
</div>

Dès que vous modifiez la structure du DOM plutôt que de l'ajuster, retirez **les deux** familles de votre balisage et utilisez plutôt [vos propres classnames](#option-1-your-own-classnames-plus-custom-css). Il y a une raison distincte pour chacune.

**Supprimez `cart-internal-*` parce que le CSS intégré a été écrit pour le DOM intégré.** Conservez ces classes sur un balisage restructuré et vous héritez de règles de mise en page qui supposent des éléments que vous n'avez plus : des conteneurs flex attendant d'autres enfants, des espacements entre des éléments déplacés, un positionnement relatif à quelque chose que vous avez supprimé. Cela se manifeste généralement par votre propre CSS qui « ne fonctionne pas » alors que ce sont les règles intégrées qui l'emportent.

<Warning>
  **Supprimez `cart-external-*` parce que c'est un nom partagé, pas le vôtre.** Ces classnames signifient quelque chose de précis sur le balisage intégré, et votre Custom CSS est écrit une seule fois pour tout le panier. Si un modèle restructuré les réutilise, toute règle que vous écrivez cible à la fois votre structure et la structure intégrée.

  Cela tourne mal dès que vous désactivez le modèle personnalisé : le bloc revient à son balisage intégré, et votre CSS le cible toujours, stylisant désormais un DOM pour lequel il n'a jamais été écrit. Votre propre préfixe maintient les deux proprement séparés, de sorte que désactiver un modèle est un retour en arrière propre.
</Warning>

Deux façons de styliser ce que vous avez construit :

<div id="option-1-your-own-classnames-plus-custom-css">
  #### Option 1 : vos propres classnames plus Custom CSS
</div>

Idéal pour tout ce que vous allez maintenir ou réutiliser. Donnez à vos classes un préfixe avec lequel personne d'autre n'entrera en collision, généralement le nom de votre boutique ou de votre marque :

```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>
  );
}
```

Puis, dans l'éditeur de panier, sélectionnez **Cart settings** dans le panneau de gauche et ouvrez l'onglet **Custom CSS** à droite :

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

Un préfixe compte plus qu'il n'y paraît. Sans préfixe, une classe comme `.header` ou `.title` risque d'entrer en collision avec les classes propres du panier, le modèle d'une autre application ou un futur bloc.

<div id="option-2-inline-styles">
  #### Option 2 : styles inline
</div>

Pas d'aller-retour avec le panneau CSS, et tout se trouve au même endroit :

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

Adapté pour l'échafaudage de mise en page et les cas ponctuels. Ses limites sont les habituelles : pas de `:hover` ni d'autres pseudo-classes, pas de media queries, et pas de réutilisation entre blocs. Passez à l'option 1 dès que vous avez besoin de l'un de ces éléments.

<div id="picking-an-approach">
  ### Choisir une approche
</div>

| Situation                                                   | À faire                                                                                      |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Même structure, formulation ou ordre différent              | Conservez les deux classnames, restylez via Custom CSS sur `cart-external-*`                 |
| Nouvelle structure, style que vous allez maintenir          | Vos propres classes préfixées, les deux familles du panier retirées                          |
| Nouvelle structure, quelques règles de mise en page rapides | Styles inline, les deux familles du panier retirées                                          |
| Beaucoup de code personnalisé sur plusieurs blocs           | Vos propres classes préfixées partout, pour que tout modèle puisse être désactivé proprement |

<Note>
  Le panier est rendu dans un shadow root, donc la feuille de style de votre thème ne peut pas y pénétrer. Les styles d'un modèle personnalisé doivent provenir du panneau **Custom CSS** du panier ou de styles inline, pas de votre thème. Consultez [Custom CSS](/fr/aftersell/cart/custom-css).
</Note>

<div id="when-a-template-fails">
  ## Quand un modèle échoue
</div>

Un modèle défaillant ne casse jamais le panier. Le bloc n'affiche **rien** et tout ce qui l'entoure continue de fonctionner, ce qui est sûr mais facile à manquer : un espace vide à l'emplacement de votre bloc est le symptôme.

| Échec                        | Quand vous le verrez               | Où il est signalé                                                                                                                          |
| ---------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Erreur de type               | Pendant que vous tapez             | Un soulignement en ligne dans l'éditeur. Elle ne bloque **pas** la compilation : le compilateur supprime les types au lieu de les vérifier |
| Erreur de syntaxe            | Quand vous cliquez sur **Compile** | L'éditeur, avant que cela puisse atteindre votre boutique                                                                                  |
| Un plantage pendant le rendu | Sur la boutique, une fois en ligne | `console.error('[aftersell-cart] module crashed: …')`                                                                                      |

Comme le bloc disparaît silencieusement plutôt que d'afficher une erreur visible, vérifiez toujours un modèle en [prévisualisation](/fr/aftersell/cart/previewing-carts) avant de publier. Si un bloc a disparu, ouvrez d'abord la console du navigateur.

Deux points méritent des garde-fous, car les deux font planter un modèle qui suppose le contraire :

* **Les props nullables.** De nombreuses props sont `null` dans des conditions normales (`logoUrl` sans logo, `imageUrl` sans image, `variantTitle` sur un produit à variante unique). Vérifiez avant de les utiliser.
* **Les tableaux qui peuvent être vides.** `discountTags` et `discountCodes` valent `[]` bien plus souvent qu'autrement.

<div id="limitations">
  ## Limitations
</div>

* **Les modèles personnalisés sont des remplacements d'affichage.** Pour exécuter une logique sur le panier (s'abonner aux événements, ajouter des articles, réagir aux modifications), utilisez les [Scripts personnalisés](/fr/aftersell/cart/custom-scripts) et le [Cart SDK](/fr/aftersell/cart/sdk-overview).
* **Presque tous les blocs en prennent un en charge.** Les exceptions sont le bloc **[Express payments](/fr/aftersell/cart/express-payments-block)**, qui héberge les boutons de paiement propres à Shopify, et le conteneur **[Cart items](/fr/aftersell/cart/cart-items-block)** lui-même, bien que la ligne **Product** qu'il contient prenne en charge un modèle personnalisé.
* **Un modèle ne peut pas changer ce qu'un bloc fait fondamentalement.** Il change la façon dont les données du bloc sont présentées, pas les données ni le comportement sous-jacent.

<div id="props-for-each-block">
  ## Props de chaque bloc
</div>

Chaque bloc transmet ses propres données. Le tableau complet des props, avec les types et un exemple concret, se trouve sur la page de ce bloc :

| Bloc                                                                                  | Props qu'il reçoit                                                                                                                 |
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [Header](/fr/aftersell/cart/header-block#custom-template)                             | `title`, `logoUrl`, `leftSection`, `rightSection`, `itemCount`, `onClose`, `isLoading`                                             |
| [Banner](/fr/aftersell/cart/banner-block#custom-template)                             | `text`, `shouldUseTimer`, `isTimerExpiredAndShouldHide`, `isLoading`                                                               |
| [Rewards](/fr/aftersell/cart/rewards-block#custom-template)                           | `milestones`, `rewardsMessageHtml`, `showIcons`, `isLoading`                                                                       |
| [Cart items · Product](/fr/aftersell/cart/cart-items-block#custom-template)           | 25 props : contenu par ligne, identifiants et contrôles de quantité                                                                |
| [Subscription upgrade](/fr/aftersell/cart/subscription-upgrade-block#custom-template) | `view`, `selectPlan`, `onChange`, `oneTimeValue`, et plus                                                                          |
| [Summary](/fr/aftersell/cart/summary-block#custom-template)                           | `leftHtml`, `rightHtml`, `discountCodes`, `totalPrice`, `savings`, et plus                                                         |
| [Checkout button](/fr/aftersell/cart/checkout-button-block#custom-template)           | `label`, `href`, `isLoading`                                                                                                       |
| [Discount code](/fr/aftersell/cart/discount-code-block#custom-template)               | `discountCodeInput`, `placeholder`, `buttonText`, `isValidating`, `isInvalid`, `setDiscountCodeInput`, `handleSubmit`, `isLoading` |
| [Empty cart](/fr/aftersell/cart/empty-cart-block#custom-template)                     | `text`, `cta`, `href`                                                                                                              |
| [Image](/fr/aftersell/cart/image-block#custom-template)                               | `imageUrl`, `altText`, `maxHeight`, `fullWidth`                                                                                    |
| [Notes](/fr/aftersell/cart/notes-block#custom-template)                               | `titleHtml`, `placeholder`, `noteInput`, `status`, `isExpanded`, `onNoteChange`, `onNoteBlur`, `onToggle`, et plus                 |
| [Product add-on](/fr/aftersell/cart/product-add-on-block#custom-template)             | `addonTitleHtml`, `descriptionHtml`, `priceHtml`, `imageUrl`, `format`, `isEnabled`, `handleAdd`, `handleToggle`, et plus          |
| [Shipping protection](/fr/aftersell/cart/shipping-protection-block#custom-template)   | `titleHtml`, `descriptionHtml`, `priceHtml`, `imageUrl`, `format`, `isEnabled`, `handleAdd`, `handleToggle`, et plus               |
| [Upsells](/fr/aftersell/cart/upsells-block#custom-template)                           | `title`, `addButtonText`, `layout`, `upsells`, `selectVariant`, `handleAdd`, et les contrôles du carrousel                         |

Le bloc [Custom code](/fr/aftersell/cart/custom-code-blocks) est la seule surface qui **ajoute** du balisage plutôt que de remplacer le rendu d'un bloc, donc ses props sont différentes : le panier entier, plus une action d'ajout au panier. Consultez [Blocs de code personnalisé → Props](/fr/aftersell/cart/custom-code-blocks#props).
