Animated Financial Display Design System
by @wpank
Patterns for animating financial numbers with spring physics, formatting, and visual feedback. Covers animated counters, price tickers, percentage changes, and value flash effects. Use when building financial dashboards or trading UIs. Triggers on animated number, price animation, financial display, number formatting, spring animation, value ticker.
clawhub install animated-financial-displayπ About This Skill
name: animated-financial-display model: standard description: Patterns for animating financial numbers with spring physics, formatting, and visual feedback. Covers animated counters, price tickers, percentage changes, and value flash effects. Use when building financial dashboards or trading UIs. Triggers on animated number, price animation, financial display, number formatting, spring animation, value ticker.
Animated Financial Display
Create engaging financial number displays with smooth animations, proper formatting, and visual feedback on value changes.
When to Use
Pattern 1: Spring-Animated Number
Using framer-motion's spring physics:
import { useSpring, animated } from '@react-spring/web';
import { useEffect, useRef } from 'react';interface AnimatedNumberProps {
value: number;
prefix?: string;
suffix?: string;
decimals?: number;
duration?: number;
}
export function AnimatedNumber({
value,
prefix = '',
suffix = '',
decimals = 2,
duration = 500,
}: AnimatedNumberProps) {
const prevValue = useRef(value);
const { number } = useSpring({
from: { number: prevValue.current },
to: { number: value },
config: { duration },
});
useEffect(() => {
prevValue.current = value;
}, [value]);
return (
{number.to((n) => ${prefix}${n.toFixed(decimals)}${suffix})}
);
}
Usage
Pattern 2: Value with Flash Effect
Flash color on value change:
import { useEffect, useState, useRef } from 'react';
import { cn } from '@/lib/utils';interface FlashingValueProps {
value: number;
formatter: (value: number) => string;
}
export function FlashingValue({ value, formatter }: FlashingValueProps) {
const [flash, setFlash] = useState<'up' | 'down' | null>(null);
const prevValue = useRef(value);
useEffect(() => {
if (value !== prevValue.current) {
setFlash(value > prevValue.current ? 'up' : 'down');
prevValue.current = value;
const timer = setTimeout(() => setFlash(null), 600);
return () => clearTimeout(timer);
}
}, [value]);
return (
{formatter(value)}
);
}
Pattern 3: Financial Number Formatting
// lib/formatters.ts
export function formatCurrency(
value: number,
options: {
currency?: string;
compact?: boolean;
decimals?: number;
} = {}
): string {
const { currency = 'USD', compact = false, decimals = 2 } = options; if (compact && Math.abs(value) >= 1_000_000_000) {
return $${(value / 1_000_000_000).toFixed(1)}B;
}
if (compact && Math.abs(value) >= 1_000_000) {
return $${(value / 1_000_000).toFixed(1)}M;
}
if (compact && Math.abs(value) >= 1_000) {
return $${(value / 1_000).toFixed(1)}K;
}
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(value);
}
export function formatPercentage(
value: number,
options: { showSign?: boolean; decimals?: number } = {}
): string {
const { showSign = true, decimals = 2 } = options;
const sign = showSign && value > 0 ? '+' : '';
return ${sign}${value.toFixed(decimals)}%;
}
export function formatNumber(
value: number,
options: { compact?: boolean; decimals?: number } = {}
): string {
const { compact = false, decimals = 0 } = options;
if (compact) {
return Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 1,
}).format(value);
}
return new Intl.NumberFormat('en-US', {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(value);
}
Pattern 4: Price Ticker Component
interface PriceTickerProps {
symbol: string;
price: number;
change24h: number;
changePercent24h: number;
}export function PriceTicker({
symbol,
price,
change24h,
changePercent24h,
}: PriceTickerProps) {
const isPositive = changePercent24h >= 0;
return (
{symbol}
{formatPercentage(changePercent24h)}
);
}
Pattern 5: Metric Card with Animation
interface MetricCardProps {
label: string;
value: number;
previousValue?: number;
format: 'currency' | 'percent' | 'number';
}export function MetricCard({
label,
value,
previousValue,
format,
}: MetricCardProps) {
const formatValue = (v: number) => {
switch (format) {
case 'currency': return formatCurrency(v, { compact: true });
case 'percent': return formatPercentage(v);
case 'number': return formatNumber(v, { compact: true });
}
};
const change = previousValue ? ((value - previousValue) / previousValue) * 100 : null;
return (
{label}
{change !== null && (
= 0 ? 'text-success' : 'text-destructive'
)}>
{formatPercentage(change)} from previous
)}
);
}
Pattern 6: CSS Value Flash Animation
@keyframes value-flash-up {
0% {
color: hsl(var(--success));
text-shadow: 0 0 8px hsl(var(--success) / 0.5);
}
100% {
color: inherit;
text-shadow: none;
}
}@keyframes value-flash-down {
0% {
color: hsl(var(--destructive));
text-shadow: 0 0 8px hsl(var(--destructive) / 0.5);
}
100% {
color: inherit;
text-shadow: none;
}
}
.animate-flash-up {
animation: value-flash-up 0.6s ease-out;
}
.animate-flash-down {
animation: value-flash-down 0.6s ease-out;
}
Related Skills
NEVER Do
Typography for Numbers
.metric {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
font-weight: 600;
letter-spacing: -0.02em;
}.price-large {
font-size: 2rem;
font-weight: 800;
}
.percentage {
font-size: 0.875rem;
font-weight: 500;
}
β‘ When to Use
π‘ Examples