OpenUI Forge — Anthropic
SkillAI & modelsOpenUI generative UI with Anthropic Claude SDK backend. Stream conversion to OpenAI NDJSON format.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the OpenUI Forge — Anthropic skill
What this skill tells your AI
The instructions your AI receives, as published by othmanadi/openui-forge in skills/openui-forge-anthropic/SKILL.md and read by ahel’s review.
Build generative UI apps with OpenUI + Anthropic Claude. Converts Anthropic streaming events to OpenAI-compatible NDJSON.
Activation Triggers
- "openui anthropic", "openui claude", "openui sonnet"
- "generative ui claude", "claude streaming ui"
Prerequisites
- Node.js >= 22 (24 LTS recommended), React >= 18.3.1 (19+ recommended)
ANTHROPIC_API_KEYenvironment variable set- Next.js project (App Router recommended)
Quick Start
- Install dependencies:
npm install @openuidev/react-ui @openuidev/react-headless @openuidev/react-lang lucide-react zod @anthropic-ai/sdk
- Add the CSS import to
app/layout.tsx:
import "@openuidev/react-ui/components.css";
- Create the API route and frontend page below
- Run
npm run devand test
Full Code
Backend: app/api/chat/route.ts
The backend streams from Anthropic and converts each event into OpenAI-compatible SSE chunks that openAIAdapter() expects (data: {json}\n\n lines, terminated by data: [DONE]).
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
export async function POST(req: Request) {
const { messages } = await req.json();
const systemPrompt = openuiChatLibrary.prompt({
preamble: "You are a helpful assistant that generates interactive UIs.",
additionalRules: ["Always use Stack as root when combining multiple components."],
});
// ANTHROPIC_MODEL alternatives: claude-opus-4-8, claude-haiku-4-5, claude-fable-5
const stream = client.messages.stream({
model: process.env.ANTHROPIC_MODEL ?? "claude-sonnet-4-6",
max_tokens: 4096,
system: systemPrompt,
messages,
});
const encoder = new TextEncoder();
const readableStream = new ReadableStream({
async start(controller) {
const id = `chatcmpl-${Date.now()}`;
for await (const event of stream) {
if (
event.type === "content_block_delta" &&
event.delta.type === "text_delta"
) {
const chunk = {
id,
object: "chat.completion.chunk",
choices: [
{
index: 0,
delta: { content: event.delta.text },
finish_reason: null,
},
],
};
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`)
);
}
}
const done = {
id,
object: "chat.completion.chunk",
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
};
controller.enqueue(encoder.encode(`data: ${JSON.stringify(done)}\n\n`));
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
},
});
return new Response(readableStream, {
headers: { "Content-Type": "text/event-stream" },
});
}
Frontend: app/chat/page.tsx
"use client";
import { FullScreen } from "@openuidev/react-ui";
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import {
openAIAdapter,
openAIMessageFormat,
} from "@openuidev/react-headless";
export default function ChatPage() {
return (
<FullScreen
componentLibrary={openuiChatLibrary}
streamProtocol={openAIAdapter()}
messageFormat={openAIMessageFormat}
apiUrl="/api/chat"
/>
);
}
The backend emits SSE (
data: {json}\n\n). Pair it withopenAIAdapter()on the frontend —openAIReadableStreamAdapter()is for NDJSON (nodata:prefix) and will silently produce no output here.
Component Creation
import { defineComponent } from "@openuidev/react-lang";
import { z } from "zod";
export const StatusCard = defineComponent({
name: "StatusCard",
description: "Displays a status with label and color indicator",
props: z.object({
label: z.string().describe("Status label text"),
status: z.enum(["ok", "warning", "error"]).describe("Current status level"),
}),
component: ({ props }) => {
const colors = { ok: "#22c55e", warning: "#eab308", error: "#ef4444" };
return (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span style={{ width: 10, height: 10, borderRadius: "50%", background: colors[props.status] }} />
<span>{props.label}</span>
</div>
);
},
});
System Prompt Generation
npx @openuidev/cli generate ./src/lib/library.ts --out src/generated/system-prompt.txt
Or at runtime: openuiChatLibrary.prompt({ preamble: "...", additionalRules: [...] }).
Validation Checklist
-
ANTHROPIC_API_KEYis set in.env.local - CSS import present in root layout
- Backend converts Anthropic
content_block_deltaevents to OpenAI-compatible SSE chunks - Final chunk has
finish_reason: "stop"and ends withdata: [DONE] - Frontend uses
streamProtocol={openAIAdapter()}andopenAIMessageFormat -
componentLibrary={openuiChatLibrary}prop passed toFullScreen
Error Patterns
| Error | Cause | Fix |
|---|---|---|
| 401 from Anthropic | Missing or invalid API key | Set ANTHROPIC_API_KEY in .env.local |
| Stream hangs | Missing [DONE] sentinel or controller.close() | Ensure final chunk and [DONE] are sent |
| Garbled output | Not wrapping in data: ... SSE format | Each chunk must be data: {json}\n\n |
| Components render as text | Library not passed to FullScreen | Add componentLibrary={openuiChatLibrary} prop |
| Nothing renders, no error | Used openAIReadableStreamAdapter() (NDJSON) on SSE stream, or adapter= prop (silently ignored) | Use streamProtocol={openAIAdapter()} |
max_tokens required | Anthropic API requires explicit max_tokens | Always set max_tokens (e.g., 4096) |
Signals
- GitHub stars
- 22
- Last commit
- Aug 2026
ahel review
S4info
community integration — published by othmanadi, not anthropic
Automated review, not a security audit. Ruleset v1.
Advanced
- Catalog kind
- skill
- Gateway key
openui-forge-anthropic- Source
- github.com/othmanadi/openui-forge