---
name: cart-item-compact
title: Cart Item Compact
url: https://ui.frontic.com/components/templates/cart-item-compact.md
tier: templates
install: npx @frontic/ui add cart-item-compact
files:
  - components/templates/cart-item-compact/CartItemCompact.vue
  - components/templates/cart-item-compact/index.ts
  - components/templates/cart-item-compact/types.ts
registryDependencies:
  - button
  - cart
  - image
  - number-field
  - price
dependencies:
  - "@lucide/vue"
---

# Cart Item Compact

Cart lines with a quantity field and a remove button.

## Install

```sh
npx @frontic/ui add cart-item-compact
```

Built from: [Button](https://ui.frontic.com/components/ui/button.md), [Cart](https://ui.frontic.com/lib/cart.md), [Image](https://ui.frontic.com/components/ui/image.md), [Number Field](https://ui.frontic.com/components/ui/number-field.md), [Price](https://ui.frontic.com/components/ui/price.md)

Used by: [Cart Compact](https://ui.frontic.com/components/templates/cart-compact.md)

npm packages: `@lucide/vue`

## Examples

```vue
<script setup lang="ts">
import { ref } from 'vue'
import { CartItemCompact, type CartLine } from '@/components/templates/cart-item-compact'

const items = ref<CartLine[]>([
  {
    id: 'frost-escape-parka',
    name: 'Frost Escape Parka',
    variant: 'Sun yellow · M',
    price: { amount: 14900, currency: 'EUR' },
    quantity: 1,
    image: 'https://static.frontic.com/cdn-cgi/image/width=640,format=auto/ui/products/frost-escape-parka.jpg',
  },
  {
    id: 'snowflake-ski-goggles',
    name: 'Snowflake Ski Goggles',
    variant: 'Mint · One size',
    price: { amount: 12700, currency: 'EUR', ref: 15900 },
    quantity: 2,
    image: 'https://static.frontic.com/cdn-cgi/image/width=640,format=auto/ui/products/snowflake-ski-goggles.jpg',
  },
  {
    id: 'trailmaster-22',
    name: 'TrailMaster 22',
    variant: 'Brown · 20L',
    price: { amount: 14100, currency: 'EUR' },
    quantity: 1,
    image: 'https://static.frontic.com/cdn-cgi/image/width=640,format=auto/ui/products/trailmaster-22.jpg',
  },
])
</script>

<template>
  <div class="mx-auto w-full max-w-sm">
    <CartItemCompact v-model:items="items" />
  </div>
</template>
```

## API reference

### CartItemCompact

Lists the cart's items with a quantity control and a remove button.

Styling: `cart-item-compact-image`, which extends [`thumbnail`](https://ui.frontic.com/DESIGN.md#thumbnail); [`line-title`](https://ui.frontic.com/DESIGN.md#line-title); [`meta`](https://ui.frontic.com/DESIGN.md#meta); [`quiet-action`](https://ui.frontic.com/DESIGN.md#quiet-action). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `maxQuantity` | `number` | `10` | Highest quantity a shopper can set on one item. |
| `removeLabel` | `((name: string) => string)`: `(name: string): string` | ``Remove ${name}`` | Builds the screen-reader name of an item's remove button from the item's name. |
| `quantityLabel` | `string` | `'Quantity'` | Screen-reader name of each item's quantity field. |
| `locale` | `string` |  | BCP 47 locale the prices are formatted in; the ConfigProvider's when left out. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `items` | `CartLine[]` | `[]` | The cart's lines; quantity changes and removals update it. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:items` | `value: CartLine[]` | Fires with the new value when `items` changes; `v-model:items` binds it. |

## Exports

```ts
import { type CartLine } from '@/components/templates/cart-item-compact'
```

- `CartLine`: from [Cart](https://ui.frontic.com/lib/cart.md)

## Styling

| Part | Recipe |
| --- | --- |
| `default` | [`cart-item-compact`](https://ui.frontic.com/DESIGN.md#cart-item-compact) |
| `image` | [`cart-item-compact-image`](https://ui.frontic.com/DESIGN.md#cart-item-compact-image) |
| `line-title` | [`line-title`](https://ui.frontic.com/DESIGN.md#line-title) |
| `meta` | [`meta`](https://ui.frontic.com/DESIGN.md#meta) |
| `quiet-action` | [`quiet-action`](https://ui.frontic.com/DESIGN.md#quiet-action) |

## Source

`components/templates/cart-item-compact/CartItemCompact.vue`

```vue
<script lang="ts">
/** Lists the cart's items with a quantity control and a remove button. */
export default {}
</script>

<script setup lang="ts">
import { Trash2Icon } from '@lucide/vue'

import type { HTMLAttributes } from 'vue'
import type { CartLine } from './types'
import { clampQuantity, lineTotal } from '@library/lib/cart'
import { Button } from '@library/components/ui/button'
import { Image } from '@library/components/ui/image'
import { NumberField, NumberFieldContent, NumberFieldDecrement, NumberFieldIncrement, NumberFieldInput } from '@library/components/ui/number-field'
import { Price } from '@library/components/ui/price'

const props = withDefaults(
  defineProps<{
    /** Highest quantity a shopper can set on one item. */
    maxQuantity?: number
    /** Builds the screen-reader name of an item's remove button from the item's name. */
    removeLabel?: (name: string) => string
    /** Screen-reader name of each item's quantity field. */
    quantityLabel?: string
    /** BCP 47 locale the prices are formatted in; the ConfigProvider's when left out. */
    locale?: string
    class?: HTMLAttributes['class']
  }>(),
  {
    maxQuantity: 10,
    removeLabel: (name: string) => `Remove ${name}`,
    quantityLabel: 'Quantity',
  },
)
/** The cart's lines; quantity changes and removals update it. */
const items = defineModel<CartLine[]>('items', { default: () => [] })

function setQuantity(id: string, quantity: number) {
  items.value = items.value.map((line) => (line.id === id ? { ...line, quantity: clampQuantity(quantity, props.maxQuantity) } : line))
}

function remove(id: string) {
  items.value = items.value.filter((line) => line.id !== id)
}
</script>

<template>
  <TransitionGroup
    tag="ul"
    role="list"
    :class="['fui-cart-item-compact', props.class]"
    leave-active-class="transition-opacity duration-200 ease-out"
    leave-to-class="opacity-0"
    move-class="transition-transform duration-300 ease-out"
  >
    <li v-for="line in items" :key="line.id" class="flex gap-3">
      <div data-slot="cart-item-compact-image" class="fui-cart-item-compact-image shrink-0 overflow-hidden">
        <Image :src="line.image ?? '/placeholder.svg'" :alt="line.name" class="size-full object-cover" />
      </div>
      <div class="flex min-w-0 flex-1 flex-col justify-between gap-2">
        <div class="flex items-start justify-between gap-2">
          <div class="flex min-w-0 flex-col gap-0.5">
            <h3 data-slot="line-title" class="fui-line-title">{{ line.name }}</h3>
            <p v-if="line.variant" data-slot="meta" class="fui-meta">{{ line.variant }}</p>
          </div>
          <div class="-mt-1 -me-2 flex shrink-0">
            <Button variant="ghost" color="neutral" size="icon-sm" data-slot="quiet-action" class="fui-quiet-action" :aria-label="removeLabel(line.name)" @click="remove(line.id)">
              <Trash2Icon data-icon />
            </Button>
          </div>
        </div>
        <div class="flex flex-wrap items-center justify-between gap-2">
          <NumberField
            :model-value="line.quantity"
            :min="1"
            :max="maxQuantity"
            :aria-label="quantityLabel"
            class="w-28"
            @update:model-value="(quantity?: number) => quantity !== undefined && setQuantity(line.id, quantity)"
          >
            <NumberFieldContent>
              <NumberFieldDecrement />
              <NumberFieldInput />
              <NumberFieldIncrement />
            </NumberFieldContent>
          </NumberField>
          <Price size="sm" :price="lineTotal(line)" :locale="locale" :show-discount="false" align="end" class="ms-auto shrink-0" />
        </div>
      </div>
    </li>
  </TransitionGroup>
</template>
```

`components/templates/cart-item-compact/types.ts`

```ts
export type { CartLine } from '@library/lib/cart'
```
