Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
bjornmelin avatar

Ai Sdk Ui

  • 9 installs
  • 5 repo stars
  • Updated August 5, 2026
  • bjornmelin/dev-skills

Ai-sdk-ui is a Claude Code skill giving expert guidance for building chat and generative UIs with AI SDK React hooks like useChat, useObject, and useCompletion.

About

Ai-sdk-ui is a Claude Code skill for building chat and generative UIs with the AI SDK React hooks. It provides a hook-selection guide (useChat for conversations, useObject for structured data, useCompletion for single-turn text) and patterns for status handling, error and retry, message editing, file attachments, custom request options, and message metadata. Developers use it to build streaming chatbots and tool UIs wired to Next.js, Node, Fastify, or Nest backends.

  • Guidance for building chat and generative UIs with AI SDK React hooks
  • Covers useChat, useObject, useCompletion with a hook-selection decision tree
  • Handles tool UIs, message persistence, streaming, and file attachments

Ai Sdk Ui by the numbers

  • 9 all-time installs (skills.sh)
  • Ranked #1,714 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

ai-sdk-ui capabilities & compatibility

Free skill; the chat UI it builds calls an LLM provider that requires an API key.

Capabilities
frontend · api development
Works with
vercel · openai
Use cases
frontend · ui design · api development
Pricing
Bring your own API key
From the docs

What ai-sdk-ui says it does

AI SDK UI provides framework-agnostic hooks for building interactive chat, completion, and assistant applications with real-time streaming and state management.
SKILL.md
Need conversation history + tools? → `useChat`
SKILL.md
npx skills add https://github.com/bjornmelin/dev-skills --skill ai-sdk-ui

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs9
repo stars5
Last updatedAugust 5, 2026
Repositorybjornmelin/dev-skills

What it does

Build a streaming chat or generative UI with AI SDK React hooks (useChat, useObject, useCompletion) wired to a backend route.

Who is it for?

Building streaming chatbots and generative UIs with AI SDK React hooks

Skip if: Server-side LLM calls and tool wiring, which the AI SDK Core skill covers

When should I use this skill?

Building chatbots with useChat, tool UIs, message persistence, generative UI, or useObject streaming

What you get

A working chat or generative UI with correct hook selection, streaming, tool parts, and error handling.

  • chat and generative UI React components
  • streaming API routes

By the numbers

  • 3-hook selection guide (useChat/useObject/useCompletion)
  • 4 status values: submitted, streaming, ready, error

Files

SKILL.mdMarkdownGitHub ↗

AI SDK UI - Chat & Generative UI Framework

AI SDK UI provides framework-agnostic hooks for building interactive chat, completion, and assistant applications with real-time streaming and state management.

Quick Start: Basic Chat

Client (React)

'use client';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';

export default function Chat() {
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({ api: '/api/chat' }),
  });
  const [input, setInput] = useState('');

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.role === 'user' ? 'User: ' : 'AI: '}
          {message.parts.map((part, index) =>
            part.type === 'text' ? <span key={index}>{part.text}</span> : null
          )}
        </div>
      ))}

      <form onSubmit={e => {
        e.preventDefault();
        if (input.trim()) {
          sendMessage({ text: input });
          setInput('');
        }
      }}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          disabled={status !== 'ready'}
        />
        <button type="submit" disabled={status !== 'ready'}>
          Submit
        </button>
      </form>
    </>
  );
}

Server (Next.js App Router)

import { convertToModelMessages, streamText, UIMessage } from 'ai';
import { openai } from '@ai-sdk/openai';

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'),
    system: 'You are a helpful assistant.',
    messages: await convertToModelMessages(messages), // v6: now async
  });

  return result.toUIMessageStreamResponse();
}

Hook Selection Guide

HookUse CaseStream TypeBest For
useChatMulti-turn conversationsMessages with parts (text, tools, files)Chatbots, assistants, tool-calling UIs
useObjectStructured data streamingTyped objects with Zod schemaForms, dashboards, real-time data
useCompletionSingle-turn text generationPlain textAutocomplete, simple generation

