Qure UI
Components

Status Bar

A persistent bar reporting the state of the thing it sits above.

The most recent prior for this patient is 14 months old. Comparison may be unreliable.

'use client'import { Button } from '@/registry/qure/ui/button'import { StatusBar, StatusBarActions, StatusBarText } from '@/registry/qure/ui/status-bar'export default function StatusBarDemo() {  return (    <StatusBar status="attention" className="max-w-xl">      <StatusBarText>        The most recent prior for this patient is 14 months old. Comparison may be unreliable.      </StatusBarText>      <StatusBarActions>        <Button variant="secondary" size="sm">Find priors</Button>      </StatusBarActions>    </StatusBar>  )}

Installation

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

Usage

import {
  StatusBar, StatusBarActions, StatusBarText,
} from '@/components/ui/status-bar'
<StatusBar status="attention">
  <StatusBarText>Past its SLA by 12 minutes.</StatusBarText>
  <StatusBarActions>
    <Button variant="secondary" size="sm">Reassign</Button>
  </StatusBarActions>
</StatusBar>

This is one of the components with no shadcn equivalent. It comes from the Qure design system — Figma's "Status-bar", four statuses by two emphases — and it exists because a clinical surface usually has one fact about itself that has to stay on screen.

Toast, Alert, or Status Bar?

The three look similar and are not interchangeable. The axis is persistence:

Lives forUse it for
Toasta few secondssomething that just happened, that nobody must act on
Alertas long as the pageone message, usually about a form the reader is filling in
Status Baras long as the statewhat is true of this study, report or workspace right now

A status bar is the only one of the three you can rely on somebody seeing, because it is still there when they look up. That is why the fact that a report is unsigned belongs here and not in a toast.

Composition

<StatusBar>          {/* the bar; owns status and emphasis, renders the icon */}
  <StatusBarText>    {/* the message; takes the remaining width */}
  <StatusBarActions> {/* at most two — a bar is not a toolbar */}
</StatusBar>

The icon is chosen by status and is the same set Toast uses, so a success reads the same whichever surface reports it. It is aria-hidden: the text already says what the icon says. Pass icon to replace it, or icon={null} for a bar without one.

Status

This study is being read by A. Mehta.

Report signed and delivered to the respiratory clinic.

Past its SLA by 12 minutes.

qER flagged an intracranial haemorrhage. Unread for 42 minutes.

'use client'import { StatusBar, StatusBarText } from '@/registry/qure/ui/status-bar'const bars = [  { status: 'default', text: 'This study is being read by A. Mehta.' },  { status: 'success', text: 'Report signed and delivered to the respiratory clinic.' },  { status: 'attention', text: 'Past its SLA by 12 minutes.' },  { status: 'urgent', text: 'qER flagged an intracranial haemorrhage. Unread for 42 minutes.' },] as constexport default function StatusBarStatus() {  return (    <div className="flex w-full max-w-xl flex-col gap-3">      {bars.map(bar => (        <StatusBar key={bar.status} status={bar.status}>          <StatusBarText>{bar.text}</StatusBarText>        </StatusBar>      ))}    </div>  )}

The four are the design system's semantic states, minus brand — a status bar reports a condition, and "brand" is not one.

Emphasis

lowhigh

default

default

success

success

attention

attention

urgent

urgent

'use client'import { StatusBar, StatusBarText } from '@/registry/qure/ui/status-bar'const statuses = ['default', 'success', 'attention', 'urgent'] as const/** * The same four statuses at both emphases. Note the Attention row: its solid * fill carries dark text, not white — amber is a light colour and white on * #ffb400 does not clear contrast. That pairing comes from the token * (`--text-attention-on-attention`), not from a judgement made here. */export default function StatusBarEmphasis() {  return (    <div className="grid w-full max-w-3xl gap-3 sm:grid-cols-2">      <span className="text-label-emphasis text-muted-foreground">low</span>      <span className="text-label-emphasis text-muted-foreground max-sm:hidden">high</span>      {statuses.map(status => (        <div key={status} className="contents">          <StatusBar status={status} emphasis="low">            <StatusBarText className="capitalize">{status}</StatusBarText>          </StatusBar>          <StatusBar status={status} emphasis="high">            <StatusBarText className="capitalize">{status}</StatusBarText>          </StatusBar>        </div>      ))}    </div>  )}

