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.
How can I help?
Ask a question or pick a prompt to get started.
Sample Conversation
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:
- Set it per-theme — Add
buttonShape: "pill"to your theme'scomponentsobject. ThePaperpreset already does this. - 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
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-gfmSidecar
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" },
]}
/>
```