Decision tree:

  • Need conversation history + tools? → useChat
  • Need typed/structured streaming data? → useObject
  • Need simple text completion? → useCompletion

Core Patterns

1. Status Management

const { status, stop } = useChat();

// status values: 'submitted' | 'streaming' | 'ready' | 'error'

{(status === 'submitted' || status === 'streaming') && (
  <div>
    {status === 'submitted' && <Spinner />}
    <button onClick={() => stop()}>Stop</button>
  </div>
)}

2. Error Handling

const { error, reload } = useChat();

{error && (
  <>
    <div>An error occurred.</div>
    <button onClick={() => reload()}>Retry</button>
  </>
)}

Server-side error messages:

return result.toUIMessageStreamResponse({
  onError: error => {
    if (error instanceof Error) return error.message;
    return 'Unknown error';
  },
});

3. Message Modification

const { messages, setMessages } = useChat();

const handleDelete = (id: string) => {
  setMessages(messages.filter(m => m.id !== id));
};

const handleEdit = (id: string, newText: string) => {
  setMessages(messages.map(m =>
    m.id === id
      ? { ...m, parts: [{ type: 'text', text: newText }] }
      : m
  ));
};

4. File Attachments

const [files, setFiles] = useState<FileList | undefined>();

<form onSubmit={e => {
  e.preventDefault();
  sendMessage({ text: input, files });
  setFiles(undefined);
}}>
  <input
    type="file"
    onChange={e => setFiles(e.target.files ?? undefined)}
    multiple
  />
</form>

5. Custom Request Options

// Per-request customization (recommended)
sendMessage(
  { text: input },
  {
    headers: { Authorization: 'Bearer token' },
    body: { temperature: 0.7, user_id: '123' },
    metadata: { sessionId: 'abc' },
  }
);

// Hook-level configuration
const { sendMessage } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/chat',
    headers: () => ({ Authorization: `Bearer ${getToken()}` }),
    body: { systemContext: 'expert' },
  }),
});

6. Message Metadata

// Server: Attach metadata
return result.toUIMessageStreamResponse({
  messageMetadata: ({ part }) => {
    if (part.type === 'start') {
      return { createdAt: Date.now(), model: 'gpt-4o' };
    }
    if (part.type === 'finish') {
      return { totalTokens: part.totalUsage.totalTokens };
    }
  },
});
// Client: Access metadata
{messages.map(m => (
  <div key={m.id}>
    {m.metadata?.createdAt && new Date(m.metadata.createdAt).toLocaleString()}
    {m.parts.map(part => part.type === 'text' ? part.text : null)}
    {m.metadata?.totalTokens && <span>{m.metadata.totalTokens} tokens</span>}
  </div>
))}

7. Regenerate & Stop

const { regenerate, stop, status } = useChat();

<>
  <button onClick={stop} disabled={status !== 'streaming'}>
    Stop
  </button>
  <button onClick={regenerate} disabled={!(status === 'ready' || status === 'error')}>
    Regenerate
  </button>
</>

Message Parts Type Reference

Messages use a parts array instead of content for flexible multi-modal rendering:

type MessagePart =
  | { type: 'text'; text: string }
  | { type: 'file'; filename: string; mediaType: string; url: string }
  | { type: 'tool-invocation'; toolName: string; input: unknown; result?: unknown }
  | { type: 'tool-result'; toolName: string; result: unknown }
  | { type: 'reasoning'; text: string }  // DeepSeek R1, Claude 3.7 Sonnet
  | { type: 'source-url'; id: string; url: string; title?: string }  // Perplexity, Google
  | { type: 'source-document'; id: string; title?: string };

// Render pattern
{message.parts.map((part, index) => {
  switch (part.type) {
    case 'text':
      return <span key={index}>{part.text}</span>;
    case 'file':
      return part.mediaType.startsWith('image/')
        ? <img key={index} src={part.url} alt={part.filename} />
        : null;
    case 'reasoning':
      return <pre key={index}>{part.text}</pre>;
    case 'source-url':
      return <a key={index} href={part.url}>{part.title ?? 'Source'}</a>;
    case 'tool-invocation':
      return <ToolUI key={index} tool={part} />;
    default:
      return null;
  }
})}

