Building a Decentralized Trading Platform: Frontend UI/UX Lessons Learned
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:
// 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:
.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;
}
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:
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:
// 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:
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.