Qure UI
Components

Badge

Labels a value with a clinical status, using colour and a label together — never colour alone.

Draft In review Reviewed Pending Critical
import { Badge, BadgeDot } from '@/registry/qure/ui/badge'export default function BadgeTone() {  return (    <>      <Badge tone="neutral"><BadgeDot /> Draft</Badge>      <Badge tone="brand"><BadgeDot /> In review</Badge>      <Badge tone="success"><BadgeDot /> Reviewed</Badge>      <Badge tone="attention"><BadgeDot /> Pending</Badge>      <Badge tone="urgent"><BadgeDot /> Critical</Badge>    </>  )}

Installation

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

Usage

import { Badge, BadgeDot } from '@/components/ui/badge'
<Badge tone="urgent"><BadgeDot /> Critical</Badge>

A badge describes the thing next to it. It is not a control: it has no focus ring, no pressed state and no hit target worth the name, so a badge that filters a worklist when clicked is a button wearing the wrong clothes. If it does something, make it a Button; if it merely says something, this is the component.

Tone

Draft In review Reviewed Pending Critical
import { Badge, BadgeDot } from '@/registry/qure/ui/badge'export default function BadgeTone() {  return (    <>      <Badge tone="neutral"><BadgeDot /> Draft</Badge>      <Badge tone="brand"><BadgeDot /> In review</Badge>      <Badge tone="success"><BadgeDot /> Reviewed</Badge>      <Badge tone="attention"><BadgeDot /> Pending</Badge>      <Badge tone="urgent"><BadgeDot /> Critical</Badge>    </>  )}

tone is the clinical axis and the only one that carries meaning. Everything else is presentation.

ToneMeans
neutralNo judgement — a category, a filter, a label
brandIn progress, assigned, ours
successDone, cleared, within range
attentionNeeds someone, but not now
urgentNeeds someone now

Pick the tone from what the reader should do about it, not from how the word feels. "Rejected" sounds bad and is often neutral: the study has left the queue and needs nobody.

Variant

neutralbrandsuccessattentionurgent
neutralbrandsuccessattentionurgent
neutralbrandsuccessattentionurgent
import { Badge } from '@/registry/qure/ui/badge'const tones = ['neutral', 'brand', 'success', 'attention', 'urgent'] as constconst variants = ['soft', 'solid', 'outline'] as constexport default function BadgeVariant() {  return (    <div className="grid gap-3">      {variants.map(variant => (        <div key={variant} className="flex flex-wrap items-center gap-2">          {tones.map(tone => (            <Badge key={tone} tone={tone} variant={variant}>{tone}</Badge>          ))}        </div>      ))}    </div>  )}

Fill weight, holding the tone's colour. Soft is the default, matching Figma — a column of solid urgent tags turns a worklist red and stops any single one of them being urgent. Keep solid for the one row in fifty that has to break out of the page, and use outline where badges sit on an already-tinted surface and a soft fill would disappear into it.

Size

Critical Critical
import { Badge, BadgeDot } from '@/registry/qure/ui/badge'export default function BadgeSize() {  return (    <div className="flex items-center gap-3">      <Badge size="sm" tone="urgent"><BadgeDot /> Critical</Badge>      <Badge size="md" tone="urgent"><BadgeDot /> Critical</Badge>    </div>  )}

24px and 20px. md is the default; drop to sm inside dense table rows, where a 24px chip forces the row height up for the sake of one word.

With an icon

Signed Past SLA Critical
import { CircleCheckIcon, ClockIcon, TriangleAlertIcon } from 'lucide-react'import { Badge } from '@/registry/qure/ui/badge'export default function BadgeIcon() {  return (    <div className="flex flex-wrap items-center gap-2">      <Badge tone="success"><CircleCheckIcon /> Signed</Badge>      <Badge tone="attention"><ClockIcon /> Past SLA</Badge>      <Badge tone="urgent" variant="solid"><TriangleAlertIcon /> Critical</Badge>    </div>  )}

BadgeDot is composed rather than injected, so a badge can carry an icon instead. An icon is worth the space when it distinguishes two statuses of the same tone — a clock for "past SLA" against a triangle for "critical" — and is noise when it just restates the word.

In a worklist

AccessionStudyStatus
ACC-482913CT Chest Critical
ACC-482910Chest X-ray Past SLA
ACC-482904CT Head In review
ACC-482891Chest X-ray Signed
import { Badge, BadgeDot } from '@/registry/qure/ui/badge'const rows = [  { accession: 'ACC-482913', study: 'CT Chest', tone: 'urgent', status: 'Critical' },  { accession: 'ACC-482910', study: 'Chest X-ray', tone: 'attention', status: 'Past SLA' },  { accession: 'ACC-482904', study: 'CT Head', tone: 'brand', status: 'In review' },  { accession: 'ACC-482891', study: 'Chest X-ray', tone: 'success', status: 'Signed' },] as constexport default function BadgeTable() {  return (    <table className="w-full max-w-md text-left text-sm">      <thead className="text-muted-foreground">        <tr>          <th scope="col" className="pb-2 font-medium">Accession</th>          <th scope="col" className="pb-2 font-medium">Study</th>          <th scope="col" className="pb-2 font-medium">Status</th>        </tr>      </thead>      <tbody>        {rows.map(row => (          <tr key={row.accession} className="border-t">            <td className="py-2 tabular-nums">{row.accession}</td>            <td className="py-2">{row.study}</td>            <td className="py-2">              <Badge size="sm" tone={row.tone}><BadgeDot /> {row.status}</Badge>            </td>          </tr>        ))}      </tbody>    </table>  )}

The place badges actually live. One badge per row, in its own column, at sm, all of them the same variant so the tone is the only thing varying — that is what makes the red row findable when there are forty of them.

Never colour alone

Roughly 1 in 12 men has a red-green deficiency, and a reporting monitor is calibrated for images rather than for UI. Every badge should survive its colour being removed, which is what the label and the dot are for.

A dot with no text is not a badge, it is a decoration. If the status can only be read from the colour of a circle, nobody in greyscale and nobody using a screen reader can read it at all.

API Reference

No primitive behind it — a span with everything a span accepts, plus:

Prop

Type

BadgeDot takes no props of its own. It is aria-hidden, 6px, and coloured by currentColor, so it follows whichever tone the badge is wearing.

On this page