---
name: display-components
description: >
  Use static/presentational design system components. Card (CardHeader,
  CardContent, CardFooter, CardTitle, CardDescription), Text (responsive
  size/weight/color/decoration, truncate, clamped line-clamp), Heading
  (semantic h1-h6 + display/title/subtitle/section variants, decoupled
  as and variant props), Badge (default/secondary/destructive/outline),
  Avatar (AvatarImage, AvatarFallback, AvatarBadge, AvatarGroup),
  Separator, Spinner (sm/default/lg/xl), Skeleton. Activate when rendering
  display content, typography, or status indicators.
type: core
library: '@loke/design-system'
library_version: '2.0.0-rc.6'
requires:
  - getting-started
sources:
  - 'LOKE/merchant-frontends:packages/design-system/src/components/card'
  - 'LOKE/merchant-frontends:packages/design-system/src/components/text'
  - 'LOKE/merchant-frontends:packages/design-system/src/components/heading'
  - 'LOKE/merchant-frontends:packages/design-system/src/components/badge'
  - 'LOKE/merchant-frontends:packages/design-system/src/components/avatar'
  - 'LOKE/merchant-frontends:packages/design-system/src/components/separator'
  - 'LOKE/merchant-frontends:packages/design-system/src/components/spinner'
  - 'LOKE/merchant-frontends:packages/design-system/src/components/skeleton'
---

# Display Components

This skill builds on **getting-started**. Read it first for setup and imports.

## Setup

Each component is imported from its own subpath:

```tsx
import { Card, CardHeader, CardContent, CardFooter, CardTitle, CardDescription } from "@loke/design-system/card";
import { Text } from "@loke/design-system/text";
import { Heading } from "@loke/design-system/heading";
import { Badge } from "@loke/design-system/badge";
import { Avatar, AvatarImage, AvatarFallback, AvatarBadge, AvatarGroup, AvatarGroupCount } from "@loke/design-system/avatar";
import { Separator } from "@loke/design-system/separator";
import { Spinner } from "@loke/design-system/spinner";
import { Skeleton } from "@loke/design-system/skeleton";
```

Combined usage example:

```tsx
<Card>
  <CardHeader>
    <CardTitle>Monthly Revenue</CardTitle>
    <CardDescription>Compared to last month</CardDescription>
  </CardHeader>
  <CardContent>
    <Heading as="h3" variant="display">$12,400</Heading>
    <Text size="sm" color="muted">Up 8% from October</Text>
  </CardContent>
  <CardFooter>
    <Badge variant="secondary">Verified</Badge>
  </CardFooter>
</Card>
```

## Core Patterns

### Card composition

`Card` is a layout shell. Use `CardHeader` + `CardTitle` + `CardDescription` for the top section, `CardContent` for the body, and `CardFooter` for actions or summary info. Both `CardHeader` and `CardFooter` accept a `centered` prop.

```tsx
<Card>
  <CardHeader centered>
    <CardTitle>Order #4821</CardTitle>
    <CardDescription>Placed on 1 April 2026</CardDescription>
  </CardHeader>
  <CardContent>
    <Text>2 items · $48.00</Text>
  </CardContent>
  <CardFooter centered>
    <Badge>Fulfilled</Badge>
  </CardFooter>
</Card>
```

### Typography with Text and Heading

`Text` renders a `<span>` with a full variant system. All props are responsive (pass a plain value or `{ base, sm, md, lg }` breakpoint object).

```tsx
// Size, weight, color
<Text size="sm" weight="semibold" color="muted">Invoice #1042</Text>

// Text transform and alignment
<Text transform="uppercase" size="xs" weight="medium" color="primary">
  Category
</Text>

// Truncate a single line
<Text truncate>A very long product name that should be cut off with an ellipsis</Text>

// Clamp to N lines
<Text clamped={3}>
  A long description that should be clamped to exactly three lines before
  being hidden, regardless of how much content follows…
</Text>

// Responsive size
<Text size={{ base: "sm", md: "base", lg: "lg" }}>Responsive body copy</Text>
```

`Text` prop reference:
- `size`: `xs` | `sm` | `base` | `lg` | `xl` | `2xl` | `3xl` | `4xl`
- `weight`: `light` | `normal` | `medium` | `semibold` | `bold`
- `color`: `foreground` | `muted` | `primary` | `secondary` | `accent` | `destructive` | `card` | `popover` | `white`
- `align`: `left` | `center` | `right` | `justify`
- `decoration`: `none` | `underline` | `line-through` | `italic`
- `lineHeight`: `none` | `tight` | `snug` | `normal` | `relaxed` | `loose`
- `transform`: `normal` | `uppercase` | `lowercase` | `capitalize`
- `whiteSpace`: `normal` | `nowrap` | `pre` | `pre-line` | `pre-wrap`
- `wordBreak`: `normal` | `words` | `all` | `keep`
- `truncate`: `boolean` — single-line ellipsis
- `clamped`: `number` — multi-line clamp via `-webkit-line-clamp`

`Heading` renders a semantic heading element with visual styling decoupled from the HTML tag. Use `as` to control the DOM element and `variant` to control appearance independently.

```tsx
// Defaults: as="h2", variant matches the as tag
<Heading>Section Title</Heading>

// Decouple visual appearance from semantic level
<Heading as="h3" variant="display">Hero-sized text in an h3</Heading>
<Heading as="h1" variant="subtitle">Large subtitle styling on the page h1</Heading>

// Neutral color override
<Heading as="h2" variant="h2" color="n-500">Muted heading</Heading>
```

