Skip to content
  1. Keyline UI
  2. Status and badge
Keyline UIComponents
  • Get started
    • Installation
    • Theming
    • Modes
    • Configuration
    • Composition
  • Actions
    • Button
    • Button group
    • Split button
    • Toggle
    • Toggle group
    • Toolbar
    • Menu
    • Menubar
    • Context menu
  • Inputs
    • Field and form
    • Checkbox
    • Checkbox group
    • Radio group
    • Switch
    • Slider
    • Segmented
    • Select
    • Native select
    • Combobox
    • Multiselect
    • Number field
    • OTP field
    • Time field
    • Date picker and calendar
    • Date range
  • Data
    • Table
    • Chart
    • Stat and meter
    • Status and badge
    • Progress
    • Skeleton
    • Empty
    • Avatar
    • Kbd
    • Marker
  • Navigation
    • Tabs
    • Breadcrumb
    • Pagination
    • Nav
    • Navigation menu
    • Command menu
  • Overlays
    • Dialog
    • Alert dialog
    • Drawer
    • Sheet
    • Popover
    • Hover card
    • Tooltip
    • Toast
    • Notice
  • Layout
    • App shell
    • Sidebar
    • Panel
    • Resizable
    • Scroll area
    • Accordion
    • Collapsible
    • Carousel
    • Inspector
    • Item
    • Direction
  • Conversation
    • Message
    • Bubble
    • Message scroller
    • Attachment
    • Questionnaire
  • Blocks
    • Shipments

Status and badge

A state in words with a glyph whose shape carries the meaning, so it reads in grayscale and in forced colours. Only attention is red. Badge is a square code label in a 1px frame, never a pill.

Loading the live example…

"use client"

import { useState } from "react"
import { Button } from "@/components/keyline/ui/button"
import { Badge, Status, type StatusKind } from "@/components/keyline/ui/status"

type Stop = { id: string; client: string; cases: number; state: "loading" | "out" | "delivered" | "held"; rush?: boolean; note?: string }

const START: Stop[] = [
  { id: "DM-4102", client: "Scranton Business Park", cases: 40, state: "out", rush: true },
  { id: "DM-4107", client: "Lackawanna County", cases: 120, state: "out" },
  { id: "DM-4111", client: "Vance Refrigeration", cases: 6, state: "held", note: "No one to sign. Call Bob Vance before 3 PM." },
  { id: "DM-4115", client: "Blue Cross of Pennsylvania", cases: 60, state: "loading" },
]

const SAY: Record<Stop["state"], [StatusKind, string]> = {
  loading: ["waiting", "Loading at the warehouse"],
  out: ["normal", "Out for delivery"],
  delivered: ["done", "Delivered"],
  held: ["attention", "Held"],
}

export function DeliveryBoard() {
  const [stops, setStops] = useState(START)
  const [last, setLast] = useState("Darryl's truck left at 9:40.")
  const advance = (s: Stop) => {
    const next = s.state === "loading" ? "out" : "delivered"
    setStops((all) => all.map((x) => (x.id === s.id ? { ...x, state: next } : x)))
    setLast(next === "out" ? `${s.id} is on the truck.` : `${s.id} signed for at ${s.client}.`)
  }
  const done = stops.filter((s) => s.state === "delivered").length

  return (
    <div className="grid gap-4">
      <ul className="m-0 grid list-none gap-0 p-0">
        {stops.map((s) => {
          const [kind, words] = SAY[s.state]
          return (
            <li key={s.id} className="grid grid-cols-[1fr_auto] items-center gap-x-4 gap-y-1 border-b border-kl-line py-3 first:pt-0 last:border-b-0">
              <div className="grid min-w-0 gap-1">
                <div className="flex flex-wrap items-center gap-2">
                  <Badge>{s.id}</Badge>
                  {s.rush ? <Badge tone="signal">RUSH</Badge> : null}
                  <span className="text-[14px] font-semibold">{s.client}</span>
                  <span className="text-[14px] text-kl-ink-3 tabular-nums">{s.cases} cases</span>
                </div>
                <Status kind={kind}>{s.note ? `${words}: ${s.note}` : words}</Status>
              </div>
              {s.state === "loading" || s.state === "out" ? (
                <Button size="compact" onClick={() => advance(s)}>
                  {s.state === "loading" ? "Load truck" : "Mark delivered"}
                </Button>
              ) : null}
            </li>
          )
        })}
      </ul>
      <p role="status" className="m-0 text-[14px] text-kl-ink-2">
        {done === stops.length - 1 ? "Every stop is done except Vance Refrigeration." : `${done} of ${stops.length} delivered. ${last}`}
      </p>
    </div>
  )
}

Installation

npx shadcn@latest add @keyline-ui/status

Run it where your components.json is. The first time, map the @keyline-ui registry: one command, in Installation.

Usage

import { Badge, Status } from "@/components/keyline/ui/status"
<Status kind="attention">Held: no one to sign</Status>
<Badge>DM-4111</Badge>
<Badge tone="signal">RUSH</Badge>

Examples

Badge tones

plain labels a code, signal is the one filled form for what is new or urgent, and attention marks what needs someone. Keep to one signal badge in view.

Loading the live example…

"use client"

import { Badge } from "@/components/keyline/ui/status"

const INVOICES = [
  { id: "INV-2207", client: "Scranton Business Park", amount: "$1,284.00", tone: "plain" as const, tag: "SENT" },
  { id: "INV-2208", client: "Lackawanna County", amount: "$9,610.00", tone: "signal" as const, tag: "NEW" },
  { id: "INV-2164", client: "Harper Collins", amount: "$2,450.00", tone: "attention" as const, tag: "OVERDUE" },
]

export function InvoiceBadges() {
  return (
    <ul className="m-0 grid list-none gap-3 p-0 text-[14px]">
      {INVOICES.map((i) => (
        <li key={i.id} className="flex flex-wrap items-center gap-x-3 gap-y-1">
          <Badge>{i.id}</Badge>
          <span className="min-w-0 flex-1">{i.client}</span>
          <span className="tabular-nums">{i.amount}</span>
          <Badge tone={i.tone}>{i.tag}</Badge>
        </li>
      ))}
    </ul>
  )
}

Props

PropTypeDefaultDescription
kind"normal" | "waiting" | "attention" | "done" | "none"Default —On Status: the glyph. Each kind has its own shape; the words say the rest.
childrenReactNodeDefault —The state in words. Say what to do when it is attention.
tone"plain" | "signal" | "attention"Default "plain"On Badge. signal is the only filled badge.

Both render a span and pass other props through. A badge holds a code or a short word in capitals, not a sentence.

Sheet 35 of 73PreviousStat and meterNextProgress
On this page