# Standings: Category Chart

> A journaling app's eight weeks after its relaunch, climbing a store's Productivity chart from sixth to first, with a rank readout that turns over at each pass.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/standings-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/standings-demo
- Screenshot: https://shadcn.exchange/shot/keyframeui/standings-demo/light.png
- Registry item: https://keyframeui.com/r/standings-demo.json
- Categories: data, chart, motion
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/standings-demo.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/standings.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/standings#standings-demo

## Files

### `registry/demos/standings-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { Standings, type StandingsEntry } from "@/components/ui/standings";

// Daybook, a journaling app, over the eight weeks after its relaunch: daily
// downloads in the store's Productivity chart, and the five apps above it.
const WEEKS = ["W1", "W2", "W3", "W4", "W5", "W6", "W7", "W8"];

function AppIcon({ from, to, children }: { from: string; to: string; children: React.ReactNode }) {
  return (
    <span
      className="grid size-full place-items-center text-white shadow-[inset_0_0_0_1px_rgb(255_255_255/0.18)]"
      style={{ backgroundImage: `linear-gradient(145deg, ${from}, ${to})` }}
    >
      <svg viewBox="0 0 20 20" className="size-[18px]" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
        {children}
      </svg>
    </span>
  );
}

const APPS: StandingsEntry[] = [
  {
    id: "daybook",
    label: "Daybook",
    detail: "Journal & mood",
    you: true,
    value: [3100, 3900, 5200, 6400, 7900, 9300, 10800, 12600],
    icon: (
      <AppIcon from="#ff8a3d" to="#ff3d00">
        <path d="M5 3.5h8.5a1.5 1.5 0 0 1 1.5 1.5v11.5H6.5A1.5 1.5 0 0 1 5 15V3.5Z" />
        <path d="M8 7.5h4M8 10.5h2.5" />
      </AppIcon>
    ),
  },
  {
    id: "tally",
    label: "Tally Notes",
    detail: "Notes & lists",
    value: [11800, 11600, 11900, 11400, 11200, 11000, 10900, 10600],
    icon: (
      <AppIcon from="#4f8bff" to="#1d4ed8">
        <path d="M5.5 6h9M5.5 10h9M5.5 14h5.5" />
      </AppIcon>
    ),
  },
  {
    id: "northdesk",
    label: "Northdesk",
    detail: "Shared inbox",
    value: [9800, 9600, 9100, 9400, 9000, 8800, 8600, 8500],
    icon: (
      <AppIcon from="#34d399" to="#059669">
        <path d="M3.5 10.5 6 5h8l2.5 5.5V15h-13v-4.5Z" />
        <path d="M3.5 10.5H7l1 2h4l1-2h3.5" />
      </AppIcon>
    ),
  },
  {
    id: "kiln",
    label: "Kiln Calendar",
    detail: "Calendar & booking",
    value: [8600, 8900, 9300, 8200, 7600, 7300, 7100, 6900],
    icon: (
      <AppIcon from="#fb7185" to="#e11d48">
        <rect x="4" y="5" width="12" height="11" rx="2" />
        <path d="M4 8.5h12M7.5 3.5v3M12.5 3.5v3" />
      </AppIcon>
    ),
  },
  {
    id: "pebble",
    label: "Pebble",
    detail: "Habit tracker",
    value: [6200, 6000, 5800, 6100, 6300, 6000, 5900, 5700],
    icon: (
      <AppIcon from="#a78bfa" to="#6d28d9">
        <circle cx="10" cy="10" r="5.5" />
        <path d="m7.8 10 1.6 1.6 3-3.2" />
      </AppIcon>
    ),
  },
  {
    id: "orbit",
    label: "Orbit Focus",
    detail: "Focus timer",
    value: [4900, 4700, 4500, 4300, 4100, 3900, 3800, 3600],
    icon: (
      <AppIcon from="#fbbf24" to="#ea580c">
        <circle cx="10" cy="10.5" r="5.5" />
        <path d="M10 7.5v3l2 1.5M8.5 3.5h3" />
      </AppIcon>
    ),
  },
];

export function StandingsDemo() {
  const [place, setPlace] = React.useState(6);
  const numeral = React.useRef<HTMLSpanElement>(null);

  const onOrderChange = React.useCallback((order: string[]) => setPlace(order.indexOf("daybook") + 1), []);

  // The numeral rolls in from below whenever Daybook changes place.
  const drawn = React.useRef(place);
  React.useEffect(() => {
    if (drawn.current === place) return;
    drawn.current = place;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    numeral.current?.animate(
      [
        { transform: "translateY(40%)", opacity: 0 },
        { transform: "translateY(0)", opacity: 1 },
      ],
      { duration: 260, easing: "cubic-bezier(0.23, 1, 0.32, 1)" },
    );
  }, [place]);

  return (
    <div className="w-full max-w-xl rounded-2xl border border-zinc-950/[0.08] bg-white text-zinc-950 shadow-[0_1px_2px_rgb(0_0_0/0.04),0_12px_32px_-12px_rgb(0_0_0/0.12)] dark:border-white/10 dark:bg-zinc-950 dark:text-zinc-50">
      <div className="flex items-start justify-between gap-4 border-b border-zinc-950/[0.07] px-5 pt-4 pb-3.5 dark:border-white/[0.08]">
        <div className="min-w-0">
          <p className="text-[13px] font-semibold tracking-[-0.01em]">Top free · Productivity</p>
          <p className="mt-0.5 text-xs text-zinc-500 dark:text-zinc-400">United States · daily downloads</p>
        </div>
        <div className="text-right">
          <p className="text-[11px] font-medium text-zinc-500 dark:text-zinc-400">Daybook rank</p>
          <p className="mt-0.5 overflow-hidden text-3xl leading-none font-semibold tracking-[-0.04em] tabular-nums">
            <span className="text-[#ff5a1f]">#</span>
            <span ref={numeral} className="inline-block">
              {place}
            </span>
          </p>
        </div>
      </div>
      <Standings
        aria-label="Top free apps in Productivity, by week"
        entries={APPS}
        periods={WEEKS}
        loop
        onOrderChange={onOrderChange}
        className="px-2 pt-1 pb-3 [--standings-surface:#ffffff] dark:[--standings-surface:#09090b]"
      />
    </div>
  );
}
```
