Skip to content
  1. Keyline UI
  2. Stat and meter
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

Stat and meter

Stat is one figure with its label, unit and change. The change is an arrow and words, and turns red only when its tone is bad. Meter measures a quantity against a limit; it is not for a task in progress, which is Progress.

Loading the live example…

"use client"

import { useState } from "react"
import { Panel, PanelBody, PanelHeader } from "@/components/keyline/ui/panel"
import { Segmented, SegmentedItem } from "@/components/keyline/ui/segmented"
import { Stat } from "@/components/keyline/ui/stat"

const QUARTERS = {
  q3: { reams: 18_240, reamsWas: 17_100, clients: 64, clientsWas: 66, quota: 81 },
  q4: { reams: 19_880, reamsWas: 18_240, clients: 61, clientsWas: 64, quota: 94 },
}

const pct = (now: number, was: number) => `${Math.abs(Math.round(((now - was) / was) * 1000) / 10)}%`

export function BranchStats() {
  const [q, setQ] = useState<keyof typeof QUARTERS>("q4")
  const d = QUARTERS[q]
  const lost = d.clientsWas - d.clients

  return (
    <Panel>
      <PanelHeader
        title="Scranton branch"
        code={q.toUpperCase()}
        actions={
          <Segmented aria-label="Quarter" size="compact" value={q} onValueChange={(v: string) => setQ(v as keyof typeof QUARTERS)}>
            <SegmentedItem value="q3">Q3</SegmentedItem>
            <SegmentedItem value="q4">Q4</SegmentedItem>
          </Segmented>
        }
      />
      <PanelBody flush>
        <div className="@container">
          <div className="grid divide-y divide-kl-line @md:grid-cols-3 @md:divide-x @md:divide-y-0">
            <Stat label="Paper sold" value={d.reams.toLocaleString("en-US")} unit="REAMS" delta={{ value: pct(d.reams, d.reamsWas), direction: "up", tone: "good", label: `up ${pct(d.reams, d.reamsWas)} on the quarter before` }} />
            <Stat label="Active clients" value={d.clients} delta={{ value: lost, direction: "down", tone: "bad", label: `${lost} fewer than the quarter before` }} />
            <Stat label="Corporate quota" value={d.quota} unit="%" meter={{ value: d.quota, label: "Corporate quota met" }} />
          </div>
        </div>
      </PanelBody>
    </Panel>
  )
}

Installation

npx shadcn@latest add @keyline-ui/stat

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

Usage

import { Meter, Stat } from "@/components/keyline/ui/stat"
<Stat
  label="Paper sold"
  value="19,880"
  unit="REAMS"
  delta={{ value: "9%", direction: "up", tone: "good", label: "up 9% on Q3" }}
/>
<Meter label="Warehouse floor, pallets" value={88} max={120} high={100} />

Examples

Meter with a limit

high marks the level that needs attention. Past it, the fill and the figure turn red, and the words beside it say what to do.

Loading the live example…

"use client"

import { useState } from "react"
import { Button } from "@/components/keyline/ui/button"
import { Meter } from "@/components/keyline/ui/stat"

const MAX = 120
const HIGH = 100

export function WarehouseSpace() {
  const [pallets, setPallets] = useState(88)

  return (
    <div className="grid max-w-[420px] gap-4">
      <Meter label="Warehouse floor, pallets" value={pallets} max={MAX} high={HIGH} />
      <div className="flex flex-wrap gap-3">
        <Button disabled={pallets + 8 > MAX} onClick={() => setPallets(pallets + 8)}>
          Receive 8 pallets
        </Button>
        <Button disabled={pallets === 0} onClick={() => setPallets(Math.max(0, pallets - 8))}>
          Ship 8 pallets
        </Button>
      </div>
      <p role="status" className="m-0 text-[14px] text-kl-ink-2">
        {pallets > HIGH
          ? `Over ${HIGH}. The fire marshal wants the aisles clear, so tell Darryl before the next delivery.`
          : pallets + 8 > MAX
            ? "Full."
            : `Room for ${MAX - pallets} more.`}
      </p>
    </div>
  )
}

Props

PropTypeDefaultDescription
labelReactNodeDefault —What is measured.
valueReactNodeDefault —The figure, formatted. A Skeleton shape="value" while it loads.
unitReactNodeDefault —In the code voice beside the figure: REAMS, %, PLT.
delta{ value, direction, tone?, label? }Default —The change. direction is up, down or flat; tone says whether that is good. label is what a screen reader hears.
meter{ value, max?, label? }Default —A meter under the figure, with its label hidden because the stat already says it.
highnumberDefault —On Meter: past this value it turns red.
hideLabelbooleanDefault falseOn Meter: only when the text beside it already gives the label and value.

A stat has no frame and no width of its own: set stats in a row inside a panel, and they share it. The meter is built on Base UI Meter.

Sheet 34 of 73PreviousChartNextStatus and badge
On this page