# KPI

A tile with a label and one headline figure, and when they help a delta, a badge, a caption and a spark.

A KPI tile answers one question with one number. The figure is a count, grouped as 12,480 and counted up as it appears, or ready text such as 98.2% or 1.4 s. When the figure is compacted, exact holds the full value as a tooltip; a delta or a badge sits beside the label, and a spark draws the recent trend under the figure.

## Use it

Web:

```rust
use ocean_ui_core::{Delta, Figure, Kpi};
let delta = Some(Delta { by: -6, against: 134, fewer_is_better: true });
Kpi { label: "Open tickets".into(), figure: Figure::Count(128), delta, ..Default::default() }
```

Native:

```rust
use ocean_ui_native::{Show, core::{Delta, Figure, Kpi}};
let delta = Some(Delta { by: 6, against: 24, fewer_is_better: false });
Kpi { label: "Commits".into(), figure: Figure::Count(12_480), delta, ..Default::default() }.show(ui);
```

Cargo feature `tiles`; since 2026-10-29.
In App UI the `kpis` block draws it ([kpis](/ui/blocks/kpis.md)).

## Props

| Name | Type | Default | What |
|---|---|---|---|
| `label` | `String` | `—` | What the figure counts, in sentence case. |
| `figure` | `Figure` | `Count(0)` | Figure::Count(n), a whole number grouped and counted up, or Figure::Text(s), ready words. |
| `exact` | `Option<String>` | `None` | The full value as a tooltip when the figure is compacted. |
| `delta` | `Option<Delta>` | `None` | The change since the last period, beside the label. |
| `badge` | `Option<Badge>` | `None` | A short state beside the label. |
| `caption` | `Option<String>` | `None` | One line under the figure, such as the period it covers. |
| `spark` | `Option<Vec<f32>>` | `None` | Recent values drawn as a small line under the figure. |

On the Mac: ![KPI, dark](/ui/native/kpi-dark.png) ![KPI, light](/ui/native/kpi-light.png)

## Examples

### With a comparison

A number with what it is measured against, and a spark of the trend.

```rust
Kpi {
    label: "Open tickets".into(),
    figure: Figure::Count(1_284),
    delta: Some(Delta { by: -36, against: 1_320, fewer_is_better: true }),
    caption: Some("Against last week".into()),
    spark: Some(vec![9.0, 11.0, 10.0, 13.0, 12.0, 15.0, 14.0]),
    ..Default::default()
}
```

### A row of tiles

Tiles say how it is going; at most eight on a screen.

```rust
out.push_str(r#"<div class="u-kpis">"#);
Kpi {
    label: "Resolved today".into(),
    figure: Figure::Count(342),
    delta: Some(Delta { by: 41, against: 301, fewer_is_better: false }),
    ..Default::default()
}.html(out);
Kpi {
    label: "First reply".into(),
    figure: Figure::Text("4 min".into()),
    caption: Some("Median, last 24 hours".into()),
    ..Default::default()
}.html(out);
Kpi {
    label: "Spend this month".into(),
    figure: Figure::Text("$8,420".into()),
    badge: Some(Badge { text: "On budget".into(), tone: Tone::Ok }),
    ..Default::default()
}.html(out);
out.push_str("</div>");
```

### Compact figure

A large count is shortened; the exact value is its tooltip.

```rust
Kpi {
    label: "Rows synced".into(),
    figure: Figure::Text("2.4M".into()),
    exact: Some("2,418,903".into()),
    caption: Some("Since the first sync".into()),
    ..Default::default()
}
```

## Accessibility

- The tile is a section with its label as an h3, so it shows in the page's heading outline.
- A count is written into the page as grouped text, so a screen reader reads the final number; the count-up does not run under reduced motion.
- The spark is aria-hidden and the exact value is only a title tooltip, so put anything that matters in the caption or the delta.

## Related

- [Delta](/ui/components/delta.md)
- [Spark](/ui/components/spark.md)
- [Card](/ui/components/card.md)
