---
name: review-list
title: Review List
url: https://ui.frontic.com/components/sections/review-list.md
tier: sections
install: npx @frontic/ui add review-list
files:
  - components/sections/review-list/ReviewList.vue
  - components/sections/review-list/index.ts
  - components/sections/review-list/types.ts
registryDependencies:
  - avatar
  - badge
  - format
  - rating
  - separator
  - text
dependencies:
  - "@lucide/vue"
  - reka-ui
---

# Review List

Review list with avatars, author and date, star ratings, a verified-purchase mark, and the review text.

## Install

```sh
npx @frontic/ui add review-list
```

Built from: [Avatar](https://ui.frontic.com/components/ui/avatar.md), [Badge](https://ui.frontic.com/components/ui/badge.md), [Format](https://ui.frontic.com/lib/format.md), [Rating](https://ui.frontic.com/components/ui/rating.md), [Separator](https://ui.frontic.com/components/ui/separator.md), [Text](https://ui.frontic.com/lib/text.md)

npm packages: `@lucide/vue`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { ReviewList, type ReviewItem } from '@/components/sections/review-list'

const avatar = (n: number) => `https://static.frontic.com/cdn-cgi/image/width=128,format=auto/ui/avatars/${n}.jpg`

// Reviews of the Blizzard Defender.
const reviews: ReviewItem[] = [
  {
    id: '1',
    author: 'Sofia R.',
    avatar: avatar(7),
    date: '2026-10-06',
    rating: 5,
    verified: true,
    content: 'Took it up the Zugspitze in sleet and came down dry. The hood stays put in the wind, and the cuffs seal over my gloves.',
  },
  {
    id: '2',
    author: 'Daniel M.',
    avatar: avatar(2),
    date: '2026-09-30',
    rating: 5,
    verified: true,
    content: 'The aubergine looks even better in person. Warm enough for ski days without a mid layer, and the zips run smoothly with gloves on.',
  },
  {
    id: '3',
    author: 'Petra H.',
    date: '2026-09-21',
    rating: 4,
    content: 'Great jacket for the price. The fit is roomy, so I would size down if you wear it without a fleece underneath.',
  },
]
</script>

<template>
  <ReviewList :reviews="reviews" />
</template>
```

## API reference

### ReviewList

Lists customer reviews with the reviewer, date, rating and verified-purchase mark. Renders a `<div>`.

`data-slot="review-list"` · `ReviewList.vue`

Styling: [`copy-muted`](https://ui.frontic.com/DESIGN.md#copy-muted); [`section-title`](https://ui.frontic.com/DESIGN.md#section-title); [`meta`](https://ui.frontic.com/DESIGN.md#meta). Change them there and every part that extends them follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` | `'Customer Reviews'` | Screen-reader heading of the list. |
| `reviews` | `ReviewItem[]` | `[]` | The reviews to list, in the order given. |
| `verifiedLabel` | `string` | `'Verified purchase'` | Shown on the badge of a review from a verified purchase. |
| `locale` | `string` | `'en-US'` | BCP 47 locale the review dates are formatted in; the ConfigProvider's when left out. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

## Exports

```ts
import { type ReviewItem } from '@/components/sections/review-list'
```

- `ReviewItem`: below

```ts
interface ReviewItem {
  id: string
  author: string
  avatar?: string
  /** ISO date, shown in the item's locale. */
  date: string
  rating: number
  content: string
  verified?: boolean
}
```

## Styling

| Part | Recipe |
| --- | --- |
| `default` | [`review-list`](https://ui.frontic.com/DESIGN.md#review-list) |
| `copy-muted` | [`copy-muted`](https://ui.frontic.com/DESIGN.md#copy-muted) |
| `section-title` | [`section-title`](https://ui.frontic.com/DESIGN.md#section-title) |
| `meta` | [`meta`](https://ui.frontic.com/DESIGN.md#meta) |

## Source

`components/sections/review-list/ReviewList.vue`

```vue
<script lang="ts">
/** Lists customer reviews with the reviewer, date, rating and verified-purchase mark. */
export default {}
</script>

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

import type { HTMLAttributes } from 'vue'
import type { ReviewItem } from './types'
import { Avatar, AvatarFallback, AvatarImage } from '@library/components/ui/avatar'
import { Badge } from '@library/components/ui/badge'
import { Rating } from '@library/components/ui/rating'
import { Separator } from '@library/components/ui/separator'
import { formatDate } from '@library/lib/format'
import { useLocale } from 'reka-ui'
import { toRef } from 'vue'
import { initials } from '@library/lib/text'

const props = withDefaults(
  defineProps<{
    /** Screen-reader heading of the list. */
    title?: string
    /** The reviews to list, in the order given. */
    reviews?: ReviewItem[]
    /** Shown on the badge of a review from a verified purchase. */
    verifiedLabel?: string
    /** BCP 47 locale the review dates are formatted in; the ConfigProvider's when left out. */
    locale?: string
    class?: HTMLAttributes['class']
  }>(),
  {
    title: 'Customer Reviews',
    reviews: () => [],
    verifiedLabel: 'Verified purchase',
    locale: 'en-US',
  },
)
const locale = useLocale(toRef(() => props.locale))

const dateText = (date: string) => formatDate(date, { year: 'numeric', month: 'long', day: 'numeric' }, locale.value)
</script>

<template>
  <div data-slot="review-list" :class="['fui-review-list @container', props.class]">
    <div class="mx-auto max-w-2xl px-4 py-12 @2xl:px-6 @2xl:py-16 @5xl:max-w-4xl @5xl:px-8">
      <h2 class="sr-only">{{ title }}</h2>

      <div class="-my-8">
        <div v-for="(review, index) in reviews" :key="review.id" class="flex gap-4">
          <div class="shrink-0 py-8">
            <Avatar>
              <AvatarImage v-if="review.avatar" :src="review.avatar" :alt="review.author" />
              <AvatarFallback>{{ initials(review.author) }}</AvatarFallback>
            </Avatar>
          </div>

          <div class="min-w-0 flex-1">
            <Separator v-if="index > 0" />
            <div class="flex flex-col gap-3 py-8">
              <div class="flex flex-col gap-1">
                <div class="flex flex-wrap items-baseline justify-between gap-x-3">
                  <h3 data-slot="section-title" class="fui-section-title">
                    {{ review.author }}
                  </h3>
                  <p data-slot="meta" class="fui-meta">
                    <time :datetime="review.date">{{ dateText(review.date) }}</time>
                  </p>
                </div>
                <div class="flex flex-wrap items-center gap-2">
                  <Rating :rating="review.rating" :max="5" size="sm" />
                  <Badge v-if="review.verified" variant="success" size="sm">
                    <CircleCheckIcon data-icon />
                    {{ verifiedLabel }}
                  </Badge>
                </div>
              </div>

              <p data-slot="copy-muted" class="fui-copy-muted">
                {{ review.content }}
              </p>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
```

`components/sections/review-list/types.ts`

```ts
export interface ReviewItem {
  id: string
  author: string
  avatar?: string
  /** ISO date, shown in the item's locale. */
  date: string
  rating: number
  content: string
  verified?: boolean
}
```
