TrendIndicator
Data DisplaySigned comparisons with direction, configurable success meaning, and missing-data feedback.
Responsive viewport — resize the window to explore. Direction and density apply only to this preview. Theme applies to the docs too.
"use client";
import { TrendIndicator } from "@/components/ui/trend-indicator";
export default function TrendIndicatorExample() {
return (
<div className="grid w-full max-w-2xl gap-4 sm:grid-cols-2">
{[
{ title: "Revenue", value: "$48,240", delta: 12.4, good: true },
{ title: "Response time", value: "184 ms", delta: -8.2, good: false },
{ title: "Active projects", value: "24", delta: 0, good: true },
{ title: "New workspace", value: "—", delta: null, good: true },
].map((item) => (
<div
key={item.title}
className="space-y-3 rounded-xl border bg-card p-5"
>
<p className="text-xs text-muted-foreground">{item.title}</p>
<p className="text-2xl font-semibold tracking-tight">{item.value}</p>
<TrendIndicator
value={item.delta}
positiveIsGood={item.good}
label="vs. previous period"
/>
</div>
))}
</div>
);
}Usage
Pass an already calculated change and a concise comparison label. The component does not derive percentages from totals. Percent adds a percent sign; number displays an absolute signed delta. Set positiveIsGood=false when lower values are desirable, such as response time. Zero is neutral and null/non-finite values display No comparison. Values use en-US formatting with up to two decimal places.
import { TrendIndicator } from "@/components/ui/trend-indicator";Shipped as editable source in src/components/ui/trend-indicator.tsx in both apps. Copy the implementation below when moving it to another project. Copy the complete example from Source code for all imports and state. Add "use client" when using hooks or event handlers. See installation and theming for project setup.
Component source
The complete implementation is included in both apps. It uses the React, Radix, Lucide, and shared-core dependencies already installed in Apex. Keep your project’s semantic theme tokens when reusing it.
src/components/ui/trend-indicator.tsx
import { ArrowDownRight, ArrowUpRight, Minus } from "lucide-react";
import { cn } from "@dashboardpack/core/lib/utils";
export interface TrendIndicatorProps {
value: number | null;
label: string;
unit?: "percent" | "number";
positiveIsGood?: boolean;
className?: string;
}
export function TrendIndicator({
value,
label,
unit = "percent",
positiveIsGood = true,
className,
}: TrendIndicatorProps) {
if (value === null || !Number.isFinite(value))
return (
<span className={cn("text-xs text-muted-foreground", className)}>
{label}: No comparison
</span>
);
const direction = value > 0 ? "up" : value < 0 ? "down" : "flat";
const good = direction === "flat" ? null : value > 0 === positiveIsGood;
const Icon =
direction === "up"
? ArrowUpRight
: direction === "down"
? ArrowDownRight
: Minus;
return (
<span
className={cn(
"inline-flex items-center gap-1.5 text-xs",
good === null
? "text-muted-foreground"
: good
? "text-success"
: "text-destructive",
className,
)}
>
<Icon aria-hidden className="size-3.5 shrink-0" />
<span>
{value > 0 ? "+" : ""}
{new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format(
value,
)}
{unit === "percent" ? "%" : ""}
<span className="sr-only">
{" "}
{direction === "flat"
? "unchanged"
: direction === "up"
? "increase"
: "decrease"}
</span>
</span>{" "}
<span className="text-muted-foreground">{label}</span>
</span>
);
}API reference
Common props and defaults. Slot-specific props are named below; native elements also accept their standard HTML attributes. The component TypeScript definitions are the complete type reference.
| Prop | Type / Default | Description |
|---|---|---|
| value | number | nullDefault: required | Signed delta; null or non-finite means no comparison. |
| label | stringDefault: required | Context such as vs. previous period. |
| unit | percent | numberDefault: percent | Whether to append the percent symbol. |
| positiveIsGood | booleanDefault: true | Reverse success/destructive meaning when a lower metric is better. |
| className | stringDefault: — | Extend the container with semantic theme utilities. |