Skip to main content
Use Case

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.

Related Blocks

Component Docs

FAQ