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.
10 352
GB
10 352
GB
10 352
GB
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" /> </HvCard> ))} </div> ); } function Kpi({ title, value, unit, }: { title: React.ReactNode; value: React.ReactNode; unit: React.ReactNode; }) { return ( <HvCardContent className="grid gap-sm pb-xs!"> <div className="flex items-center gap-xxs"> <HvStatusIcon size="xs" customIcon={<div className="i-ph-copy-simple" />} /> <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> ); }
Selectable Cards
KPI cards with radio selection support—ideal for interactive dashboards or comparisons.
10 352
GB
10 352
GB
10 352
GB
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" selected={i === selectedIndex} onSelect={() => setSelectedIndex(i)} /> </HvCard> ))} </div> ); } function Kpi({ title, value, unit, selected, onSelect, }: { title: React.ReactNode; value: React.ReactNode; unit: React.ReactNode; 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" customIcon={<div className="i-ph-copy-simple" />} /> <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> ); }
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" customIcon={<div className="i-ph-copy-simple" />} /> <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); }); }