Skip to content
weftGitHub

Card

A box that groups related things, and can be a link or a button.

SourcePackage @weft-js/warpPlayground

Preview

tsx
import { Card, Text } from '@weft-js/warp'export function Static() {  return (    <Card eyebrow="Project" title="storefront">      <Text subtle>Live in eu-west, updated 2 minutes ago.</Text>    </Card>  )}

Installation

npm install @weft-js/warp

Usage

tsx
import { Card } from '@weft-js/warp'<Card eyebrow="Project" title="storefront">  <Text subtle>Live in eu-west, updated 2 minutes ago.</Text></Card>

A button

onClick makes the whole card a button.

tsx
import { state } from '@weft-js/core'import { Card, Grid, Text } from '@weft-js/warp'export function Press() {  const picked = state('Team')  return (    <Grid cols={2} gap="related">      {PLANS.map(plan => (        <Card key={plan} eyebrow={picked.value === plan ? 'Selected' : 'Plan'} title={plan} aria-pressed={picked.value === plan ? 'true' : 'false'} onClick={() => (picked.value = plan)}>          <Text subtle>{plan === 'Team' ? '$40 a month' : '$120 a month'}</Text>        </Card>      ))}    </Grid>  )}const PLANS = ['Team', 'Scale']

Bare

Every part is optional, and sweep={false} drops the hover line.

tsx
import { Card, Text } from '@weft-js/warp'export function Bare() {  return (    <Card sweep={false}>      <Text>No eyebrow, no title and no sweep. Just a surface for what you put in it.</Text>    </Card>  )}

A row of figures

Use eyebrow and title for a label and a figure, laid out in a Grid.

tsx
import { Card, Grid, Text } from '@weft-js/warp'export function Stats() {  return (    <Grid min="xs" gap="related">      <Card eyebrow="Requests" title="86.1M"><Text role="caption" tone="positive">Up 8.6% on September</Text></Card>      <Card eyebrow="Errors" title="0.22%"><Text role="caption" tone="positive">Down from 0.36%</Text></Card>      <Card eyebrow="Deploys" title="167"><Text role="caption" subtle>About 5 a day</Text></Card>      <Card eyebrow="Spend" title="$8,930"><Text role="caption" subtle>Up $370</Text></Card>    </Grid>  )}const min = 60_000

A grid of projects

Give a card href to make the whole card a link.

tsx
import { Badge, Card, Grid, Row, Text } from '@weft-js/warp'export function ProjectGrid() {  return (    <Grid min="md" gap="related">      {PROJECTS.slice(0, 6).map(p => (        <Card key={p.id} href="/components/card" eyebrow={p.region} title={p.name}>          <Row justify="between">            <Text role="caption" subtle>{p.deploys} deploys</Text>            <Badge tone={TONE_OF[p.status]} dot>{p.status}</Badge>          </Row>        </Card>      ))}    </Grid>  )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [  { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' },  { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' },  { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' },  { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' },  { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' },  { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' },  { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' },  { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' },  { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' },  { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]const TONE_OF: Record<Status, 'positive' | 'info' | 'critical' | 'neutral'> = { Live: 'positive', Building: 'info', Failed: 'critical', Paused: 'neutral' }const min = 60_000

When to use

Use it for

  • Grouping one thing's details
  • A grid of things to pick from

Reach for something else

  • Table, when people compare many rows by the same columns
  • Inset, when you only want padding, with no surface

Playground

import { Card } from '@weft-js/warp'<Card eyebrow="Project" title="storefront">  <Text subtle>Live in eu-west, updated 2 minutes ago.</Text></Card>

Accessibility

  • A card with href is a real link, and with onClick a real button, so don't put other buttons or links inside those.

API Reference

PropTypeDefaultDescription
eyebrowunknown—A small line above the title.
titleunknown—The card's title.
hrefstring—Makes the card a link.
sweepbooleantrueThe line that runs on hover.
childrenunknown—What it contains.
EventTypeDefaultDescription
onClick((e: MouseEvent) => void)—Makes the card a button.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="surface"] [data-part="eyebrow"]

eyebrowtitlesweep

RelatedGridBadgeInset