Build in Public

Web3 Trading Dashboard Design: Making Complex Metrics Friendly for Retail Users

ClawDUX TeamApril 7, 20266 min read0 views

Web3 Trading Dashboard Design: Making Complex Metrics Friendly for Retail Users

The challenge: your data is for quants, but your users might not know what "Sharpe ratio" means. Here's how to present sophisticated metrics accessibly.

Principle 1: Progressive Disclosure

Show the simple number first. Explain on hover/click.

tsx
function MetricCard({ label, value, explanation, status }: Props) {
  const [showTooltip, setShowTooltip] = useState(false);

  const statusColors = {
    good: 'text-emerald-400 bg-emerald-400/10',
    warning: 'text-amber-400 bg-amber-400/10',
    bad: 'text-red-400 bg-red-400/10',
  };

  return (
    <div
      className="relative bg-surface-card border border-surface-border
                 rounded-xl p-4 cursor-help"
      onMouseEnter={() => setShowTooltip(true)}
      onMouseLeave={() => setShowTooltip(false)}
    >
      <div className="text-xs text-gray-500 mb-1">{label}</div>
      <div className={`text-xl font-bold ${statusColors[status]
                       .split(' ')[0]}`}>
        {value}
      </div>

      {showTooltip && (
        <div className="absolute bottom-full left-0 mb-2 w-64
                       bg-surface-elevated border border-surface-border
                       rounded-lg p-3 text-xs text-gray-400 shadow-xl z-10">
          {explanation}
        </div>
      )}
    </div>
  );
}

// Usage
<MetricCard
  label="Sharpe Ratio"
  value="1.85"
  status="good"
  explanation="Risk-adjusted return. Above 1.0 means the strategy
               earns more than it risks. 1.85 is excellent — it means
               for every unit of risk, you get 1.85 units of return."
/>

Principle 2: Color as Information

tsx
function formatMetric(value: number, type: string): {
  display: string;
  color: string;
} {
  switch (type) {
    case 'return':
      return {
        display: `${value > 0 ? '+' : ''}${value.toFixed(1)}%`,
        color: value > 0 ? 'text-emerald-400'
             : value < 0 ? 'text-red-400'
             : 'text-gray-400',
      };
    case 'sharpe':
      return {
        display: value.toFixed(2),
        color: value > 2 ? 'text-emerald-400'
             : value > 1 ? 'text-blue-400'
             : value > 0 ? 'text-amber-400'
             : 'text-red-400',
      };
    case 'drawdown':
      return {
        display: `${value.toFixed(1)}%`,
        color: Math.abs(value) < 10 ? 'text-emerald-400'
             : Math.abs(value) < 20 ? 'text-amber-400'
             : 'text-red-400',
      };
    default:
      return { display: String(value), color: 'text-gray-300' };
  }
}

Principle 3: Escrow State as a Timeline

Don't show raw status strings. Show a visual timeline:

tsx
function EscrowTimeline({ order }) {
  const steps = [
    { label: 'Escrowed', icon: '🔒', done: true },
    { label: 'Verifying', icon: '🔍',
      done: ['CONFIRMED','DISPUTED','REFUNDED'].includes(order.status),
      active: order.status === 'LOCKED',
    },
    { label: order.status === 'DISPUTED' ? 'Disputed' : 'Confirmed',
      icon: order.status === 'DISPUTED' ? '⚖️' : '✅',
      done: ['CONFIRMED','REFUNDED'].includes(order.status),
      active: order.status === 'DISPUTED',
    },
    { label: 'Settled', icon: '💰',
      done: order.status === 'CONFIRMED' || order.status === 'REFUNDED',
    },
  ];

  return (
    <div className="flex items-center gap-2">
      {steps.map((step, i) => (
        <div key={i} className="flex items-center gap-2">
          <div className={`w-8 h-8 rounded-full flex items-center
                          justify-center text-sm ${
            step.done ? 'bg-emerald-600/20 border-emerald-500/40'
            : step.active ? 'bg-blue-600/20 border-blue-500/40 animate-pulse'
            : 'bg-surface-elevated border-surface-border'
          } border`}>
            {step.icon}
          </div>
          {i < steps.length - 1 && (
            <div className={`w-8 h-0.5 ${
              step.done ? 'bg-emerald-500/40' : 'bg-surface-border'
            }`} />
          )}
        </div>
      ))}
    </div>
  );
}

Principle 4: Humanize Numbers

tsx
function humanizePrice(usdc6Decimals: bigint): string {
  const amount = Number(usdc6Decimals) / 1e6;
  if (amount >= 1000) return `$${(amount / 1000).toFixed(1)}k`;
  return `$${amount.toFixed(2)}`;
}

function humanizeTime(dateString: string): string {
  const diff = Date.now() - new Date(dateString).getTime();
  const hours = diff / 3600000;
  if (hours < 1) return 'Just now';
  if (hours < 24) return `${Math.floor(hours)}h ago`;
  if (hours < 168) return `${Math.floor(hours / 24)}d ago`;
  return new Date(dateString).toLocaleDateString();
}

Every UI decision on ClawDUX follows these principles — because a marketplace is only useful if both sophisticated quants and first-time buyers can navigate it confidently.

The core logic discussed in this article has been integrated into the ClawDUX API. Access ClawDUX-core for full permissions, or browse the marketplace to discover verified trading strategies.

#dashboard#data-visualization#ux#retail-users#web3

Related Articles