Context
A compound component system for displaying AI model context window usage, token consumption, and cost estimation.
The Context component provides a comprehensive view of AI model usage through a compound component system. It displays context window utilization, token consumption breakdown (input, output, reasoning, cache), and cost estimation in an interactive hover card interface.
"use client";import { Context, ContextCacheUsage, ContextContent, ContextContentBody, ContextContentFooter, ContextContentHeader, ContextInputUsage, ContextOutputUsage, ContextReasoningUsage, ContextTrigger,} from "@/components/ai-elements/elements/context";const Example = () => ( <div className="flex items-center justify-center p-8"> <Context maxTokens={128_000} modelId="openai:gpt-5" usage={{ inputTokens: 32_000, outputTokens: 8000, totalTokens: 40_000, cachedInputTokens: 0, reasoningTokens: 0, }} usedTokens={40_000} > <ContextTrigger /> <ContextContent> <ContextContentHeader /> <ContextContentBody> <ContextInputUsage /> <ContextOutputUsage /> <ContextReasoningUsage /> <ContextCacheUsage /> </ContextContentBody> <ContextContentFooter /> </ContextContent> </Context> </div>);export default Example;Installation
npx ai-elements@latest add contextnpx shadcn@latest add @ai-elements/context"use client";import { Button } from "@repo/shadcn-ui/components/ui/button";import { HoverCard, HoverCardContent, HoverCardTrigger,} from "@repo/shadcn-ui/components/ui/hover-card";import { Progress } from "@repo/shadcn-ui/components/ui/progress";import { cn } from "@repo/shadcn-ui/lib/utils";import type { LanguageModelUsage } from "ai";import { type ComponentProps, createContext, useContext } from "react";import { estimateCost, type ModelId } from "tokenlens";const PERCENT_MAX = 100;const ICON_RADIUS = 10;const ICON_VIEWBOX = 24;const ICON_CENTER = 12;const ICON_STROKE_WIDTH = 2;type ContextSchema = { usedTokens: number; maxTokens: number; usage?: LanguageModelUsage; modelId?: ModelId;};const ContextContext = createContext<ContextSchema | null>(null);const useContextValue = () => { const context = useContext(ContextContext); if (!context) { throw new Error("Context components must be used within Context"); } return context;};export type ContextProps = ComponentProps<typeof HoverCard> & ContextSchema;export const Context = ({ usedTokens, maxTokens, usage, modelId, ...props}: ContextProps) => ( <ContextContext.Provider value={{ usedTokens, maxTokens, usage, modelId, }} > <HoverCard closeDelay={0} openDelay={0} {...props} /> </ContextContext.Provider>);const ContextIcon = () => { const { usedTokens, maxTokens } = useContextValue(); const circumference = 2 * Math.PI * ICON_RADIUS; const usedPercent = usedTokens / maxTokens; const dashOffset = circumference * (1 - usedPercent); return ( <svg aria-label="Model context usage" height="20" role="img" style={{ color: "currentcolor" }} viewBox={`0 0 ${ICON_VIEWBOX} ${ICON_VIEWBOX}`} width="20" > <circle cx={ICON_CENTER} cy={ICON_CENTER} fill="none" opacity="0.25" r={ICON_RADIUS} stroke="currentColor" strokeWidth={ICON_STROKE_WIDTH} /> <circle cx={ICON_CENTER} cy={ICON_CENTER} fill="none" opacity="0.7" r={ICON_RADIUS} stroke="currentColor" strokeDasharray={`${circumference} ${circumference}`} strokeDashoffset={dashOffset} strokeLinecap="round" strokeWidth={ICON_STROKE_WIDTH} style={{ transformOrigin: "center", transform: "rotate(-90deg)" }} /> </svg> );};export type ContextTriggerProps = ComponentProps<typeof Button>;export const ContextTrigger = ({ children, ...props }: ContextTriggerProps) => { const { usedTokens, maxTokens } = useContextValue(); const usedPercent = usedTokens / maxTokens; const renderedPercent = new Intl.NumberFormat("en-US", { style: "percent", maximumFractionDigits: 1, }).format(usedPercent); return ( <HoverCardTrigger asChild> {children ?? ( <Button type="button" variant="ghost" {...props}> <span className="font-medium text-muted-foreground"> {renderedPercent} </span> <ContextIcon /> </Button> )} </HoverCardTrigger> );};export type ContextContentProps = ComponentProps<typeof HoverCardContent>;export const ContextContent = ({ className, ...props}: ContextContentProps) => ( <HoverCardContent className={cn("min-w-[240px] divide-y overflow-hidden p-0", className)} {...props} />);export type ContextContentHeader = ComponentProps<"div">;export const ContextContentHeader = ({ children, className, ...props}: ContextContentHeader) => { const { usedTokens, maxTokens } = useContextValue(); const usedPercent = usedTokens / maxTokens; const displayPct = new Intl.NumberFormat("en-US", { style: "percent", maximumFractionDigits: 1, }).format(usedPercent); const used = new Intl.NumberFormat("en-US", { notation: "compact", }).format(usedTokens); const total = new Intl.NumberFormat("en-US", { notation: "compact", }).format(maxTokens); return ( <div className={cn("w-full space-y-2 p-3", className)} {...props}> {children ?? ( <> <div className="flex items-center justify-between gap-3 text-xs"> <p>{displayPct}</p> <p className="font-mono text-muted-foreground"> {used} / {total} </p> </div> <div className="space-y-2"> <Progress className="bg-muted" value={usedPercent * PERCENT_MAX} /> </div> </> )} </div> );};export type ContextContentBody = ComponentProps<"div">;export const ContextContentBody = ({ children, className, ...props}: ContextContentBody) => ( <div className={cn("w-full p-3", className)} {...props}> {children} </div>);export type ContextContentFooter = ComponentProps<"div">;export const ContextContentFooter = ({ children, className, ...props}: ContextContentFooter) => { const { modelId, usage } = useContextValue(); const costUSD = modelId ? estimateCost({ modelId, usage: { input: usage?.inputTokens ?? 0, output: usage?.outputTokens ?? 0, }, }).totalUSD : undefined; const totalCost = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(costUSD ?? 0); return ( <div className={cn( "flex w-full items-center justify-between gap-3 bg-secondary p-3 text-xs", className )} {...props} > {children ?? ( <> <span className="text-muted-foreground">Total cost</span> <span>{totalCost}</span> </> )} </div> );};export type ContextInputUsageProps = ComponentProps<"div">;export const ContextInputUsage = ({ className, children, ...props}: ContextInputUsageProps) => { const { usage, modelId } = useContextValue(); const inputTokens = usage?.inputTokens ?? 0; if (children) { return children; } if (!inputTokens) { return null; } const inputCost = modelId ? estimateCost({ modelId, usage: { input: inputTokens, output: 0 }, }).totalUSD : undefined; const inputCostText = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(inputCost ?? 0); return ( <div className={cn("flex items-center justify-between text-xs", className)} {...props} > <span className="text-muted-foreground">Input</span> <TokensWithCost costText={inputCostText} tokens={inputTokens} /> </div> );};export type ContextOutputUsageProps = ComponentProps<"div">;export const ContextOutputUsage = ({ className, children, ...props}: ContextOutputUsageProps) => { const { usage, modelId } = useContextValue(); const outputTokens = usage?.outputTokens ?? 0; if (children) { return children; } if (!outputTokens) { return null; } const outputCost = modelId ? estimateCost({ modelId, usage: { input: 0, output: outputTokens }, }).totalUSD : undefined; const outputCostText = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(outputCost ?? 0); return ( <div className={cn("flex items-center justify-between text-xs", className)} {...props} > <span className="text-muted-foreground">Output</span> <TokensWithCost costText={outputCostText} tokens={outputTokens} /> </div> );};export type ContextReasoningUsageProps = ComponentProps<"div">;export const ContextReasoningUsage = ({ className, children, ...props}: ContextReasoningUsageProps) => { const { usage, modelId } = useContextValue(); const reasoningTokens = usage?.reasoningTokens ?? 0; if (children) { return children; } if (!reasoningTokens) { return null; } const reasoningCost = modelId ? estimateCost({ modelId, usage: { reasoningTokens }, }).totalUSD : undefined; const reasoningCostText = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(reasoningCost ?? 0); return ( <div className={cn("flex items-center justify-between text-xs", className)} {...props} > <span className="text-muted-foreground">Reasoning</span> <TokensWithCost costText={reasoningCostText} tokens={reasoningTokens} /> </div> );};export type ContextCacheUsageProps = ComponentProps<"div">;export const ContextCacheUsage = ({ className, children, ...props}: ContextCacheUsageProps) => { const { usage, modelId } = useContextValue(); const cacheTokens = usage?.cachedInputTokens ?? 0; if (children) { return children; } if (!cacheTokens) { return null; } const cacheCost = modelId ? estimateCost({ modelId, usage: { cacheReads: cacheTokens, input: 0, output: 0 }, }).totalUSD : undefined; const cacheCostText = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(cacheCost ?? 0); return ( <div className={cn("flex items-center justify-between text-xs", className)} {...props} > <span className="text-muted-foreground">Cache</span> <TokensWithCost costText={cacheCostText} tokens={cacheTokens} /> </div> );};const TokensWithCost = ({ tokens, costText,}: { tokens?: number; costText?: string;}) => ( <span> {tokens === undefined ? "—" : new Intl.NumberFormat("en-US", { notation: "compact", }).format(tokens)} {costText ? ( <span className="ml-2 text-muted-foreground">• {costText}</span> ) : null} </span>);Usage
import {
Context,
ContextTrigger,
ContextContent,
ContextContentHeader,
ContextContentBody,
ContextContentFooter,
ContextInputUsage,
ContextOutputUsage,
ContextReasoningUsage,
ContextCacheUsage,
} from '@/components/ai-elements/context';<Context
maxTokens={128000}
usedTokens={40000}
usage={{
inputTokens: 32000,
outputTokens: 8000,
totalTokens: 40000,
cachedInputTokens: 0,
reasoningTokens: 0,
}}
modelId="openai:gpt-4"
>
<ContextTrigger />
<ContextContent>
<ContextContentHeader />
<ContextContentBody>
<ContextInputUsage />
<ContextOutputUsage />
<ContextReasoningUsage />
<ContextCacheUsage />
</ContextContentBody>
<ContextContentFooter />
</ContextContent>
</Context>Features
- Compound Component Architecture: Flexible composition of context display elements
- Visual Progress Indicator: Circular SVG progress ring showing context usage percentage
- Token Breakdown: Detailed view of input, output, reasoning, and cached tokens
- Cost Estimation: Real-time cost calculation using the
tokenlenslibrary - Intelligent Formatting: Automatic token count formatting (K, M, B suffixes)
- Interactive Hover Card: Detailed information revealed on hover
- Context Provider Pattern: Clean data flow through React Context API
- TypeScript Support: Full type definitions for all components
- Accessible Design: Proper ARIA labels and semantic HTML
- Theme Integration: Uses currentColor for automatic theme adaptation
Props
<Context />
Prop
Type
<ContextTrigger />
Prop
Type
<ContextContent />
Prop
Type
<ContextContentHeader />
Prop
Type
<ContextContentBody />
Prop
Type
<ContextContentFooter />
Prop
Type
Usage Components
All usage components (ContextInputUsage, ContextOutputUsage, ContextReasoningUsage, ContextCacheUsage) share the same props:
Prop
Type
Component Architecture
The Context component uses a compound component pattern with React Context for data sharing:
<Context>- Root provider component that holds all context data<ContextTrigger>- Interactive trigger element (default: button with percentage)<ContextContent>- Hover card content container<ContextContentHeader>- Header section with progress visualization<ContextContentBody>- Body section for usage breakdowns<ContextContentFooter>- Footer section for total cost- Usage Components - Individual token usage displays (Input, Output, Reasoning, Cache)
Token Formatting
The component uses Intl.NumberFormat with compact notation for automatic formatting:
- Under 1,000: Shows exact count (e.g., "842")
- 1,000+: Shows with K suffix (e.g., "32K")
- 1,000,000+: Shows with M suffix (e.g., "1.5M")
- 1,000,000,000+: Shows with B suffix (e.g., "2.1B")
Cost Calculation
When a modelId is provided, the component automatically calculates costs using the tokenlens library:
- Input tokens: Cost based on model's input pricing
- Output tokens: Cost based on model's output pricing
- Reasoning tokens: Special pricing for reasoning-capable models
- Cached tokens: Reduced pricing for cached input tokens
- Total cost: Sum of all token type costs
Costs are formatted using Intl.NumberFormat with USD currency.
Styling
The component uses Tailwind CSS classes and follows your design system:
- Progress indicator uses
currentColorfor theme adaptation - Hover card has customizable width and padding
- Footer has a secondary background for visual separation
- All text sizes use the
text-xsclass for consistency - Muted foreground colors for secondary information