---
title: "Quote"
description: "Blockquotes with icon, citation, avatar and labels."
requested_language: sk
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/sk/next/components/quote
version: unreleased (main)
docs_index: https://docs.systhema.app/sk/next/llms.txt
---
> This page isn't translated yet. Showing English.


`Quote` renders a blockquote with an optional quote mark, the quoted text, and a citation with an avatar and two labels. Every part is a separate component, so you compose only what a testimonial or pull quote needs.

```tsx preview title="Quote"
import {
  Paragraph,
  Quote,
  QuoteAvatar,
  QuoteCite,
  QuoteIcon,
  QuoteLabels,
  QuotePrimaryLabel,
  QuoteSecondaryLabel,
} from '@systhemaui/next'

export default function Demo() {
  return (
    <Quote className="max-w-xl">
      <QuoteIcon />
      <Paragraph type="lead">
        We replaced three style guides with one token file. Designers change a color in Figma and
        the site follows after the next sync.
      </Paragraph>
      <QuoteCite>
        <QuoteAvatar src="/demo-assets/avatar-1.webp" alt="" />
        <QuoteLabels>
          <QuotePrimaryLabel>Maya Lindqvist</QuotePrimaryLabel>
          <QuoteSecondaryLabel>Head of Design, Northwind</QuoteSecondaryLabel>
        </QuoteLabels>
      </QuoteCite>
    </Quote>
  )
}
```

## Import

```tsx
import {
  Quote,
  QuoteAvatar,
  QuoteCite,
  QuoteIcon,
  QuoteLabels,
  QuotePrimaryLabel,
  QuoteSecondaryLabel,
} from '@systhemaui/next'
```

In a React app without Next.js, import them from `@systhemaui/react`. There `QuoteAvatar` renders a plain `<img>`.

## Variants and tags

Each part has a default element, shorthands for the other common ones, and an `as` prop for anything else:

| Part                  | Default        | Shorthands                                                  | Class                   |
| --------------------- | -------------- | ----------------------------------------------------------- | ----------------------- |
| `Quote`               | `<blockquote>` | `Quote.blockquote`, `Quote.div`, `Quote.p`, `Quote.section` | `quote`                 |
| `QuoteIcon`           | `<span>`       | `QuoteIcon.span`, `QuoteIcon.div`, `QuoteIcon.svg`          | `quote-icon`            |
| `QuoteCite`           | `<cite>`       | `QuoteCite.cite`, `QuoteCite.div`, `QuoteCite.footer`       | `quote-cite`            |
| `QuoteAvatar`         | `<img>`        | `QuoteAvatar.img`                                           | `quote-avatar`          |
| `QuoteLabels`         | `<div>`        | `QuoteLabels.div`, `QuoteLabels.span`                       | `quote-cite-labels`     |
| `QuotePrimaryLabel`   | `<p>`          | `QuotePrimaryLabel.p`, `.span`, `.div`                      | `quote-label-primary`   |
| `QuoteSecondaryLabel` | `<p>`          | `QuoteSecondaryLabel.p`, `.span`, `.div`                    | `quote-label-secondary` |

`Quote.p` only fits a one-line quote with inline content, because a `<p>` cannot contain other block elements.

## Examples

### Text only

The smallest quote is the wrapper and a paragraph. The border, padding and background come from the `quote` tokens.

```tsx preview title="Text only"
import { Paragraph, Quote } from '@systhemaui/next'

export default function Demo() {
  return (
    <Quote className="max-w-xl">
      <Paragraph>
        Good design is as little design as possible. Less, but better, because it concentrates on
        the essential aspects.
      </Paragraph>
    </Quote>
  )
}
```

### Quote mark

An empty `QuoteIcon` draws the default quote mark, sized by `--quote-icon-size` and colored by the quote icon token. Put your own glyph inside it, or render it as the SVG itself with `QuoteIcon.svg`.

```tsx preview title="Default and custom quote marks"
import { Paragraph, Quote, QuoteIcon } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="grid w-full gap-6 sm:grid-cols-2">
      <Quote>
        <QuoteIcon />
        <Paragraph>The default mark, drawn from the configured icon.</Paragraph>
      </Quote>
      <Quote>
        <QuoteIcon.svg viewBox="0 -960 960 960" fill="currentColor" aria-hidden="true">
          <path d="m233-120 65-281L80-590l288-25 112-265 112 265 288 25-218 189 65 281-247-149-247 149Z" />
        </QuoteIcon.svg>
        <Paragraph>A custom SVG, sized and colored the same way.</Paragraph>
      </Quote>
    </div>
  )
}
```