`Heading` variant reference:
- `h1` – `text-4xl font-extrabold`
- `h2` – `text-3xl font-bold`
- `h3` – `text-2xl font-semibold`
- `h4` – `text-xl font-semibold`
- `h5` – `text-lg font-medium`
- `h6` – `text-base font-medium`
- `display` – `text-5xl font-extrabold` (hero/marketing)
- `title` – `text-4xl font-bold`
- `subtitle` – `text-2xl font-medium text-foreground/80`
- `section` – `text-xl font-semibold text-primary`

`Heading` color reference: `foreground` | `accent` | `card` | `destructive` | `popover` | `primary` | `secondary` | `n-50` through `n-950` (zinc scale)

### Avatar with fallback

`Avatar` accepts a `size` prop (`sm` | `default` | `lg`). Always pair `AvatarImage` with `AvatarFallback` — fallback shows automatically when the image fails or is absent.

```tsx
// Single avatar with badge
<Avatar size="lg">
  <AvatarImage src="/users/jane.jpg" alt="Jane Smith" />
  <AvatarFallback>JS</AvatarFallback>
  <AvatarBadge />
</Avatar>

// Group of avatars with overflow count
<AvatarGroup>
  <Avatar>
    <AvatarImage src="/users/alice.jpg" alt="Alice" />
    <AvatarFallback>AL</AvatarFallback>
  </Avatar>
  <Avatar>
    <AvatarImage src="/users/bob.jpg" alt="Bob" />
    <AvatarFallback>BO</AvatarFallback>
  </Avatar>
  <AvatarGroupCount>+4</AvatarGroupCount>
</AvatarGroup>
```

`AvatarBadge` renders a small dot indicator at the bottom-right of the avatar. Pass icon children to `AvatarBadge` for a status icon — the icon is automatically hidden at `sm` size.

### Status indicators

**Badge** — four variants, all `inline-flex` and pill-shaped. Pass `onRemove` to make the badge a removable button with an X icon.

```tsx
<Badge>Active</Badge>
<Badge variant="secondary">Draft</Badge>
<Badge variant="destructive">Error</Badge>
<Badge variant="outline">Beta</Badge>

// Removable badge
<Badge variant="secondary" onRemove={() => removeTag(id)}>
  electronics
</Badge>
```

**Spinner** — loading indicator using an animated `LoaderCircle` icon. Renders an `<output>` element with `aria-label="Loading"`.

```tsx
<Spinner />                          {/* md size, primary color */}
<Spinner size="sm" />
<Spinner size="lg" color="secondary" />
<Spinner size="xl" color="accent" />
```

`Spinner` size reference: `sm` (16px) | `md` (24px, default) | `lg` (32px) | `xl` (48px)
`Spinner` color reference: `primary` | `secondary` | `accent`

**Skeleton** — animated pulse placeholder. Size it with `className` to match the content it replaces.

```tsx
{/* Text line placeholders */}
<Skeleton className="h-4 w-48" />
<Skeleton className="h-4 w-32" />

{/* Card layout placeholder */}
<Card>
  <CardHeader>
    <Skeleton className="h-6 w-40" />
    <Skeleton className="h-4 w-64" />
  </CardHeader>
  <CardContent>
    <Skeleton className="h-32 w-full" />
  </CardContent>
</Card>

{/* Avatar placeholder */}
<Skeleton className="size-8 rounded-full" />
```

**Separator** — horizontal by default, pass `orientation="vertical"` for inline use.

```tsx
<Separator />
<Separator orientation="vertical" className="h-6" />
```

## Common Mistakes

### CRITICAL: Using native HTML elements instead of design system equivalents

Wrong:

```tsx
<h2 className="text-3xl font-bold">Section title</h2>
<span className="text-sm text-gray-500">Supporting text</span>
```

Right:

```tsx
<Heading as="h2">Section title</Heading>
<Text size="sm" color="muted">Supporting text</Text>
```

Native elements bypass the variant system, semantic tokens, and responsive props. This applies everywhere display content appears — cross-skill with interactive-components for the same issue in form labels and button text.

### MEDIUM: Not decoupling Heading `as` and `variant` props

Wrong:

```tsx
{/* Reaching for raw className to get a large visual on a semantic h3 */}
<h3 className="text-5xl font-extrabold">Hero Headline</h3>
```

Right:

```tsx
<Heading as="h3" variant="display">Hero Headline</Heading>
```

The `as` prop controls the DOM element (and thus document outline / accessibility). The `variant` prop controls only visual appearance. They are fully independent — any combination is valid.

### MEDIUM: Building custom truncation instead of using Text props

Wrong:

```tsx
<span className="overflow-hidden text-ellipsis whitespace-nowrap block">
  Long product name
</span>

<p className="line-clamp-3">
  Long description text that needs clamping
</p>
```

Right:

```tsx
<Text truncate>Long product name</Text>

<Text clamped={3}>Long description text that needs clamping</Text>
```

`truncate` handles single-line ellipsis. `clamped={n}` handles multi-line clamp with `-webkit-line-clamp`. Neither requires additional className overrides.

## See also

- `theming/SKILL.md` — customizing component appearance with semantic tokens
- `interactive-components/SKILL.md` — Button, Input, and other interactive elements
