The TokenInput component provides a unified interface for selecting tokens and entering amounts. It includes balance display, USD value calculation, and percentage shortcuts.
Import
import { TokenInput } from '@/components/TokenInput';
Overview
TokenInput is a reusable component used throughout the application for token-related inputs. It combines:
- Asset Selection: Token picker with search and balance display
- Amount Input: Numeric input with validation
- Balance Display: Shows available balance for selected token
- USD Value: Calculates and displays fiat value
- Percentage Shortcuts: Quick-select buttons (25%, 50%, 75%, MAX)
Props
Display label for the input field (e.g., “Sell”, “Buy”, “You’re sending”)
Array of available assets to choose frominterface Asset {
aggregatedAssetId: string;
symbol: string;
name: string;
decimals: number;
aggregatedEntities: AggregatedAssetEntity[];
}
The aggregated asset ID of the currently selected token (e.g., “ob:usdc”)
onAssetChange
(value: string) => void
required
Callback fired when the selected asset changes
The current amount value (human-readable format)
onAmountChange
(e: React.ChangeEvent<HTMLInputElement>) => void
required
Callback fired when the amount input changes
balance
TokenBalance | null
default:"null"
Balance information for the selected tokeninterface TokenBalance {
aggregatedAssetId: string;
balance: string; // Raw balance in token's base unit
fiatValue: number; // USD value of the balance
}
Whether to show percentage shortcut buttons (25%, 50%, 75%, MAX)
onPercentageClick
(percentage: number) => void
Callback fired when a percentage button is clicked. Receives the percentage (25, 50, 75, or 100)
Whether the input is disabled
Whether the amount input is read-only (used for output amounts in swaps)
balances
TokenBalance[]
default:"[]"
Array of all token balances (passed to AssetSelect for balance display)
usdValue
string | null
default:"null"
Override USD value to display (if not provided, calculated automatically)
Usage Examples
import { TokenInput } from '@/components/TokenInput';
import { useState } from 'react';
function MyComponent() {
const [asset, setAsset] = useState('ob:usdc');
const [amount, setAmount] = useState('');
return (
<TokenInput
label="Amount"
assets={assets}
selectedAsset={asset}
onAssetChange={setAsset}
amount={amount}
onAmountChange={(e) => setAmount(e.target.value)}
/>
);
}
<TokenInput
label="Sell"
assets={assets}
selectedAsset={sourceAsset}
onAssetChange={setSourceAsset}
amount={amount}
onAmountChange={(e) => setAmount(e.target.value)}
balance={sourceBalance}
showPercentageButtons={true}
onPercentageClick={(percentage) => {
if (sourceBalance) {
const maxAmount = formatTokenAmount(
sourceBalance.balance,
selectedAsset.decimals
);
const targetAmount = (parseFloat(maxAmount) * percentage / 100).toString();
setAmount(targetAmount);
}
}}
balances={allBalances}
/>
Read-Only (Output Display)
<TokenInput
label="Buy"
assets={assets}
selectedAsset={targetAsset}
onAssetChange={setTargetAsset}
amount={calculatedAmount}
onAmountChange={() => {}} // No-op for read-only
balance={targetBalance}
readOnly={true}
usdValue="1234.56"
balances={allBalances}
/>
Features
Amount Validation
The component validates that input is a valid number:
const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
// Only allow valid number format
if (!/^(\d*\.?\d*)?$/.test(value)) return;
// Call parent handler
onAmountChange(e);
};
USD Value Calculation
Automatically calculates USD value based on balance and amount:
const getUSDValue = () => {
if (!balance || !amount || !selectedAssetData) return null;
const numericAmount = parseFloat(amount);
if (isNaN(numericAmount) || numericAmount === 0) return null;
const balanceAmount = parseFloat(
formatTokenAmount(balance.balance, selectedAssetData.decimals || 18)
);
if (balanceAmount === 0) return null;
const pricePerToken = balance.fiatValue / balanceAmount;
const usdValue = numericAmount * pricePerToken;
return usdValue.toFixed(2);
};
Balance Display
Shows formatted balance with token symbol:
const formatBalance = (balance: TokenBalance, asset: Asset) => {
const formattedAmount = formatTokenAmount(balance.balance, asset.decimals || 18);
const numericAmount = Number(formattedAmount);
return numericAmount.toFixed(numericAmount < 0.01 ? 6 : 2);
};
Percentage Shortcuts
Optional quick-select buttons:
{showPercentageButtons && balance && selectedAssetData && onPercentageClick && (
<div className="flex gap-2 px-1">
{[25, 50, 75, 100].map(percentage => (
<Button
key={percentage}
variant="outline"
size="sm"
onClick={() => onPercentageClick(percentage)}
disabled={disabled}
>
{percentage === 100 ? 'MAX' : `${percentage}%`}
</Button>
))}
</div>
)}
Layout Structure
┌─────────────────────────────────────────┐
│ Label [25%][50%][75%][MAX] │
├─────────────────────────────────────────┤
│ │
│ 1234.56 [USDC ▼] │
│ $1,234.56 1000 USDC │
│ │
└─────────────────────────────────────────┘
Amount USD Value Selector Balance
Styling
The component uses Tailwind CSS classes and adapts to light/dark themes:
- Container:
bg-muted/50 rounded-2xl p-4 border border-border
- Input:
text-2xl font-medium bg-transparent
- USD Value:
text-sm text-muted-foreground
- Balance:
text-xs text-muted-foreground
Onboarding Support
The amount input includes a data attribute for onboarding:
<Input
data-onboarding="amount-input"
// ... other props
/>
Asset Symbol Helper
Extracts display symbol from aggregated asset ID:
const getAssetSymbol = (assetId: string) => {
return assetId.split(':')[1]?.toUpperCase() || assetId;
};
// Example:
// 'ob:usdc' → 'USDC'
// 'ob:eth' → 'ETH'
Dependencies
import { AssetSelect } from '@/components/AssetSelect';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
Best Practices
Always validate the amount on the parent component before using it in API calls. The TokenInput component only validates format, not value constraints.
When using percentage buttons, ensure the onPercentageClick handler accounts for token decimals correctly to avoid precision errors.
The readOnly prop is useful for output fields (like the destination amount in a swap) where users shouldn’t edit the value directly.