Change the default mark for the whole project with `blocks.quote` (see [Component CSS blocks](https://docs.systhema.app/sk/next/styling/blocks.md)).

### Citation without an avatar

Leave out `QuoteAvatar` and the labels take the full width. Use one label when a name is enough.

```tsx preview title="Labels only"
import {
  Paragraph,
  Quote,
  QuoteCite,
  QuoteLabels,
  QuotePrimaryLabel,
  QuoteSecondaryLabel,
} from '@systhemaui/next'

export default function Demo() {
  return (
    <Quote className="max-w-xl">
      <Paragraph>The migration took an afternoon, and nobody noticed the switch.</Paragraph>
      <QuoteCite.footer>
        <QuoteLabels>
          <QuotePrimaryLabel>Jonas Berg</QuotePrimaryLabel>
          <QuoteSecondaryLabel>Lead developer</QuoteSecondaryLabel>
        </QuoteLabels>
      </QuoteCite.footer>
    </Quote>
  )
}
```

### Testimonials side by side

Quotes in a grid stretch to the same height, which makes a tidy testimonial row.

```tsx preview title="Testimonial grid"
import {
  Paragraph,
  Quote,
  QuoteAvatar,
  QuoteCite,
  QuoteLabels,
  QuotePrimaryLabel,
  QuoteSecondaryLabel,
} from '@systhemaui/next'

const testimonials = [
  {
    text: 'Our editors build landing pages from blocks without asking for help.',
    name: 'Maya Lindqvist',
    role: 'Head of Design',
    avatar: '/demo-assets/avatar-1.webp',
  },
  {
    text: 'Dark mode was one token file away. We shipped it in a day.',
    name: 'Daniel Okafor',
    role: 'Frontend lead',
    avatar: '/demo-assets/avatar-2.webp',
  },
]

export default function Demo() {
  return (
    <div className="grid w-full gap-6 sm:grid-cols-2">
      {testimonials.map((item) => (
        <Quote.div key={item.name}>
          <Paragraph>{item.text}</Paragraph>
          <QuoteCite.div>
            <QuoteAvatar src={item.avatar} alt="" />
            <QuoteLabels>
              <QuotePrimaryLabel>{item.name}</QuotePrimaryLabel>
              <QuoteSecondaryLabel>{item.role}</QuoteSecondaryLabel>
            </QuoteLabels>
          </QuoteCite.div>
        </Quote.div>
      ))}
    </div>
  )
}
```

## Props

### `Quote`

`Quote` takes the props of the element it renders. `disableAnimation` drops the configured `animationClasses`; the quote then also stops suppressing the reveals of its children.

<!-- generated:props @systhemaui/react QuoteProps -->

| Prop                               | Type          | Default        | Description |
| ---------------------------------- | ------------- | -------------- | ----------- |
| `as`                               | `ElementType` | `'blockquote'` |             |
| `className`                        | `string`      | -              |             |
| `disableAnimation`                 | `boolean`     | -              |             |
| …and all `<blockquote>` attributes |               |                |             |

<!-- /generated -->

### Quote parts

`QuoteAvatar` defaults `alt` to `''`, `width` and `height` to `64`, and in React `loading` to `'lazy'` and `decoding` to `'async'`. The other parts take the props of the element they render.

<!-- generated:props @systhemaui/react QuoteIconProps -->

| Prop                         | Type          | Default  | Description |
| ---------------------------- | ------------- | -------- | ----------- |
| `as`                         | `ElementType` | `'span'` |             |
| `className`                  | `string`      | -        |             |
| …and all `<span>` attributes |               |          |             |

<!-- /generated -->

<!-- generated:props @systhemaui/react QuoteCiteProps -->

| Prop                         | Type          | Default  | Description |
| ---------------------------- | ------------- | -------- | ----------- |
| `as`                         | `ElementType` | `'cite'` |             |
| `className`                  | `string`      | -        |             |
| …and all `<cite>` attributes |               |          |             |

<!-- /generated -->

<!-- generated:props @systhemaui/react QuoteAvatarProps -->

| Prop                        | Type     | Default | Description |
| --------------------------- | -------- | ------- | ----------- |
| `alt`                       | `string` | -       |             |
| `className`                 | `string` | -       |             |
| …and all `<img>` attributes |          |         |             |

<!-- /generated -->

<!-- generated:props @systhemaui/react QuoteLabelsProps -->

| Prop                        | Type          | Default | Description |
| --------------------------- | ------------- | ------- | ----------- |
| `as`                        | `ElementType` | `'div'` |             |
| `className`                 | `string`      | -       |             |
| …and all `<div>` attributes |               |         |             |

<!-- /generated -->

<!-- generated:props @systhemaui/react QuotePrimaryLabelProps -->

| Prop                      | Type          | Default | Description |
| ------------------------- | ------------- | ------- | ----------- |
| `as`                      | `ElementType` | `'p'`   |             |
| `className`               | `string`      | -       |             |
| …and all `<p>` attributes |               |         |             |

<!-- /generated -->

<!-- generated:props @systhemaui/react QuoteSecondaryLabelProps -->

| Prop                      | Type          | Default | Description |
| ------------------------- | ------------- | ------- | ----------- |
| `as`                      | `ElementType` | `'p'`   |             |
| `className`               | `string`      | -       |             |
| …and all `<p>` attributes |               |         |             |

<!-- /generated -->

## HTML and CSS

The same markup with classes only. An empty `.quote-icon` draws the default mark; the labels use `text-body` plus their own color class:

```html
<blockquote class="quote">
  <span class="quote-icon"></span>
  <p class="text-lead color-body">We replaced three style guides with one token file.</p>
  <cite class="quote-cite">
    <img class="quote-avatar" src="/avatar.webp" alt="" width="64" height="64" loading="lazy" />
    <div class="quote-cite-labels">
      <p class="text-body quote-label-primary">Maya Lindqvist</p>
      <p class="text-body quote-label-secondary">Head of Design, Northwind</p>
    </div>
  </cite>
</blockquote>
```

Every size, gap and color is a `quote` token: `--quote-border-width`, `--quote-padding-x`, `--quote-icon-size`, `--quote-avatar-size`, `--color-quote-background` and so on.

<!-- generated:utilities quote -->

| Class                   | Styles                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quote`                 | `& { border-left-style: solid; border-left-width: var(--quote-border-width); padding-left: var(--quote-padding-x); padding-block: var(--quote-padding-y); background-color: var(--color-quote-background); border-color: var(--color-quote-border); }`<br>`&  > * { margin-block: var(--quote-gap-y); color: var(--color-quote-content); }`<br>`.quote  > * { &:first-child { margin-top: 0; } }`<br>`.quote  > * { &:last-child { margin-bottom: 0; } }`                                                   |
| `quote-icon`            | `& { object-position: center; display: block; overflow: visible; object-fit: contain; width: var(--quote-icon-size); height: var(--quote-icon-size); font-size: var(--quote-icon-size); line-height: var(--quote-icon-size); color: var(--color-quote-icon); }`<br>`div.quote-icon:empty { mask-image: url("data:image/svg+xml,…"); mask-repeat: no-repeat; mask-position: center; mask-size: var(--quote-icon-size) var(--quote-icon-size); background-color: var(--color-quote-icon); }`<br>+ 1 more rule |
| `quote-avatar`          | `object-position: center;`<br>`flex-grow: 0;`<br>`flex-shrink: 0;`<br>`overflow: hidden;`<br>`object-fit: cover;`<br>`width: var(--quote-avatar-size);`<br>`height: var(--quote-avatar-size);`<br>`border-radius: var(--quote-avatar-border-radius);`                                                                                                                                                                                                                                                       |
| `quote-cite`            | `& { display: flex; justify-content: flex-start; align-items: center; column-gap: var(--quote-cite-gap-y); }`<br>`& * { flex-shrink: 0; }`                                                                                                                                                                                                                                                                                                                                                                  |
| `quote-cite-labels`     | `& { flex-grow: 1; }`<br>`& * { margin-block: var(--quote-labels-gap-y); }`<br>`.quote-cite-labels * { &:first-child { margin-top: 0; } }`<br>`.quote-cite-labels * { &:last-child { margin-bottom: 0; } }`                                                                                                                                                                                                                                                                                                 |
| `quote-label-primary`   | `color: var(--color-quote-primary-label);`                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `quote-label-secondary` | `color: var(--color-quote-secondary-label);`                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

<!-- /generated -->

## Next.js

The `@systhemaui/next` `QuoteAvatar` renders `next/image`, so the avatar is resized and served in a modern format. It defaults `width` and `height` to `64`; pass the size you display it at. The other parts render the same markup as in React.

<!-- generated:props @systhemaui/next QuoteAvatarProps -->

| Prop                               | Type     | Default | Description |
| ---------------------------------- | -------- | ------- | ----------- |
| `alt`                              | `string` | -       |             |
| …and props from `next`             |          |         |             |
| …and all inherited HTML attributes |          |         |             |

<!-- /generated -->

## Accessibility

- The default quote mark is a CSS mask on an empty element, so there is nothing to announce. Give a custom `QuoteIcon` child `aria-hidden="true"`.
- Leave the avatar's `alt` empty when the name is in the labels next to it, so the name is not read twice.
- HTML reserves `<cite>` for the title of a work. To attribute a person, `QuoteCite.footer` or `QuoteCite.div` is the more accurate element.

## Related

- [Quote block](https://docs.systhema.app/sk/next/payload/blocks/quote.md)
- [Avatar](https://docs.systhema.app/sk/next/components/avatar.md)
- [Paragraph](https://docs.systhema.app/sk/next/components/paragraph.md)
