Skip to content

Case Study

Building with
Claude API
+ Nodus Design System

This case study covers two things: the Agent Chat Interface reference implementation, and the Anthropic SDK integration pattern with the four Nodus DS components that map to the layers above the API. The reference page is a standalone demonstration. It replays a scripted thread, makes no model call and does not import those components. Three of the four ship in the package; ModelSelector is in the repo but is not exported from the package root.

Claude APIAnthropic SDKStreamingAccessibilitySprint 26
0Model calls made by the reference page
6Scripted messages in the reference thread
3Inline spec card types it renders
4Nodus DS components in the pattern

Reference page

What the reference page does.

It replays a hard-coded thread of six messages on timers and shows a typing indicator before each agent reply. Three of the agent replies carry an inline spec card: an MIT card, an action item and an approval card whose Approve and Deny buttons update local state. Text typed into the input gets one fixed reply after 1.4 seconds. The claude-sonnet-4-6 label in its header is text; nothing calls a model.

Integration pattern (illustrative)

Five layers. One rendering pipeline.

This is how the components are designed to sit in a Claude API integration, from API call to rendered UI. The reference page does not implement it, and nothing in this repo wires all five layers together.

Claude APIAnthropic SDKModel provider

Streams a reply through the Anthropic SDK. In this repo, app/api/sandbox/route.ts streams text deltas with client.messages.stream on claude-haiku-4-5-20251001, but only under `npm run dev` with NODE_ENV=development — a deployed build answers 503 and never imports the SDK. app/api/copilotkit/route.ts no longer calls the Claude API directly at all: in development it spawns the developer's own `claude` CLI login, with no fixed model name; a deployed build answers 503 there too. No route here defines tools, so tool use is part of the API but not exercised by this repo.

StreamingArtifactNodus DSOutput renderer

Renders the content string you pass it, with a state of streaming, complete or error. Kinds are code, markdown and text, with an optional diff view and copy button. While streaming it sets aria-live=polite. Your code feeds it from the stream.

FunctionCallVisualizerNodus DSTool call display

Shows one tool call: its name, input and output as JSON blocks, a status of pending, running, done or error, and an optional duration. It is collapsible, and starts closed when defaultCollapsed is set.

ContextWindowMeterNodus DSToken budget

Takes segments (label and token count) and a limit, and draws a segmented bar with role=meter. By default it warns at 80% of the limit and flags critical at 95% (warnAt and criticalAt props). You read the counts from the API's usage field and pass them in.

ModelSelectorRepo sourceModel configuration

Takes a list of options (id, name, context window, capabilities, cost tier and latency tier) and renders a dropdown or a comparison panel. The list is yours to supply. Source is components/ai/ModelSelector.tsx; it is not exported from the package root.

Implementation (illustrative)

Three steps.

01

Install the SDK

# Nodus is in private preview — distribution details TBD
# Install the Anthropic SDK separately:
npm install @anthropic-ai/sdk
02

Create the streaming client

// Illustrative. app/api/sandbox/route.ts is the real route in this repo,
// gated to NODE_ENV=development — a deployed build answers 503 instead.
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

export function createStream(messages: Anthropic.MessageParam[]) {
  return client.messages.stream({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 1200,
    messages,
    // tools: [...] would go here for tool use
  });
}
03

Pass stream state into the components

// Illustrative. text, state, call and segments come from your own
// handling of the stream; the components do not read it themselves.
import {
  StreamingArtifact,
  FunctionCallVisualizer,
  ContextWindowMeter,
} from "@danagraph/design-system";

export function AgentChat({ text, state, call, segments }) {
  return (
    <div>
      <ContextWindowMeter segments={segments} limit={200000} />
      <StreamingArtifact content={text} state={state} />
      {call && (
        <FunctionCallVisualizer
          name={call.name}
          input={call.input}
          status={call.status}
          output={call.output}
        />
      )}
    </div>
  );
}

Token Economics

Context window in context.

ContextWindowMeter draws the segments and limit you pass it. The numbers below are sample values, not measurements from a session: 24,382 of 200,000 tokens, 12.2%.

CONTEXT WINDOW24.4k / 200.0k
system8,192user6,440tools6,144assistant3,606REMAINING175,618

Accessibility

Contrast computed. Behavior cited.

Contrast ratios are computed from the token values the Agent Chat page renders, in light and dark, with the WCAG relative-luminance formula. The two pairs that sit on a primary color have a second row for wide-gamut (Display P3) displays, where the light theme uses richer primaries; the neutral pairs are the same on both. tests/case-study-contrast.test.ts recomputes every figure from tokens/bauhaus.css and fails if this page drifts. The other rows name the file that implements the behavior, and the last one is a gap.

Contrast — primary text on raised surface (agent bubble)

sRGB, light / dark. WCAG AA requires 4.5:1

17.72 / 14.79PASS

Contrast — secondary text on raised surface

sRGB, light / dark. WCAG AA requires 4.5:1

7.73 / 8.41PASS

Contrast — inverse text on temporal (user bubble)

sRGB, light / dark. WCAG AA requires 4.5:1

7.29 / 6.28PASS

Contrast — inverse text on agency (Approve button)

sRGB, light / dark. WCAG AA requires 4.5:1

5.21 / 6.50PASS

Contrast — inverse text on temporal, wide-gamut display (user bubble)

Display P3, light / dark, read in Chromium with color-gamut: p3 emulated. The wide-gamut primaries in tokens/bauhaus.css apply to the light theme only, so dark is the sRGB figure. WCAG AA requires 4.5:1

7.75 / 6.28PASS

Contrast — inverse text on agency, wide-gamut display (Approve button)

Display P3, light / dark, read in Chromium with color-gamut: p3 emulated. The wide-gamut primaries in tokens/bauhaus.css apply to the light theme only, so dark is the sRGB figure. WCAG AA requires 4.5:1

5.51 / 6.50PASS

Streaming announcements

StreamingArtifact sets aria-live=polite and aria-busy while streaming. The reference page has no live region.

politeIN CODE

Keyboard operation

The reference page uses a native input and buttons, and Enter sends. It adds no Arrow or Escape handling.

NativeIN CODE

Reduced motion

A global rule in app/globals.css shortens every animation and transition under prefers-reduced-motion.

0.01msIN CODE

Focus indicator

The global :focus-visible ring is a 2px outline with 2px offset. The reference page's text input sets outline: none inline, so it shows no ring.

InputGAP

Awards Submission

Exhibit A for the agent era.

This case study is submitted as the primary exhibit for the Awwwards SOTD and CSS Design Awards nominations. The Agent Chat Interface reference implementation shows the rendered-UI end of that integration: a scripted thread with inline spec cards, with no API call behind it. It is a standalone demonstration page; three of the four components described here ship in the package.

StreamingArtifact, ContextWindowMeter, and FunctionCallVisualizer are components for streamed model output, token budgets and tool calls.

Awards submission →