SolanaUISolanaUI

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 };