# Customize the cart button (/themes/cart-button)

The cart button in the header is a **platform component**. It owns the mini cart (a popover on
large screens, a side sheet on small ones), the live item count, the hand-off to checkout, and
accessibility (screen reader announcements, focus handling). A theme gets it as a ready-made
piece of chrome and decides **where it sits and how it looks**, without any cart logic of its
own.

## Where it is [#where-it-is]

The header is a section group in your theme's `layout/header.json`: a `header` section whose
`bar` region holds blocks such as `logo`, `menu`, `search`, `language`, `account` and `cart`. The
`cart` block draws the cart button. In the layout file it looks like this:

```json
{
  "id": "cart-mkdl2e",
  "type": "cart",
  "settings": {
    "display": "icon_count",
    "opens": "drawer"
  }
}
```

| Setting   | Values                                                       | What it does                                                   |
| --------- | ------------------------------------------------------------ | -------------------------------------------------------------- |
| `display` | `icon`, `icon_count`, `icon_label_count`, `icon_label_total` | The icon alone, with the count, with the label and count, or with the label and total |
| `opens`   | `drawer`, `page`                                             | Opens the mini cart, or links straight to `/cart`              |

In code the same button is `CartButton` from
[`@poltura/theme-sdk/cart`](/themes/sdk/reference/cart), and the mini cart body is `MiniCart`.

## Ways to customize it [#ways-to-customize-it]

Try these in order. Each step changes more than the one before it.

### 1. Colours and radius: no code [#1-colours-and-radius-no-code]

The button uses only the theme's design tokens, so it follows the theme:

| What you see                            | Token it reads       |
| --------------------------------------- | -------------------- |
| Icon and label hover colour, badge glow | `--primary`          |
| Idle icon colour                        | `--muted-foreground` |
| Hover / open background                | `--muted`            |
| Hover / open border                    | `--border`           |
| Corner rounding                        | `--radius`           |

Change these in the theme settings in the **Customizer** (the merchant's colours) or in your
theme's `src/styles/tokens.css` and `tokens.dark.css`, and the cart button follows along with
the rest of the storefront.

### 2. Position and layout: move the block [#2-position-and-layout-move-the-block]

The `cart` block is an element like any other. Merchants drag it in the Customizer's header
tree. A theme sets the default position by placing it in `layout/header.json`, for example in a
right-aligned `stack` next to `account`. The block's style settings (spacing, visibility per
screen size) apply to its root.

### 3. Scoped CSS: restyle beyond tokens [#3-scoped-css-restyle-beyond-tokens]

The trigger carries the stable `.cart-button` class, its count `.cart-count`, its label
`.cart-button-label`, and the mini cart surface `.cart-panel`. See
[Chrome stable classes](/themes/chrome-classes). These classes sit on the mini cart button.
With `opens: page` the block draws a plain link to the cart page instead.

```css title="src/styles/theme.css"
:where(.cart-button) {
  border: 1px solid var(--primary);
  border-radius: 9999px;
}
```

> The platform owns the button's inner markup, and it can change between releases. Keep
> selectors on the stable classes, or shallow (`:where(button)`, not long descendant chains),
> so platform updates do not break them.

### 4. Label copy: theme texts [#4-label-copy-theme-texts]

The visible label and the announcements come from the platform's `cart` texts (`cart.title`,
`cart.itemCount`, `cart.empty`). A theme's `texts/<language>.json` overrides platform texts with
the same key, so a theme can rename the button per language:

```json title="texts/en.json"
{ "cart": { "title": "Bag" } }
```

### 5. Re-render the block: `CartButton` props [#5-re-render-the-block-cartbutton-props]

Override the `cart` block to draw the platform button with different props. It keeps the mini
cart, the count and the accessibility work. Your override decides what to draw, so the
merchant's **Shown as** and **Opens** settings apply only if you read them from `settings`:

```tsx title="src/blocks/cart.tsx"
import { CartButton } from '@poltura/theme-sdk/cart';
import { overrideBlock } from '@poltura/theme-sdk/elements';

function CartBlock() {
  return (
    <div className='inline-flex items-center'>
      <CartButton
        icon='bag'
        countPlacement='overlay'
        showLabel={false}
        classes={{ count: 'bg-foreground text-background' }}
      />
    </div>
  );
}

export const cart = overrideBlock('cart', CartBlock);
```

| Prop             | Values                | What it does                                               |
| ---------------- | --------------------- | ---------------------------------------------------------- |
| `icon`           | `cart`, `bag`         | The glyph                                                  |
| `showLabel`      | boolean               | The word "Cart" beside the icon; when omitted, it shows from `lg` up |
| `countPlacement` | `inline`, `overlay`   | Count beside the icon, or as a badge over it               |
| `labelLayout`    | `inline`, `stack`     | Label beside or under the icon                             |
| `classes`        | `icon`, `label`, `count` | Classes added to each part                              |

### 6. Full replacement: own the pixels [#6-full-replacement-own-the-pixels]

If the design needs a completely different trigger, render your own and read the live cart
state from `useCart`:

```tsx
import { useCart } from '@poltura/theme-sdk/cart';
import { useTranslations } from '@poltura/theme-sdk/i18n';
import { Link } from '@poltura/theme-sdk/routing';

export function CartLink() {
  const { cart } = useCart();
  const t = useTranslations('cart');
  const count = cart?.itemCount ?? 0;
  return (
    <Link href='/cart' aria-label={count > 0 ? t('itemCount', { count }) : t('title')}>
      {t('title')} {count > 0 ? count : null}
    </Link>
  );
}
```

`useCart().miniCart` (`open`, `setOpen`) opens and closes the mini cart, and `<MiniCart />` draws
its body inside a surface of your own (`surface='popover'` or `'sheet'`).

A custom trigger gives up the platform's popover and sheet, their focus handling and the built-in
announcements, and you then own all of that. Prefer steps 1 to 5 unless the design needs a different
interaction.

## What the platform keeps [#what-the-platform-keeps]

Cart changes, checkout, payments and pricing logic never move into a theme. A theme reads state
and calls actions through `@poltura/theme-sdk/cart`, and composes chrome from
`@poltura/theme-sdk/chrome`. That boundary is what lets a theme keep working through platform
updates.

## See your changes [#see-your-changes]

- **Save** in the code editor, and the editor's **Preview** shows the storefront next to your
  code.
- **Publish** builds a new version of the theme and puts it live on the storefront.

## Next [#next]

- [Product page: customize add to cart](/themes/product-add-to-cart): the buy box on the product
  page.
- [cart reference](/themes/sdk/reference/cart): `CartButton`, `MiniCart`, `useCart` and the rest.
