Qure UI
Components

Card

A surface that groups related content and its actions.

Chest X-ray
Ramesh Kulkarni · 08:14
qXR flagged a possible pulmonary nodule in the right upper lobe.
import { Button } from '@/registry/qure/ui/button'import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/registry/qure/ui/card'export default function CardDemo() {  return (    <Card className="w-full max-w-sm">      <CardHeader>        <CardTitle>Chest X-ray</CardTitle>        <CardDescription>Ramesh Kulkarni · 08:14</CardDescription>      </CardHeader>      <CardContent className="text-sm text-muted-foreground">        qXR flagged a possible pulmonary nodule in the right upper lobe.      </CardContent>      <CardFooter>        <Button size="sm">Open study</Button>        <Button size="sm" variant="tertiary">Dismiss</Button>      </CardFooter>    </Card>  )}

Installation

npx shadcn@latest add https://qure-ui.qure.ai/r/card.json

Usage

import {
  Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
} from '@/components/ui/card'
<Card>
  <CardHeader>
    <CardTitle>Chest X-ray</CardTitle>
    <CardDescription>Ramesh Kulkarni · 08:14</CardDescription>
  </CardHeader>
  <CardContent>…</CardContent>
  <CardFooter><Button size="sm">Open study</Button></CardFooter>
</Card>

A card is worth reaching for when the things inside it belong to one subject — one study, one patient, one job. It is the wrong choice for a list of peers: four cards in a column are four borders and four shadows saying nothing, where rows and a Separator say the same thing quietly.

Composition

<Card>                  {/* the surface: border, radius, 20px vertical padding */}
  <CardHeader>          {/* title and description, padded 20px inline */}
    <CardTitle>         {/* 16/24 semibold — a div, so you choose the heading level */}
    <CardDescription>   {/* 14/20 secondary text, sitting 2px under the title */}
  </CardHeader>
  <CardContent>         {/* the body, sharing the header's inline padding */}
  <CardFooter>          {/* a flex row with an 8px gap, for actions */}
</Card>

Every part is optional and every one is a plain div — a card is layout, and putting a runtime behind it would buy nothing. The inline padding lives on the three inner parts rather than on the root, so a full-bleed child (a table, an image, an Accordion whose dividers should reach the edges) can opt out simply by not using them.

CardTitle renders a div on purpose. Cards appear at every depth of a page, and hardcoding h3 would produce documents whose heading outline is decided by a component. Where the outline matters, put a real heading inside it — <CardTitle><h3>ACC-482913</h3></CardTitle> — or give the title role="heading" aria-level={3}.

Study summary

Chest X-ray
Ramesh Kulkarni · 08:14
qXR flagged a possible pulmonary nodule in the right upper lobe.
import { Button } from '@/registry/qure/ui/button'import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/registry/qure/ui/card'export default function CardDemo() {  return (    <Card className="w-full max-w-sm">      <CardHeader>        <CardTitle>Chest X-ray</CardTitle>        <CardDescription>Ramesh Kulkarni · 08:14</CardDescription>      </CardHeader>      <CardContent className="text-sm text-muted-foreground">        qXR flagged a possible pulmonary nodule in the right upper lobe.      </CardContent>      <CardFooter>        <Button size="sm">Open study</Button>        <Button size="sm" variant="tertiary">Dismiss</Button>      </CardFooter>    </Card>  )}

The common shape: what the study is, whose it is, one line of context, and the two actions someone would take next. Keep the footer to the actions that belong to this card — a card with five buttons is a form that has lost its heading.

With a status and an inline action

ACC-482913
CT Chest · Ramesh Kulkarni · 62M
Critical
Unreported for 4h 12m
import { Badge, BadgeDot } from '@/registry/qure/ui/badge'import { Button } from '@/registry/qure/ui/button'import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/qure/ui/card'export default function CardAction() {  return (    <Card className="w-full max-w-sm">      <CardHeader className="flex items-start justify-between gap-4">        <div>          <CardTitle>ACC-482913</CardTitle>          <CardDescription>CT Chest · Ramesh Kulkarni · 62M</CardDescription>        </div>        <Badge tone="urgent"><BadgeDot /> Critical</Badge>      </CardHeader>      <CardContent className="flex items-center justify-between gap-4 text-sm text-muted-foreground">        Unreported for 4h 12m        <Button size="xs" variant="tertiary">Assign</Button>      </CardContent>    </Card>  )}

The header is a flex row when something has to sit opposite the title. A Badge is the usual occupant: status is an attribute of the subject, so it belongs beside the title rather than buried in the body.

As a metric tile

Unreported
38
6 past SLA
Reported today
214
+12 on yesterday
Median turnaround
31m
Target 45m
import { Card, CardDescription, CardHeader, CardTitle } from '@/registry/qure/ui/card'const metrics = [  { label: 'Unreported', value: '38', note: '6 past SLA' },  { label: 'Reported today', value: '214', note: '+12 on yesterday' },  { label: 'Median turnaround', value: '31m', note: 'Target 45m' },]export default function CardMetric() {  return (    <div className="grid w-full max-w-3xl gap-4 sm:grid-cols-3">      {metrics.map(metric => (        <Card key={metric.label}>          <CardHeader>            <CardDescription>{metric.label}</CardDescription>            <CardTitle className="text-2xl">{metric.value}</CardTitle>            <CardDescription>{metric.note}</CardDescription>          </CardHeader>        </Card>      ))}    </div>  )}

Header-only cards, with the description above the number reading as its label. Reach for this on a department dashboard, where the border is doing real work separating one figure from the next.

Making a card clickable

Do not put onClick on the Card. A div that responds to clicks is invisible to a keyboard and to a screen reader. Put a real link or button inside and let it fill the card:

<Card className="relative">
  <CardHeader>
    <CardTitle>
      <a href="/studies/1" className="after:absolute after:inset-0">Chest X-ray</a>
    </CardTitle>
  </CardHeader>
</Card>

The pseudo-element covers the card, so the whole surface is clickable while the accessible name, the focus ring and the status-bar URL all come from the real link.

That trick breaks text selection inside the card, and a second link in the card will sit under the overlay unless you give it relative z-10. If the card holds two destinations, it wants two visible links rather than one invisible one.

API Reference

No primitive and no added props. Each part accepts everything a div does, and each carries a data-slot for styling from a parent.

PartElementPurpose
CarddivThe surface. A flex column with a 16px gap.
CardHeaderdivTitle and description.
CardTitlediv16/24 semibold. Not a heading element — choose your own level.
CardDescriptiondiv14/20 secondary text.
CardContentdivThe body.
CardFooterdivA flex row, 8px gap, for actions.

On this page