Build in Public

Building a Decentralized Trading Platform: Frontend UI/UX Lessons Learned

ClawDUX TeamApril 7, 20267 min read0 views

Building a Decentralized Trading Platform: Frontend UI/UX Lessons Learned

After months of building ClawDUX, here are the frontend lessons that saved us from rebuilding things twice.

Lesson 1: Design System First

Before writing a single component, define your tokens:

javascript
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        surface: {
          DEFAULT: '#0f1117',    // Deep black background
          card: '#1a1d27',       // Card backgrounds
          elevated: '#232733',   // Hover states, modals
          border: '#2a2e3a',     // Borders, dividers
        },
        primary: {
          DEFAULT: '#3b82f6',    // Interactive elements
          50: '#eff6ff',
          100: '#dbeafe',
          400: '#60a5fa',
          500: '#3b82f6',
          600: '#2563eb',
        },
      },
    },
  },
};

Why this matters: When you have 50+ components, changing "the blue" means changing one token, not 50 hex values.

Lesson 2: Glass Panel Components

The glass-morphism effect works beautifully for financial UIs because it creates depth without adding visual noise:

plaintext
.glass-panel {
  background: rgba(26, 29, 39, 0.7);
  backdrop-filter: blur(20px);
  border: 1px solid rgba(42, 46, 58, 0.5);
  border-radius: 1rem;
}
tsx
function StrategyCard({ strategy }) {
  return (
    <div className="glass-panel p-5 hover:border-blue-500/30
                    transition-all duration-300 group">
      <h3 className="text-white font-bold group-hover:text-blue-400
                     transition-colors">
        {strategy.title}
      </h3>

      {/* 2x2 metrics grid */}
      <div className="grid grid-cols-2 gap-3 mt-4">
        <MetricBadge
          label="Return"
          value={`${strategy.returnPct}%`}
          positive={strategy.returnPct > 0}
        />
        <MetricBadge
          label="Sharpe"
          value={strategy.sharpeRatio.toFixed(2)}
          positive={strategy.sharpeRatio > 1}
        />
        <MetricBadge
          label="Max DD"
          value={`${strategy.maxDrawdownPct}%`}
          positive={false}
        />
        <MetricBadge
          label="Win Rate"
          value={`${strategy.winRatePct}%`}
          positive={strategy.winRatePct > 50}
        />
      </div>
    </div>
  );
}

Lesson 3: Transaction State Machine

On-chain transactions have many states. Show every one of them:

tsx
type TxState =
  | 'idle'
  | 'approving'      // USDC approval
  | 'approved'
  | 'signing'        // User signing in wallet
  | 'broadcasting'   // TX sent to network
  | 'confirming'     // Waiting for block confirmation
  | 'confirmed'      // Success
  | 'failed';        // Reverted or rejected

function TransactionButton({ onExecute, state }: Props) {
  const labels: Record<TxState, string> = {
    idle: 'Buy Strategy',
    approving: 'Approving USDC...',
    approved: 'USDC Approved',
    signing: 'Sign in Wallet...',
    broadcasting: 'Sending Transaction...',
    confirming: 'Confirming on Chain...',
    confirmed: 'Purchase Complete!',
    failed: 'Transaction Failed',
  };

  return (
    <button
      onClick={onExecute}
      disabled={state !== 'idle' && state !== 'failed'}
      className={`w-full py-3 rounded-xl font-semibold
        transition-all ${
        state === 'confirmed'
          ? 'bg-emerald-600 text-white'
          : state === 'failed'
          ? 'bg-red-600/20 text-red-400 border border-red-500/30'
          : 'bg-blue-600 hover:bg-blue-500 text-white'
      }`}
    >
      {state !== 'idle' && state !== 'confirmed' && state !== 'failed' && (
        <span className="inline-block w-4 h-4 border-2 border-white/30
                        border-t-white rounded-full animate-spin mr-2" />
      )}
      {labels[state]}
    </button>
  );
}

Lesson 4: Mobile-First for Trading

60%+ of crypto users are on mobile. Every trading interface must work on 375px width:

tsx
// Use responsive grid that collapses gracefully
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {strategies.map(s => <StrategyCard key={s.id} strategy={s} />)}
</div>

// Slide-up modals instead of centered modals on mobile
<div className="fixed inset-0 z-50">
  <div className="absolute inset-x-0 bottom-0 md:relative md:max-w-lg
                  md:mx-auto md:mt-20 bg-surface-card rounded-t-2xl
                  md:rounded-2xl p-6">
    {/* Modal content */}
  </div>
</div>

Lesson 5: Skeleton Loading > Spinners

Financial data feels wrong with spinners. Use skeleton screens that match the layout:

tsx
function StrategyCardSkeleton() {
  return (
    <div className="bg-surface-card border border-surface-border
                    rounded-2xl p-5 animate-pulse">
      <div className="h-5 bg-surface-elevated rounded w-2/3 mb-4" />
      <div className="h-3 bg-surface-elevated rounded w-full mb-2" />
      <div className="h-3 bg-surface-elevated rounded w-4/5 mb-4" />
      <div className="grid grid-cols-2 gap-3">
        {[...Array(4)].map((_, i) => (
          <div key={i} className="h-12 bg-surface-elevated rounded-lg" />
        ))}
      </div>
    </div>
  );
}

These UI/UX patterns are battle-tested on ClawDUX — where users interact with smart contracts, evaluate strategy metrics, and manage escrow transactions through a consumer-grade interface.

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.

#ui-ux#frontend#design-system#web3#build-in-public

Related Articles