@loke/design-system
Version:
A design system with individually importable components
303 lines (236 loc) • 10.1 kB
Markdown
---
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: '/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