Build a Chat Interface with React and Tailwind CSS v4
Assemble an accessible chat UI with message bubbles, input area, typing indicators, and streaming support — using Ninna UI components and Tailwind v4.
Why Ninna UI for chat UIs
Chat interfaces need accessible message lists, keyboard-friendly input areas, and responsive layout. Ninna UI's primitives (Avatar, Text, Input, IconButton) and layout components (VStack, HStack) give you the building blocks, while the data-slot CSS API lets you customize message bubbles without source ownership.
1. Message list with avatars
Use HStack for each message row, with an Avatar and a styled message bubble:
import { Avatar, Text } from "@ninna-ui/primitives";
import { HStack } from "@ninna-ui/layout";
function Message({ role, content }: { role: "user" | "assistant"; content: string }) {
const isUser = role === "user";
return (
<HStack gap="3" className={isUser ? "flex-row-reverse" : ""}>
<Avatar name={isUser ? "You" : "AI"} size="sm" />
<div className={`rounded-2xl p-3 max-w-[80%] ${isUser ? "bg-primary text-primary-content" : "bg-base-200"}`}>
<Text size="sm">{content}</Text>
</div>
</HStack>
);
}2. Input area with send button
Use a form with Input and IconButton for keyboard-accessible submission:
import { Input, IconButton } from "@ninna-ui/primitives";
import { Send } from "lucide-react";
function ChatInput({ onSend }: { onSend: (text: string) => void }) {
return (
<form onSubmit={(e) => { e.preventDefault(); onSend(text); }}>
<div className="flex gap-2">
<Input placeholder="Type a message..." />
<IconButton color="primary" type="submit" aria-label="Send message">
<Send className="size-4" />
</IconButton>
</div>
</form>
);
}3. Typing indicator
Use the Loading component from @ninna-ui/feedback to show when the AI is generating a response:
import { Loading } from "@ninna-ui/feedback";
import { HStack, Text } from "@ninna-ui/primitives";
function TypingIndicator() {
return (
<HStack gap="2">
<Loading size="sm" />
<Text size="sm" className="text-base-content/60">AI is typing...</Text>
</HStack>
);
}4. Accessibility: aria-live for new messages
Add aria-live="polite" to the message container so screen readers announce new messages automatically. The IconButton already has aria-label for the send button.