Skip to content
txbrendan/ui

Job Card

beta

One job on one line, like an import, a send, a sync, or an export: its status, what it is, a few words about it, and how long it took. A thin bar fills while it runs. It opens to the details.

Preview

live

Import contacts: Queued

queued

Export report: Queued

running

Resize photos: Running

done

Request

{
  "to": "ana@example.com",
  "template": "welcome",
  "locale": "en"
}

Response

{
  "id": "msg_812",
  "status": 202
}
Send welcome email: Done

failed

The calendar's access ran out. Connect it again.

Sync calendar: Failed

canceled

Rebuild search index: Canceled

list

Back up database: Done
Charge renewals: Done
Send invoices: Running
Archive old orders: Queued

title only

Clear cache: Done

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/job-card

Use it for

  • In an activity log or a jobs page, one card per job: what ran, on what, and what came of it.
  • Pass status queued before it starts, running while it runs, then done, failed, or canceled.
  • Pass progress from 0 to 1 while it runs, like 0.45 for 45%. A thin bar fills along the bottom edge, and the line shows the percent.
  • Keep summary to a few words, like the file name or how many rows. The full data goes in details.
  • Pass details as labeled sections, like Request and Response. An object or array shows as JSON. Pass a node to show it your own way.
  • On failed, pass error in plain words, like "Row 214 has no email." It shows under the line, so no one has to open the card to see it.
  • Stack several in a flex column with a small gap for a list of jobs.

Avoid it for

  • A job that needs a person to say yes before it runs. Ask first, then show the job.
  • Many jobs grouped by who has to act next. Use the Task Inbox block.
  • A status on a row or in a table. Use StatusText.
  • A single upload with nothing to open. Use Progress.
  • An agent's tool call. Use ToolCallCard.

Props

  • title

    Required. What the job is, like "Import contacts".

    ReactNode

  • status

    Required. Where the job stands. queued is a dashed ring, running spins in the brand tone, done is a success check, failed is danger red, and canceled is quiet.

    "queued" | "running" | "done" | "failed" | "canceled"

  • summary

    The job in a few words, like "contacts.csv, 2,400 rows". Shown on the closed line, cut short with an ellipsis.

    ReactNode

  • progress

    running: how far along it is, from 0 to 1. A thin bar fills along the bottom edge, and the line shows the percent.

    number

  • details

    What the open card shows, in order, like Request and Response. An object or array shows as JSON. With none, it says it is waiting, or that there are no details.

    { label: string; value: ReactNode | Record<string, unknown> | unknown[] }[] · default []

  • error

    failed: what went wrong, in plain words. It shows under the line, open or closed.

    ReactNode

  • duration

    How long it took, has run so far, or when it ran, like "1.5s" or "2m ago". Update it while running for a live timer.

    string

  • defaultOpen

    Starts open.

    boolean · default false

  • open

    Controls whether it is open. Pair it with onOpenChange.

    boolean

  • onOpenChange

    Called when the card opens or closes.

    (open: boolean) => void

  • disabled

    The line can't be opened.

    boolean · default false

  • className

    Extra classes on the card. Later classes win.

    string

Accessibility

  • The line is one button. It says the status in words, the title, the summary, the percent, and the time, and it tells screen readers if it is open.
  • Enter or Space opens and closes it.
  • Status is shown three ways: words for screen readers, an icon shape, and a color.
  • While running with progress, a progress bar named by the title sits outside the button.
  • A change of status is said once, politely, in a hidden status region: Import contacts: Done.
  • The error is plain text under the line, so it is read without opening the card.
  • The spinner stops and the bar jumps to its value when the theme turns motion off, and for reduced motion. The panel then opens at once.

Examples

Running, with progress

<JobCard title="Import contacts" status="running" summary="contacts.csv, 2,400 rows" progress={0.45} duration="12s" />

Done, with details

<JobCard
  title="Send welcome email"
  status="done"
  summary="ana@example.com"
  duration="0.4s"
  details={[
    { label: "Request", value: { to: "ana@example.com", template: "welcome" } },
    { label: "Response", value: { id: "msg_812", status: 202 } },
  ]}
/>

Failed

<JobCard title="Sync calendar" status="failed" summary="Team events" duration="4.2s" error="The calendar's access ran out. Connect it again." />

Queued

<JobCard title="Export report" status="queued" summary="Q3 sales, PDF" />