# Stat tile
> Stat tile with a rolling value, a change chip against a previous value and a pixel history; invert makes a drop read as good.
- React: `import { PxKpi } from "@/components/lumesec/px-kpi"`
- Collection: Pixel UI (https://elements.lumesec.ai/components/pixel-ui)
- Registry item: https://elements.lumesec.ai/r/px-kpi.json
- Page: https://elements.lumesec.ai/components/pixel-ui/kpi



Live preview: https://elements.lumesec.ai/view/px-kpi

Demo source:

```tsx
"use client";

import * as React from "react";

import { PxKpi } from "@/components/lumesec/px-kpi";

interface Metric {
  value: number;
  data: number[];
}

const BUTTON =
  "h-[30px] rounded-lg border border-border bg-card px-[11px] text-[12.5px] font-medium hover:border-[color-mix(in_srgb,var(--lumesec)_50%,var(--border))]";

/** Moves a metric by a random factor and appends it to the history, which keeps its length. */
function drift(metric: Metric, low: number, spread: number, scale: number): Metric {
  const value = Math.round(metric.value * (low + Math.random() * spread));
  return { value, data: [...metric.data.slice(1), Math.round(value / scale)] };
}

export default function PxKpiDemo() {
  const [revenue, setRevenue] = React.useState<Metric>({ value: 48210, data: [30, 32, 31, 35, 38, 36, 40, 44, 43, 48] });
  const [latency, setLatency] = React.useState<Metric>({ value: 182, data: [230, 226, 214, 220, 207, 201, 196, 190, 186, 182] });

  const refresh = () => {
    setRevenue(drift(revenue, 0.94, 0.14, 1000));
    setLatency(drift(latency, 0.9, 0.18, 1));
  };

  return (
    <div className="w-full max-w-[400px] rounded-[14px] border border-border bg-card px-5 py-[18px] shadow-[0_14px_34px_-20px_rgb(0_0_0/0.4)]">
      <div className="grid w-[min(380px,100%)] gap-3.5">
        <PxKpi
          label="Revenue this month"
          value={revenue.value}
          previous={42900}
          format="currency"
          data={revenue.data}
          note="vs. last month"
        />
        <PxKpi label="p95 latency" value={latency.value} previous={207} format="ms" invert data={latency.data} />
        <div className="flex flex-wrap justify-center gap-1.5">
          <button type="button" className={BUTTON} onClick={refresh}>
            Refresh
          </button>
        </div>
      </div>
    </div>
  );
}
```

## Playground

Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.

## Installation

```bash
npx shadcn@latest add @lumesec/px-kpi
```

First time with the @lumesec registry? Register it once, or install by URL:

```bash
npx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.json
npx shadcn@latest add https://elements.lumesec.ai/r/px-kpi.json
```

## Usage

React:

```tsx
import { PxKpi } from "@/components/lumesec/px-kpi";

export function Example() {
  return (
    <PxKpi
      label="Revenue this month"
      value={48210}
      previous={42900}
      format="currency"
      note="vs. last month"
      data={[30, 32, 31, 35, 38, 36, 40, 44, 43, 48]}
    />
  );
}
```

## Behaviour

A metric with a rolling value, a change chip and a pixel history. Use invert when lower is better, so a drop shows green.

## API reference

### Props

Also accepts every prop of `<div>` (`React.ComponentProps<"div">`), spread onto the root element.

| Prop       | Type                                                       | Default    | Description                                                                                         |
| ---------- | ---------------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------- |
| `value`    | `number`                                                   | `0`        | The metric. Changes roll in, upward when the value grows.                                           |
| `previous` | `number`                                                   | —          | Comparison value. When set and not 0, a chip shows the percentage change with ▲ or ▼.               |
| `invert`   | `boolean`                                                  | `false`    | For metrics where lower is better: a drop is coloured green and a rise red.                         |
| `format`   | `"number" \| "currency" \| "percent" \| "compact" \| "ms"` | `"number"` | `percent` shows one decimal and %, `compact` shortens large numbers (48.2K). Uses en-US formatting. |
| `currency` | `string`                                                   | `"EUR"`    | ISO currency code used with `format="currency"`.                                                    |
| `label`    | `React.ReactNode`                                          | —          | Caption above the value.                                                                            |
| `note`     | `React.ReactNode`                                          | —          | Muted text at the end of the value row, such as "vs. last month".                                   |
| `data`     | `readonly number[]`                                        | `[]`       | History drawn as pixel columns; needs at least two values. A change replays the draw-in.            |

### Ref

`ref` points at the root `HTMLDivElement`.

## Accessibility

* The value is an `aria-live="polite"` region, so updates are announced.
* The history canvas is `aria-hidden`; the change chip is plain text with ▲ or ▼.
* Reduced motion: the value and change chip swap without rolling and the history appears at once.

## Theming

Styled with Tailwind classes on your shadcn theme tokens, so light and dark follow your theme. The accent comes from `--lumesec`. See [Theming](/docs/theming).

This component reads `--lumesec-success`, `--destructive`, `--foreground` and `--muted-foreground`.

## Notes

* The history is green when the value is at or above `previous`, or when there is no `previous`, and red otherwise; `invert` swaps this.
* `percent` shows the value as given, so `12.5` reads 12.5%.


