# Tooltip Formatter

> A chart tooltip demonstrating custom formatted values built with TanStack Charts.

A shadcn/ui block from the [@paceui](https://paceui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/paceui/tanstack-charts-tooltip-formatter).

- Page, with a live preview: https://shadcn.exchange/r/paceui/tanstack-charts-tooltip-formatter
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/paceui/tanstack-charts-tooltip-formatter-light-9e97ca1cdf1a1e1e.png
- Registry item: https://paceui.com/r/tanstack-charts-tooltip-formatter.json
- Listed: 2026-10-21, source last changed 2026-10-21

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/paceui/tanstack-charts-tooltip-formatter.json
```

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

## Dependencies

- npm packages: `@tanstack/charts`, `@tanstack/react-charts`, `d3-scale`
- Registry items: `card`

## Files

### `components/tanstack-charts/tooltip/tooltip-formatter.tsx`

```tsx
import { barY, defineChart, stack } from "@tanstack/charts";
import { motion } from "@tanstack/charts/motion";
import { tooltip } from "@tanstack/charts/tooltip";
import { Chart } from "@tanstack/react-charts/core";
import { useMemo, useRef } from "react";

import { scaleBand, scaleLinear } from "d3-scale";

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";

interface ActivityDatum {
    date: string;
    activity: "running" | "swimming";
    value: number;
}

function generateDemoData(): ActivityDatum[] {
    return [
        { date: "2024-07-15", activity: "running", value: 450 },
        { date: "2024-07-15", activity: "swimming", value: 300 },
        { date: "2024-07-16", activity: "running", value: 380 },
        { date: "2024-07-16", activity: "swimming", value: 420 },
        { date: "2024-07-17", activity: "running", value: 520 },
        { date: "2024-07-17", activity: "swimming", value: 310 },
        { date: "2024-07-18", activity: "running", value: 310 },
        { date: "2024-07-18", activity: "swimming", value: 490 },
        { date: "2024-07-19", activity: "running", value: 600 },
        { date: "2024-07-19", activity: "swimming", value: 350 },
    ];
}

const activityNames = ["running", "swimming"] as const;
const chartColors = ["var(--chart-1)", "var(--chart-2)"];

const motionRenderer = motion({
    initial: "always",
    transition: { type: "spring", stiffness: 170, damping: 18, mass: 1 },
});

function formatWeekday(value: string) {
    return new Intl.DateTimeFormat("en-US", { weekday: "short" }).format(new Date(value));
}

function titleCase(value: string) {
    return value.charAt(0).toUpperCase() + value.slice(1);
}

export function TooltipFormatter() {
    const data = useMemo(() => generateDemoData(), []);
    const seededTooltip = useRef(false);

    const definition = useMemo(() => {
        return defineChart(
            {
                renderer: motionRenderer,
                motion: {
                    transition: { type: "spring", stiffness: 170, damping: 18, mass: 1 },
                },
                marks: [
                    barY(data, {
                        id: "activity-bars",
                        x: "date",
                        y: "value",
                        z: "activity",
                        color: "activity",
                        key: (row) => `${row.date}:${row.activity}`,
                        layout: stack({ order: activityNames }),
                        radius: 4,
                        inset: 4,
                    }),
                ],
                scales: {
                    x: {
                        scale: () => scaleBand<string>().paddingInner(0.35).paddingOuter(0.15),
                        axis: {
                            line: false,
                            ticks: { size: 0, padding: 10, format: formatWeekday },
                        },
                    },
                    y: { scale: () => scaleLinear().domain([0, 1000]), axis: false },
                },
                color: { domain: activityNames, range: chartColors },
                margin: { top: 10, right: 10, bottom: 30, left: 10 },
            },
            {
                focus: "group-x",
                tooltip: {
                    use: tooltip,
                    className:
                        "font-sans shadow-none! rounded-lg bg-popover text-popover-foreground border p-2.5 text-xs min-w-[140px] pointer-events-none transition-all duration-75",
                    offset: 8,
                    sort: "color-domain",
                    content: (points) => {
                        if (!points.length) return { title: "", rows: [] };
                        const currentDate = (points[0]?.datum as ActivityDatum)?.date ?? "";
                        const ordered = [...points].sort(
                            (l, r) =>
                                activityNames.indexOf((l.datum as ActivityDatum).activity) -
                                activityNames.indexOf((r.datum as ActivityDatum).activity),
                        );
                        return {
                            title: currentDate,
                            rows: ordered.map((p) => {
                                const d = p.datum as ActivityDatum;
                                return {
                                    label: titleCase(d.activity),
                                    value: `$${(d.value * 12.5).toFixed(2)}`,
                                    color: p.color || (d.activity === "running" ? "var(--chart-1)" : "var(--chart-2)"),
                                };
                            }),
                        };
                    },
                },
            },
        );
    }, [data]);

    return (
        <Card className="flex w-full max-w-md flex-col">
            <CardHeader className="pb-4">
                <CardTitle>Financial Billing Stream</CardTitle>
                <CardDescription>Monthly billing analytics with custom formatted currency values</CardDescription>
            </CardHeader>
            <CardContent>
                <div className="mx-auto flex aspect-[16/9] w-full items-center justify-center">
                    <Chart
                        definition={definition}
                        renderer={motionRenderer}
                        initialWidth={350}
                        height={200}
                        ariaLabel="API Request Rate Chart"
                        onRender={({ scene, interaction }: any) => {
                            if (seededTooltip.current) return;
                            const point = scene.points.find(
                                (candidate: any) => (candidate.datum as ActivityDatum).date === "2024-07-16",
                            );
                            if (!point) return;
                            seededTooltip.current = true;
                            interaction.setControlledFocus(point, {
                                source: "programmatic",
                            });
                        }}
                    />
                </div>
            </CardContent>
        </Card>
    );
}

export default TooltipFormatter;
```