Framework Support

FrameworkPackageHooks
React@ai-sdk/reactuseChat, useCompletion, useObject
Vue.js@ai-sdk/vueuseChat, useCompletion, useObject
Svelte@ai-sdk/svelteChat, Completion, StructuredObject
Angular@ai-sdk/angularChat, Completion, StructuredObject
SolidJSai-sdk-solid (community)useChat, useCompletion, useObject

AI Elements (shadcn/ui Components)

Pre-built UI components for chat interfaces: https://ai-sdk.dev/elements

Includes: Message bubbles, input fields, tool UIs, and more.

Reference Navigation

ReferenceTopics
[usechat-fundamentals.md](./usechat-fundamentals.md)Hook API, transport config, status lifecycle, message state
[tool-integration.md](./tool-integration.md)Tool calling, client/server execution, tool approval, type inference
[generative-ui.md](./generative-ui.md)React components in streams, dynamic UIs, RSC integration
[persistence.md](./persistence.md)Message storage, resume streams, optimistic updates, sync patterns
[hooks-reference.md](./hooks-reference.md)Complete API for useChat/useObject/useCompletion, options reference
[backend.md](./backend.md)Next.js/Node/Fastify/Nest routes, convertToModelMessages, toUIMessageStreamResponse
[production.md](./production.md)Error boundaries, retry strategies, throttling, security best practices
[migration.md](./migration.md)v6 migration guide, breaking changes, codemod usage

Event Callbacks

const { messages } = useChat({
  onFinish: ({ message, messages, isAbort, isDisconnect, isError }) => {
    // Log completion, update analytics, trigger side effects
    if (!isError) logMessage(message);
  },
  onError: error => {
    // Custom error handling, fallback UI
    Sentry.captureException(error);
  },
  onData: data => {
    // Process data parts, validate responses
    // Throw error to abort processing
  },
});

Advanced: Custom Transport

const { sendMessage } = useChat({
  transport: new DefaultChatTransport({
    prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => {
      if (trigger === 'submit-user-message') {
        return {
          body: {
            id,
            message: messages[messages.length - 1],
            messageId,
          },
        };
      }
      // Handle regenerate, custom triggers
    },
  }),
});

Type Inference for Tools

import { InferUITools, InferAgentUIMessage, ToolSet, UIMessage } from 'ai';

const tools = {
  weather: {
    description: 'Get weather',
    inputSchema: z.object({ location: z.string() }),
    execute: async ({ location }) => `Sunny in ${location}`,
  },
} satisfies ToolSet;

type MyUITools = InferUITools<typeof tools>;
type MyUIMessage = UIMessage<never, never, MyUITools>;
// Or for agent messages:
// type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent>;

const { messages } = useChat<MyUIMessage>();

Reasoning & Sources

// Enable reasoning (DeepSeek R1, Claude 3.7 Sonnet)
return result.toUIMessageStreamResponse({ sendReasoning: true });

// Enable sources (Perplexity, Google)
return result.toUIMessageStreamResponse({ sendSources: true });
// Render reasoning and sources
{message.parts.map(part => {
  if (part.type === 'reasoning') return <pre>{part.text}</pre>;
  if (part.type === 'source-url') return <a href={part.url}>{part.title}</a>;
})}

Performance: Throttle Updates

const { messages } = useChat({
  experimental_throttle: 50, // React only: throttle to 50ms
});

Plain Text Streams

import { TextStreamChatTransport } from 'ai';

const { messages } = useChat({
  transport: new TextStreamChatTransport({ api: '/api/chat' }),
});

Note: Tools, usage, and finish reasons unavailable with TextStreamChatTransport.

Related skills

FAQ

Which AI SDK UI hook should I use?

Use useChat for multi-turn conversations with tools, useObject for typed/structured streaming data, and useCompletion for single-turn text generation.

What backends does it integrate with?

It integrates with Next.js, Node, Fastify, and Nest backends, and the server example uses streamText with toUIMessageStreamResponse in a Next.js App Router route.

Frontend Developmentfrontendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.