AI Chat

Conversational AI interface for exploring and building with the design system.

Interactive Demo

Try sending a message to see the chat interface in action.

AI Assistant

How can I help?

Ask a question or pick a prompt to get started.

Sample Conversation

AI Assistant

What's the primary color in the Grade theme?

The Grade theme uses teal as the primary color:

  • Light mode: hsl(175 84% 32%) — deep teal
  • Dark mode: hsl(175 80% 45%) — brighter teal

You can reference it in components with the --primary CSS variable or Tailwind's text-primary / bg-primary utilities. It's also used for focus rings, links, and the default button variant.

How do I make a button pill-shaped across the whole site?

Two options:

  1. Set it per-theme — Add buttonShape: "pill" to your theme's components object. The Paper preset already does this.
  2. Override on one button — Pass className="rounded-full" to <Button>.

The theme-level approach is preferred because it keeps the shape consistent across every <Button> without touching call sites. The GradeThemeProvider sets data-button-shape on the root, and globals.css reads it to override the rds-button class's radius.

Custom Suggested Prompts

AI Assistant

How can I help?

Ask a question or pick a prompt to get started.

Features

  • Markdown rendering for AI responses (bold, lists, code blocks)
  • Animated message bubbles with spring physics
  • Auto-scrolling with scroll-up detection
  • Thinking indicator with animated dots
  • Customizable suggested prompts (text-only chips in the icon-light refresh)
  • Auto-resizing textarea (up to 200px)
  • Keyboard shortcuts (Enter to send, Shift+Enter for newline)
  • Loading state with cancel button
  • Empty state with welcome message
  • Dark mode support

Usage

import { AIChat, type ChatMessage } from "@/components/ui/ai-chat"

const [messages, setMessages] = useState<ChatMessage[]>([])
const [isLoading, setIsLoading] = useState(false)

const handleSendMessage = async (message: string) => {
  // Add user message
  setMessages(prev => [...prev, {
    id: Date.now().toString(),
    role: "user",
    content: message,
    timestamp: new Date(),
  }])

  // Call your AI backend
  setIsLoading(true)
  const response = await fetch("/api/ai/chat", {
    method: "POST",
    body: JSON.stringify({ message }),
  })
  const data = await response.json()

  // Add AI response
  setMessages(prev => [...prev, {
    id: (Date.now() + 1).toString(),
    role: "assistant",
    content: data.response,
    timestamp: new Date(),
  }])
  setIsLoading(false)
}

<AIChat
  messages={messages}
  onSendMessage={handleSendMessage}
  isLoading={isLoading}
  placeholder="Ask about components, tokens, or themes..."
  suggestedPrompts={[
    { text: "Color tokens" },
    { text: "Form components" },
  ]}
/>

ChatMessage Type

interface ChatMessage {
  id: string
  role: "user" | "assistant"
  content: string         // Supports Markdown
  timestamp: Date
}

Dependencies

npm install framer-motion react-markdown remark-gfm

Sidecar

The Markdown sidecar Studio (and the Grade MCP server, when it ships) reads to understand this component — frontmatter, when- to-use guidance, and canonical examples. Authored once at packages/ui/components/ui/ai-chat.md and shipped inside the published @gradeui/ui tarball.

---
name: AIChat
import: "@gradeui/ui"
props:
  - messages?: ChatMessage[] — `{ id, role: "user" | "assistant", content, timestamp }`; defaults to empty
  - onSendMessage?: (message: string) => void — fires when the user submits a query
  - isLoading?: boolean — shows a typing indicator on the last assistant turn
  - placeholder?: string — input placeholder text
  - suggestedPrompts?: { icon?: React.ReactNode; text: string }[] — empty-state quick prompts
  - className?: string
when_to_use: A pre-built chat block — paste it in to get a working LLM chat surface without composing the message list, autoscroll, suggested prompts, and submit input yourself. Reach for it as the "AI panel" in an admin/support tool, or to demo an LLM-driven feature inside a marketing page. For Studio-grade chat with file refs and streaming structured output, you'll outgrow this and want a custom composition built on Textarea + Card + ScrollArea.
composes_with: [Card (host in a sidebar panel), Sheet (mobile drawer), Stack (place above other content)]
aliases: [ai chat, chat panel, chat block, llm chat, assistant panel, copilot chat]
---

```jsx
const [messages, setMessages] = useState([]);
const [loading, setLoading] = useState(false);

<AIChat
  messages={messages}
  isLoading={loading}
  onSendMessage={async (text) => {
    setMessages((m) => [...m, { id: uid(), role: "user", content: text, timestamp: new Date() }]);
    setLoading(true);
    const reply = await fetchAssistant(text);
    setMessages((m) => [...m, { id: uid(), role: "assistant", content: reply, timestamp: new Date() }]);
    setLoading(false);
  }}
  suggestedPrompts={[
    { icon: <Sparkles />, text: "Summarise this page" },
    { icon: <Lightbulb />, text: "Suggest next steps" },
  ]}
/>
```