low is a tint for keeping a state in view. high is a solid fill for a state that should stop somebody. Reserve it: a page of high-emphasis bars is a page with no emphasis at all.

Look at the Attention row. Its solid fill carries dark text rather than white, because amber (#ffb400) is a light colour and white on it does not clear contrast. That comes from the token — --text-attention-on-attention is #0c1723 in both themes — not from a judgement made in this component. If you are ever tempted to put white on amber, the token is telling you not to.

Dismissing

Series uploaded — 412 slices are in the archive.

'use client'import { XIcon } from 'lucide-react'import * as React from 'react'import { Button } from '@/registry/qure/ui/button'import { StatusBar, StatusBarActions, StatusBarText } from '@/registry/qure/ui/status-bar'/** * A bar the reader can put away. Dismissal belongs to the caller, not the * component: whether a state can be dismissed depends on whether it is still * true afterwards, and only the caller knows that. */export default function StatusBarDismiss() {  const [shown, setShown] = React.useState(true)  if (!shown) {    return (      <Button variant="tertiary" size="sm" onClick={() => setShown(true)}>        Bring the bar back      </Button>    )  }  return (    <StatusBar status="success" className="max-w-xl">      <StatusBarText>Series uploaded — 412 slices are in the archive.</StatusBarText>      <StatusBarActions>        <Button variant="tertiary" size="sm">View study</Button>        <Button          variant="ghost"          size="icon-sm"          aria-label="Dismiss"          onClick={() => setShown(false)}        >          <XIcon />        </Button>      </StatusBarActions>    </StatusBar>  )}

Dismissal belongs to the caller rather than to the component. Whether a bar can be put away depends on whether the state is still true afterwards, and only the caller knows that. A signed report is worth dismissing; an unsigned one is not.

Pinned above content

Draft — this report has not been signed.

Right upper lobe nodule, 8mm, spiculated margins.

No pleural effusion.

Heart size within normal limits.

Degenerative change in the lower thoracic spine.

Right upper lobe nodule, 8mm, spiculated margins.

No pleural effusion.

Heart size within normal limits.

Degenerative change in the lower thoracic spine.

'use client'import { Button } from '@/registry/qure/ui/button'import { StatusBar, StatusBarActions, StatusBarText } from '@/registry/qure/ui/status-bar'const findings = [  'Right upper lobe nodule, 8mm, spiculated margins.',  'No pleural effusion.',  'Heart size within normal limits.',  'Degenerative change in the lower thoracic spine.',]/** * Pinned above the content it describes. A status bar is the one of the three * message components you can rely on somebody seeing, because it is still * there when they look up — which is why it, and not a toast, carries the fact * that the report they are reading is not the signed one. */export default function StatusBarSticky() {  return (    <div className="w-full max-w-xl overflow-hidden rounded-md border">      <div className="max-h-56 overflow-y-auto">        <div className="bg-background sticky top-0 p-2">          <StatusBar status="urgent" emphasis="high">            <StatusBarText>Draft — this report has not been signed.</StatusBarText>            <StatusBarActions>              <Button variant="neutral" size="sm">Sign</Button>            </StatusBarActions>          </StatusBar>        </div>        <div className="flex flex-col gap-4 p-4">          {[...findings, ...findings].map((finding, i) => (            <p key={i} className="text-body-small text-muted-foreground">{finding}</p>          ))}        </div>      </div>    </div>  )}

Where the bar earns its keep: the reader scrolls the findings and the bar stays, so the fact that this is a draft cannot be scrolled past and forgotten.

Accessibility

The bar is role="status" — a polite live region. A bar that appears, or whose text changes, is announced once the reader is between utterances rather than cutting across them. A bar already on the page at load is not announced separately, which is right: it is read in document order like any other content.

Pass role="alert" only for a bar that must interrupt. It is assertive — it talks over whatever is being read — and it is the wrong choice for anything the reader is not required to act on immediately. emphasis="high" changes how a bar looks, not how it is announced; the two are deliberately separate.

API Reference

StatusBar

PropTypeDefaultDescription
status"default" | "success" | "attention" | "urgent""default"Which state is being reported. Also picks the icon.
emphasis"low" | "high""low"low tints the bar; high fills it.
iconReactNodethe status iconReplaces the icon. null renders none.
rolestring"status"Override to "alert" only when the bar must interrupt.

StatusBarText renders a <p> and StatusBarActions a <div>; both take the usual props for those elements.

On this page