KPI Cards
Discover various KPI card layouts and features to highlight important metrics in your UI.
Color Variants
Showcase of KPI cards with different border colors to indicate status or category.
KPI Label
10 352
GB
KPI Label
10 352
GB
KPI Label
10 352
GB
KPI Label
10 352
GB
import { HvCard, HvCardContent, HvColor, HvStatusIcon, HvTypography, } from "@hitachivantara/uikit-react-core"; const colors = ["positive", "warning", "negative", "info"] satisfies HvColor[]; export default function Demo() { return ( <div className="grid gap-sm grid-cols-1 sm:grid-cols-2 md:grid-cols-4"> {colors.map((color) => ( <HvCard key={color} statusColor={color} bgcolor="bgContainer"> <Kpi title="KPI Label" value="10 352" unit="GB" variant={color} /> </HvCard> ))} </div> ); } function Kpi({ title, value, unit, variant, }: { title: React.ReactNode; value: React.ReactNode; unit: React.ReactNode; variant: keyof typeof colorVariantMap; }) { return ( <HvCardContent className="grid gap-sm pb-xs!"> <div className="flex items-center gap-xxs"> <HvStatusIcon size="xs" variant={colorVariantMap[variant]} /> <span>{title}</span> </div> <div className="flex items-baseline gap-2px"> <HvTypography variant="title3">{value}</HvTypography> <HvTypography variant="caption2" className="text-textSubtle"> {unit} </HvTypography> </div> </HvCardContent> ); } const colorVariantMap = { positive: "success", warning: "warning", negative: "error", info: "info", } as const;
Selectable Cards
KPI cards with radio selection support—ideal for interactive dashboards or comparisons.
KPI Label
10 352
GB
KPI Label
10 352
GB
KPI Label
10 352
GB
KPI Label
10 352
GB
import { useId, useState } from "react"; import { HvCard, HvCardContent, HvColor, HvRadio, HvStatusIcon, HvTypography, } from "@hitachivantara/uikit-react-core"; const colors = ["positive", "warning", "negative", "info"] satisfies HvColor[]; export default function Demo() { const [selectedIndex, setSelectedIndex] = useState(-1); return ( <div className="grid gap-sm grid-cols-1 sm:grid-cols-2 md:grid-cols-4"> {colors.map((color, i) => ( <HvCard key={color} statusColor={color} bgcolor="bgContainer" className="cursor-pointer" onClick={() => setSelectedIndex(i)} selectable selected={i === selectedIndex} > <Kpi title="KPI Label" value="10 352" unit="GB" variant={color} selected={i === selectedIndex} onSelect={() => setSelectedIndex(i)} /> </HvCard> ))} </div> ); } function Kpi({ title, value, unit, variant, selected, onSelect, }: { title: React.ReactNode; value: React.ReactNode; unit: React.ReactNode; variant: keyof typeof colorVariantMap; selected: boolean; onSelect: () => void; }) { const titleId = useId(); return ( <HvCardContent className="grid gap-sm pb-xs!"> <div className="flex items-center gap-xxs"> <HvStatusIcon size="xs" variant={colorVariantMap[variant]} /> <span id={titleId}>{title}</span> </div> <div className="flex justify-between items-center"> <div className="flex items-baseline gap-2px"> <HvTypography variant="title3">{value}</HvTypography> <HvTypography variant="caption2" className="text-textSubtle"> {unit} </HvTypography> </div> <HvRadio name="kpi" aria-labelledby={titleId} checked={selected} onClick={onSelect} /> </div> </HvCardContent> ); } const colorVariantMap = { positive: "success", warning: "warning", negative: "error", info: "info", } as const;
Dynamic KPI Value
Live KPI card that fetches and displays data based on a selected endpoint, including fallback/error handling.
Loading data...
import { useState } from "react"; import useSWR from "swr"; import { HvCard, HvCardContent, HvLoading, HvRadio, HvRadioGroup, HvStatusIcon, HvTypography, } from "@hitachivantara/uikit-react-core"; export default function Demo() { const [endpoint, setEndpoint] = useState("1"); return ( <div className="grid gap-sm"> <HvRadioGroup orientation="horizontal" label="Choose endpoint" value={endpoint} onChange={(evt, val) => setEndpoint(val)} > <HvRadio label="Option 1" value="1" /> <HvRadio label="Option 2" value="2" /> <HvRadio label="Bad fetch" value="error" /> </HvRadioGroup> <HvCard statusColor="info" bgcolor="bgContainer" className="w-300px"> <HvCardContent className="grid gap-sm pb-xs! h-90px"> <KpiData id={endpoint} /> </HvCardContent> </HvCard> </div> ); } function KpiData({ id }: { id: string }) { const { data, isLoading, error } = useSWR(id, fetchData); if (isLoading) { return <HvLoading small label="Loading data..." className="flex-row" />; } if (error) { return ( <div className="flex gap-xs items-center justify-center text-negative"> <HvStatusIcon variant="error" /> <HvTypography>{error.message}</HvTypography> </div> ); } return ( <> <div className="flex items-center gap-xxs"> <HvStatusIcon size="xs" variant="info" /> <span>{data?.title}</span> </div> <div className="flex items-baseline gap-2px"> <HvTypography variant="title3">{data?.value}</HvTypography> <HvTypography variant="caption2" className="text-textSubtle"> {data?.unit} </HvTypography> </div> </> ); } function fetchData(id: string) { interface Data { title: string; value: number; unit: string; } return new Promise<Data>((resolve, reject) => { setTimeout(() => { if (Number.isNaN(Number(id))) { reject(new Error("Error fetching data")); return; } resolve({ title: `Title ${id}`, value: Number.parseInt(id.repeat(6), 8) % 10 ** 5, unit: "GB", }); }, 4000); }); }
Small Donut KPI
Small custom donut indicators for displaying percentage values, useful for compact KPI displays. No charts used, just SVG circles.
0%
3%
27%
50%
76%
100%
import { HvTypography, theme } from "@hitachivantara/uikit-react-core"; export default function Demo() { return ( <div className="w-full flex items-center justify-center gap-md"> <SmallDonut value={0} /> <SmallDonut value={3} /> <SmallDonut value={27} /> <SmallDonut value={50} /> <SmallDonut value={76} /> <SmallDonut value={100} /> </div> ); } const SmallDonut = ({ value }: { value: number }) => { const size = 24; const strokeWidth = 2; const margin = 1; const radius = (size - strokeWidth) / 2; const circumference = 2 * Math.PI * radius; const pct = Math.max(0, Math.min(100, value)); const fillLength = value === 100 ? circumference : (pct / 100) * circumference; const grayLength = value === 100 ? 0 : circumference - fillLength - (value === 0 ? 0 : 2 * margin); return ( <div className="flex gap-xs items-center"> <svg width={size} height={size} style={{ transform: "rotate(-90deg)" }}> <circle cx={size / 2} cy={size / 2} r={radius} fill="none" stroke={theme.colors.positive} strokeWidth={strokeWidth} strokeDasharray={`${fillLength} ${circumference - fillLength}`} /> <circle cx={size / 2} cy={size / 2} r={radius} fill="none" stroke={theme.colors.border} strokeWidth={strokeWidth} strokeDasharray={`${grayLength} ${circumference - grayLength}`} strokeDashoffset={-(fillLength + margin)} /> </svg> <HvTypography variant="captionLabel" style={{ width: size }} className="flex items-center" > {value}% </HvTypography> </div> ); };