Swap Box
A two-sided token swap form with flip button, detail rows, and submit action
Sell
1,250.00
Buy
Exchange Rate1 SOL = 162.56 USDC
Price Impact0.01%
Minimum Received0.00612 SOL
Network Fee0.00005 SOL
import { SwapBox } from "@/components/sol/swap-box";
const SOL_ICON =
"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png";
const USDC_ICON =
"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png";
export function SwapBoxDemo() {
return (
<SwapBox
tokens={[
{ icon: USDC_ICON, symbol: "USDC" },
{ icon: SOL_ICON, symbol: "SOL" },
]}
defaultFromToken="USDC"
defaultToToken="SOL"
fromBalance="1,250.00"
details={[
{ label: "Exchange Rate", value: "1 SOL = 162.56 USDC" },
{ label: "Price Impact", value: "0.01%", className: "text-emerald-500" },
{ label: "Minimum Received", value: "0.00612 SOL" },
]}
/>
);
}Installation
pnpm dlx shadcn@latest add @solanaui/swap-box
npx shadcn@latest add @solanaui/swap-box
yarn dlx shadcn@latest add @solanaui/swap-box
Usage
const SOL_ICON =
"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png";
const USDC_ICON =
"https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png";
<SwapBox
tokens={[
{ icon: USDC_ICON, symbol: "USDC" },
{ icon: SOL_ICON, symbol: "SOL" },
]}
defaultFromToken="USDC"
defaultToToken="SOL"
fromBalance="1,250.00"
details={[
{ label: "Exchange Rate", value: "1 SOL = 162.56 USDC" },
{ label: "Price Impact", value: "0.01%" },
]}
/>Submit Handler
Pass onSubmit to handle swap execution when the button is clicked.
<SwapBox
tokens={[
{ icon: USDC_ICON, symbol: "USDC" },
{ icon: SOL_ICON, symbol: "SOL" },
]}
defaultFromToken="USDC"
defaultToToken="SOL"
onSubmit={() => {
// Execute swap logic
}}
/>Source Code
"use client";import { ArrowDownUpIcon } from "lucide-react";import React from "react";import { TokenInput } from "@/registry/sol/token-input";import { Button } from "@/components/ui/button";import type { DetailRow } from "@/registry/lib/types";import { cn } from "@/lib/utils";type SwapBoxDetail = DetailRow;interface SwapBoxProps { tokens: { icon: string; symbol: string }[]; defaultFromToken?: string; defaultToToken?: string; fromBalance?: string; toBalance?: string; fromLabel?: string; toLabel?: string; details?: SwapBoxDetail[]; submitLabel?: string; onSubmit?: () => void; className?: string;}const SwapBox = ({ tokens, defaultFromToken, defaultToToken, fromBalance, toBalance, fromLabel = "Sell", toLabel = "Buy", details, submitLabel = "Swap", onSubmit, className,}: SwapBoxProps) => { const [fromToken, setFromToken] = React.useState(defaultFromToken); const [toToken, setToToken] = React.useState(defaultToToken); const [fromBal, setFromBal] = React.useState(fromBalance); const [toBal, setToBal] = React.useState(toBalance); const handleFlip = () => { const prevFrom = fromToken; const prevTo = toToken; const prevFromBal = fromBal; const prevToBal = toBal; setFromToken(prevTo); setToToken(prevFrom); setFromBal(prevToBal); setToBal(prevFromBal); }; return ( <div className={cn("flex flex-col border rounded-lg p-5", className)}> <div className="w-full flex flex-col gap-1.5"> <span className="text-xs font-medium text-muted-foreground"> {fromLabel} </span> <TokenInput key={`from-${fromToken}`} tokens={tokens} defaultToken={fromToken} balance={fromBal} onTokenSelect={(token) => setFromToken(token.symbol)} /> </div> <div className="flex items-center justify-center pt-4"> <Button variant="outline" size="icon" className="rounded-full size-9 bg-background border-2" onClick={handleFlip} > <ArrowDownUpIcon className="size-4" /> </Button> </div> <div className="w-full flex flex-col gap-1.5"> <span className="text-xs font-medium text-muted-foreground"> {toLabel} </span> <TokenInput key={`to-${toToken}`} tokens={tokens} defaultToken={toToken} balance={toBal} onTokenSelect={(token) => setToToken(token.symbol)} /> </div> <div className="flex flex-col gap-4 pt-4"> {details && details.length > 0 && ( <div className="flex flex-col gap-1.5 text-sm"> {details.map((detail) => ( <div key={detail.label} className="flex justify-between"> <span className="text-muted-foreground">{detail.label}</span> <span className={detail.className}>{detail.value}</span> </div> ))} </div> )} <Button className="w-full" size="lg" onClick={onSubmit}> {submitLabel} </Button> </div> </div> );};export type { SwapBoxProps, SwapBoxDetail };export { SwapBox };