← Back to articles
February 202610 min read

Best Chart UI Practices for Web3 and Finance Dashboards

Choosing chart types, avoiding misleading visuals, tooltips, mobile chart UX, and performance tips. Recharts and TradingView Lightweight Charts examples for DeFi dashboards.

ChartsRechartsDeFiUI

Choosing the right chart type

The chart type should match the data story. Line charts for trends over time. Bar charts for comparisons. Area charts for cumulative values. Pie/donut charts for composition (use sparingly — max 5–6 segments). Candlestick charts for price action.

Wrong chart type is worse than no chart. A pie chart with 20 segments is unreadable. A line chart for categorical comparisons is misleading.

Line, area, and bar charts

// components/charts/PriceLineChart.tsx
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';

export function PriceLineChart({ data }: { data: { date: string; price: number }[] }) {
  return (
    <ResponsiveContainer width="100%" height={300}>
      <LineChart data={data}>
        <XAxis dataKey="date" tick={{ fontSize: 12 }} tickFormatter={formatShortDate} />
        <YAxis tick={{ fontSize: 12 }} tickFormatter={(v) => `$${v}`} domain={['auto', 'auto']} />
        <Tooltip
          formatter={(value: number) => [`$${value.toFixed(2)}`, 'Price']}
          labelFormatter={formatFullDate}
          contentStyle={{ background: '#1a1a24', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 8 }}
        />
        <Line type="monotone" dataKey="price" stroke="#6366f1" strokeWidth={2} dot={false} />
      </LineChart>
    </ResponsiveContainer>
  );
}

For financial candlestick charts, use TradingView Lightweight Charts:

// components/charts/CandlestickChart.tsx
'use client';

import { createChart, CandlestickSeries } from 'lightweight-charts';
import { useEffect, useRef } from 'react';

export function CandlestickChart({ data }: { data: CandleData[] }) {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!containerRef.current) return;
    const chart = createChart(containerRef.current, {
      width: containerRef.current.clientWidth,
      height: 400,
      layout: { background: { color: 'transparent' }, textColor: '#9ca3af' },
      grid: { vertLines: { color: 'rgba(255,255,255,0.05)' }, horzLines: { color: 'rgba(255,255,255,0.05)' } },
    });

    const series = chart.addSeries(CandlestickSeries, {
      upColor: '#10b981', downColor: '#ef4444',
      borderUpColor: '#10b981', borderDownColor: '#ef4444',
      wickUpColor: '#10b981', wickDownColor: '#ef4444',
    });
    series.setData(data);

    const observer = new ResizeObserver(() => {
      chart.applyOptions({ width: containerRef.current!.clientWidth });
    });
    observer.observe(containerRef.current);

    return () => { chart.remove(); observer.disconnect(); };
  }, [data]);

  return <div ref={containerRef} className="w-full" />;
}

Avoiding misleading visuals

Start Y-axis at zero for bar charts. For line charts showing price changes, auto-scale is fine but indicate the range. Never truncate axes to exaggerate trends. Label axes clearly with units ($, %, tokens). Show data point count and time range in the chart title or subtitle.

Loading and no-data states

Charts need fixed-height containers to prevent layout shift. Show a skeleton during loading and a clear message when there is no data.

export function ChartContainer({ isLoading, isEmpty, children }: ChartContainerProps) {
  return (
    <div className="h-[300px] w-full">
      {isLoading && <div className="h-full animate-pulse rounded-xl bg-white/5" />}
      {isEmpty && (
        <div className="flex h-full items-center justify-center rounded-xl border border-dashed border-white/10 text-gray-400">
          No data available for this period
        </div>
      )}
      {!isLoading && !isEmpty && children}
    </div>
  );
}

Tooltips and legends

Tooltips should show exact values with proper formatting. Include the date/time for time-series data. Legends should be interactive — clicking a legend item toggles that series on/off.

Mobile chart UX

Reduce tick count on mobile (5 max on X-axis). Increase touch target size for tooltip interaction. Full-width charts with 250–300px height. Hide legends on mobile and show data in a summary below the chart instead.

Performance tips

Lazy-load chart libraries with next/dynamic. Memoize data transformations. Limit data points (aggregate hourly data for long time ranges). Use canvas-based libraries (Lightweight Charts) over SVG for large datasets.

const data = useMemo(
  () => aggregateToInterval(rawData, timeRange === '1y' ? '1d' : '1h'),
  [rawData, timeRange]
);

FAQ

**Recharts vs Lightweight Charts vs Chart.js?** Recharts for standard dashboards (area, bar, pie). Lightweight Charts for financial/trading interfaces. Chart.js is fine but heavier.

**How many data points before performance issues?** SVG charts (Recharts) slow down around 1,000 points. Canvas charts (Lightweight) handle 10,000+. Aggregate data for long time ranges.

**Should I animate charts?** Subtle entrance animations are fine. Disable animations for data that updates frequently (live prices).

**Dark mode chart colors?** Use muted grid lines (white at 5% opacity). Bright data colors with sufficient contrast. Test readability on dark backgrounds.

Want to work together? I build Web3 dashboards and DeFi interfaces.