Order Book
A vertical orderbook displaying bids and asks with cumulative depth
PriceSizeTotal
162.8940K402K
162.8618K362K
162.8345K344K
162.8023K299K
162.7736K276K
162.7448K240K
162.7128K193K
162.6856K164K
162.6520K109K
162.6242K89K
162.5931K47K
162.5716K16K
162.56Spread0.02 (0.012%)
162.5512K12K
162.5328K41K
162.5045K86K
162.4819K105K
162.4563K167K
162.4234K201K
162.4052K253K
162.3822K275K
162.3542K317K
162.3259K376K
162.2915K391K
162.2637K428K
import { OrderBook } from "@/components/sol/order-book";
export function OrderBookDemo() {
const bids = [
{ price: 162.55, size: 12400 },
{ price: 162.53, size: 28100 },
{ price: 162.50, size: 45300 },
{ price: 162.48, size: 18700 },
{ price: 162.45, size: 62500 },
];
const asks = [
{ price: 162.57, size: 15800 },
{ price: 162.59, size: 31200 },
{ price: 162.62, size: 42100 },
{ price: 162.65, size: 19500 },
{ price: 162.68, size: 55700 },
];
return <OrderBook bids={bids} asks={asks} />;
}Installation
pnpm dlx shadcn@latest add @solanaui/order-book
npx shadcn@latest add @solanaui/order-book
yarn dlx shadcn@latest add @solanaui/order-book
Usage
<OrderBook
bids={[
{ price: 162.55, size: 12400 },
{ price: 162.53, size: 28100 },
{ price: 162.50, size: 45300 },
]}
asks={[
{ price: 162.57, size: 15800 },
{ price: 162.59, size: 31200 },
{ price: 162.62, size: 42100 },
]}
/>Source Code
import { cn } from "@/lib/utils";interface OrderBookProps { bids: { price: number; size: number; }[]; asks: { price: number; size: number; }[]; className?: string;}const formatSize = (value: number) => { if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(2)}M`; if (value >= 1_000) return `${(value / 1_000).toFixed(0)}K`; return value.toFixed(0);};const OrderBook = ({ bids, asks, className }: OrderBookProps) => { if (bids.length === 0 && asks.length === 0) return null; const highestBid = bids[0]?.price ?? 0; const lowestAsk = asks[0]?.price ?? 0; const midPrice = bids.length > 0 && asks.length > 0 ? (highestBid + lowestAsk) / 2 : highestBid || lowestAsk; const spread = lowestAsk - highestBid; const spreadPercent = highestBid > 0 ? (spread / highestBid) * 100 : 0; let cumulativeBidSize = 0; const bidTotals = bids.map((bid) => { cumulativeBidSize += bid.size; return cumulativeBidSize; }); let cumulativeAskSize = 0; const askTotals = asks.map((ask) => { cumulativeAskSize += ask.size; return cumulativeAskSize; }); const maxTotal = Math.max( bidTotals[bidTotals.length - 1] || 0, askTotals[askTotals.length - 1] || 0, ); const reversedAsks = [...asks].reverse(); const reversedAskTotals = [...askTotals].reverse(); return ( <div className={cn( "w-full flex flex-col overflow-hidden rounded-lg border bg-card text-card-foreground", className, )} > {/* Column headers */} <div className="grid grid-cols-3 px-3 py-2 text-xs text-muted-foreground border-b"> <span>Price</span> <span className="text-right">Size</span> <span className="text-right">Total</span> </div> {/* Asks (reversed so lowest ask is at bottom, near spread) */} <div className="flex flex-col overflow-auto"> {reversedAsks.map((ask, i) => { const total = reversedAskTotals[i]; const depth = maxTotal > 0 ? (total / maxTotal) * 100 : 0; return ( <div key={`ask-${ask.price}`} className="relative"> <div className="absolute right-0 top-0 bottom-0 bg-red-400/10" style={{ width: `${depth}%` }} /> <div className="relative grid grid-cols-3 px-3 py-1 text-xs"> <span className="text-red-400 font-medium tabular-nums"> {ask.price.toFixed(2)} </span> <span className="text-right text-muted-foreground tabular-nums"> {formatSize(ask.size)} </span> <span className="text-right text-muted-foreground tabular-nums"> {formatSize(total)} </span> </div> </div> ); })} </div> {/* Spread / mid price */} <div className="grid grid-cols-3 px-3 py-2 border-y"> <span className="text-lg font-semibold text-emerald-500 tabular-nums"> {midPrice.toFixed(2)} </span> <span className="text-right text-xs text-muted-foreground self-center"> Spread </span> <span className="text-right text-xs text-muted-foreground self-center tabular-nums"> {spread.toFixed(2)} ({spreadPercent.toFixed(3)}%) </span> </div> {/* Bids */} <div className="flex flex-col overflow-auto"> {bids.map((bid, i) => { const total = bidTotals[i]; const depth = maxTotal > 0 ? (total / maxTotal) * 100 : 0; return ( <div key={`bid-${bid.price}`} className="relative"> <div className="absolute right-0 top-0 bottom-0 bg-emerald-500/10" style={{ width: `${depth}%` }} /> <div className="relative grid grid-cols-3 px-3 py-1 text-xs"> <span className="text-emerald-500 font-medium tabular-nums"> {bid.price.toFixed(2)} </span> <span className="text-right text-muted-foreground tabular-nums"> {formatSize(bid.size)} </span> <span className="text-right text-muted-foreground tabular-nums"> {formatSize(total)} </span> </div> </div> ); })} </div> </div> );};export type { OrderBookProps };export { OrderBook };