> ## 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: что заменяет шаблон, что доступно внутри, как его оформлять и где найти пропсы каждого блока.

**Пользовательский шаблон** позволяет переопределить отображение отдельного блока. Вместо встроенного интерфейса блока корзина отображает ваш собственный JSX, используя те же данные, которые блок использовал бы обычно. Это сквозная возможность, а не отдельный блок: большинство блоков предоставляют её на своей вкладке **Code**.

Эта страница описывает то, что относится к **каждому** блоку. Пропсы конкретного блока смотрите в [справочнике самого блока](#props-for-each-block).

<div id="custom-template-vs-custom-code-block">
  ## Пользовательский шаблон и блок Custom code
</div>

Звучат похоже, но делают разные вещи:

* **Пользовательский шаблон** *заменяет отображение существующего блока* вашей собственной разметкой и передаёт вам данные этого блока (заголовок и счётчик товаров у Header, итоги у Summary и так далее). Он не добавляет ничего нового; он переоформляет один блок.
* Блок **[Custom code](/ru/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, поэтому она ловит **синтаксические** ошибки. Ошибки типов компиляцию не останавливают — редактор подсвечивает их по мере ввода тем же IntelliSense, который автодополняет пропсы блока.
4. Включите шаблон, чтобы корзина использовала его вместо встроенного отображения.
5. **Reset to default** в любой момент восстанавливает исходный шаблон блока.

<div id="writing-a-template-with-ai">
  ## Написание шаблона с помощью ИИ
</div>

Вкладка Code включает кнопку **Copy AI prompt** (значок волшебной палочки ✦). Её нажатие копирует в буфер обмена самодостаточное задание, которое можно вставить прямо в сессию ИИ-чата (Claude, ChatGPT или аналог).

Промпт включает всё, что нужно ИИ для написания корректного шаблона именно для этого блока:

* Правила компиляции (одно выражение, без `export default`, без импортов)
* Точные пропсы, которые получает блок, совпадающие с тем, что показывает IntelliSense редактора
* Заблокированную сигнатуру функции, которую требует редактор
* Специфичные для блока правила (денежные форматы, какие обработчики подключать, требования доступности)
* Раздел для заполнения, куда вы вставляете свой текущий шаблон и описываете желаемое изменение

После копирования откройте сессию ИИ, вставьте промпт, заполните два пропуска внизу (ваш текущий шаблон и желаемое изменение) и отправьте. ИИ вернёт готовый шаблон, который вы можете вставить обратно в редактор и скомпилировать.

<Tip>
  Вставляйте свой существующий шаблон в раздел для заполнения, а не оставляйте его пустым. ИИ использует его как отправную точку, поэтому все уже сделанные вами настройки переносятся, а не заменяются шаблоном по умолчанию.
</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>

Что вы сохраняете: позицию блока в корзине, его переключатель видимости, его настройки (которые по-прежнему питают получаемые вами пропсы), панель [пользовательского CSS](/ru/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** — единственное, что может их заменить.

Что ещё важно:

* **Вам доступны пять хуков:** `useState`, `useEffect`, `useMemo`, `useRef` и `useCallback`. Плюс `Fragment` для `<>…</>`.
* **Импортов нет.** Вы не можете ничего `import`, и в области видимости нет объекта `React`, поэтому никаких `React.useReducer`, `React.Children`. Если хука нет в списке выше, он недоступен.
* **Пропсы доступны только для чтения.** Мутирование пропа не даст ничего полезного. Чтобы изменить корзину, используйте пропсы-обработчики, которые даёт блок (`onClose`, `increment`, `selectPlan` и так далее), а не запись в пропсы напрямую.
* **`window` доступен**, поэтому шаблон может обращаться к [Cart SDK](/ru/aftersell/cart/sdk-overview) через `window.aftersell.cart`, когда нужно что-то, чего пропсы блока не покрывают.

<div id="conventions-across-every-block">
  ## Соглашения для всех блоков
</div>

Три правила действуют везде, и знание их избавляет от большинства догадок:

* **Пропсы `*Html` — это предварительно очищенный форматированный текст.** Отображайте их через `dangerouslySetInnerHTML`. Они уже прошли через санитайзер корзины, а токены мерчанта вроде `{{total_price}}` уже подставлены.
* **Цены, приходящие как `string`, уже отформатированы** в денежном формате магазина. Цены как `number` — в центах. Блок даёт вам либо одно, либо другое, и таблица каждого блока указывает, что именно.
* **`isLoading` внутри шаблона всегда `false`.** Блок отображает встроенный скелетон и вызывает ваш шаблон только после загрузки корзины, поэтому проп передаётся для полноты, а не для ветвления.

<Note>
  Несколько блоков в определённых состояниях вообще ничего не возвращают, поэтому ваш шаблон никогда не вызывается с пустыми данными. Шаблон Rewards никогда не видит пустой `milestones`, а шаблон Subscription upgrade никогда не видит `view` равный null. Справочник каждого блока отмечает, где это применяется, чтобы вы могли пропустить ветку пустого состояния.
</Note>

<div id="styling-a-custom-template">
  ## Оформление пользовательского шаблона
</div>

Шаблон по умолчанию, с которого вы начинаете, несёт имена классов блока. Как оформлять ваши правки, зависит от того, насколько далеко вы уходите от этой отправной точки.

<div id="the-two-class-families">
  ### Два семейства классов
</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>

Если вы переставляете элементы, переименовываете или добавляете что-то внутри существующей структуры, оставьте имена классов в покое. Вы бесплатно сохраняете встроенный вид, а переоформляете через [пользовательский CSS](/ru/aftersell/cart/custom-css), нацеливаясь на хуки `cart-external-*`.

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

Два способа оформить то, что вы построили:

<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, и всё живёт в одном месте:

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

| Ситуация                                               | Что делать                                                                          |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| Та же структура, другие формулировки или порядок       | Сохраните оба имени класса, переоформляйте через Custom CSS по `cart-external-*`    |
| Новая структура, стили, которые вы будете поддерживать | Собственные классы с префиксом, оба семейства корзины убраны                        |
| Новая структура, несколько быстрых правил макета       | Инлайн-стили, оба семейства корзины убраны                                          |
| Много пользовательского кода в нескольких блоках       | Собственные классы с префиксом везде, чтобы любой шаблон можно было чисто выключить |

<Note>
  Корзина отображается в shadow root, поэтому таблица стилей вашей темы не может проникнуть внутрь. Стили для пользовательского шаблона должны идти из собственной панели **Custom CSS** корзины или из инлайн-стилей, а не из вашей темы. См. [Пользовательский CSS](/ru/aftersell/cart/custom-css).
</Note>

<div id="when-a-template-fails">
  ## Когда шаблон ломается
</div>

Сломанный шаблон никогда не ломает корзину. Блок отображает **ничего**, а всё вокруг него продолжает работать, что безопасно, но легко упустить: пустое место там, где должен быть ваш блок, — вот симптом.

| Сбой                  | Когда вы его увидите             | Где сообщается                                                                                                 |
| --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Ошибка типа           | По мере ввода                    | Инлайн-подчёркивание в редакторе. Оно **не** блокирует компиляцию — компилятор удаляет типы, а не проверяет их |
| Синтаксическая ошибка | При нажатии **Compile**          | В редакторе, до того как код доберётся до вашей витрины                                                        |
| Сбой при отображении  | На витрине, когда шаблон активен | `console.error('[aftersell-cart] module crashed: …')`                                                          |

Поскольку блок молча исчезает, а не выдаёт видимую ошибку, всегда проверяйте шаблон в [предпросмотре](/ru/aftersell/cart/previewing-carts) перед публикацией. Если блок пропал, сначала откройте консоль браузера.

Две вещи, от которых стоит защищаться, поскольку обе роняют шаблон, предполагающий обратное:

* **Пропсы, допускающие null.** Многие пропсы равны `null` в нормальных условиях (`logoUrl` без логотипа, `imageUrl` без изображения, `variantTitle` у товара с одним вариантом). Проверяйте перед использованием.
* **Массивы, которые могут быть пустыми.** `discountTags` и `discountCodes` гораздо чаще равны `[]`, чем нет.

<div id="limitations">
  ## Ограничения
</div>

* **Пользовательские шаблоны — это переопределения отображения.** Чтобы выполнять логику для корзины (подписываться на события, добавлять товары, реагировать на изменения), используйте [пользовательские скрипты](/ru/aftersell/cart/custom-scripts) и [Cart SDK](/ru/aftersell/cart/sdk-overview).
* **Почти каждый блок его поддерживает.** Исключения — блок **[Express payments](/ru/aftersell/cart/express-payments-block)**, который содержит собственные платёжные кнопки Shopify, и сам контейнер **[Cart items](/ru/aftersell/cart/cart-items-block)**, хотя строка **Product** внутри него пользовательский шаблон поддерживает.
* **Шаблон не может изменить то, что блок делает по существу.** Он меняет то, как представлены данные блока, а не сами данные или поведение за ними.

<div id="props-for-each-block">
  ## Пропсы каждого блока
</div>

Каждый блок передаёт собственные данные. Полная таблица пропсов с типами и рабочим примером находится на странице этого блока:

| Блок                                                                                  | Получаемые пропсы                                                                                                                  |
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [Header](/ru/aftersell/cart/header-block#custom-template)                             | `title`, `logoUrl`, `leftSection`, `rightSection`, `itemCount`, `onClose`, `isLoading`                                             |
| [Banner](/ru/aftersell/cart/banner-block#custom-template)                             | `text`, `shouldUseTimer`, `isTimerExpiredAndShouldHide`, `isLoading`                                                               |
| [Rewards](/ru/aftersell/cart/rewards-block#custom-template)                           | `milestones`, `rewardsMessageHtml`, `showIcons`, `isLoading`                                                                       |
| [Cart items · Product](/ru/aftersell/cart/cart-items-block#custom-template)           | 25 пропсов: построчное содержимое, идентификаторы и элементы управления количеством                                                |
| [Subscription upgrade](/ru/aftersell/cart/subscription-upgrade-block#custom-template) | `view`, `selectPlan`, `onChange`, `oneTimeValue` и другие                                                                          |
| [Summary](/ru/aftersell/cart/summary-block#custom-template)                           | `leftHtml`, `rightHtml`, `discountCodes`, `totalPrice`, `savings` и другие                                                         |
| [Checkout button](/ru/aftersell/cart/checkout-button-block#custom-template)           | `label`, `href`, `isLoading`                                                                                                       |
| [Discount code](/ru/aftersell/cart/discount-code-block#custom-template)               | `discountCodeInput`, `placeholder`, `buttonText`, `isValidating`, `isInvalid`, `setDiscountCodeInput`, `handleSubmit`, `isLoading` |
| [Empty cart](/ru/aftersell/cart/empty-cart-block#custom-template)                     | `text`, `cta`, `href`                                                                                                              |
| [Image](/ru/aftersell/cart/image-block#custom-template)                               | `imageUrl`, `altText`, `maxHeight`, `fullWidth`                                                                                    |
| [Notes](/ru/aftersell/cart/notes-block#custom-template)                               | `titleHtml`, `placeholder`, `noteInput`, `status`, `isExpanded`, `onNoteChange`, `onNoteBlur`, `onToggle` и другие                 |
| [Product add-on](/ru/aftersell/cart/product-add-on-block#custom-template)             | `addonTitleHtml`, `descriptionHtml`, `priceHtml`, `imageUrl`, `format`, `isEnabled`, `handleAdd`, `handleToggle` и другие          |
| [Shipping protection](/ru/aftersell/cart/shipping-protection-block#custom-template)   | `titleHtml`, `descriptionHtml`, `priceHtml`, `imageUrl`, `format`, `isEnabled`, `handleAdd`, `handleToggle` и другие               |
| [Upsells](/ru/aftersell/cart/upsells-block#custom-template)                           | `title`, `addButtonText`, `layout`, `upsells`, `selectVariant`, `handleAdd` и элементы управления каруселью                        |

Блок [Custom code](/ru/aftersell/cart/custom-code-blocks) — единственная поверхность, которая **добавляет** разметку, а не заменяет отображение блока, поэтому его пропсы другие: вся корзина плюс действие добавления в корзину. См. [Блоки пользовательского кода → Пропсы](/ru/aftersell/cart/custom-code-blocks#props).
