Skip to content
turn

guides

Getting started

Add your first component to a React project and have a working chat input in a few minutes.

requirements

  • React 19 and a framework that supports it, such as Next.js or Vite.
  • Tailwind CSS v4.
  • A project set up with the shadcn CLI. If you do not have one yet, run npx shadcn@latest init. It creates components.json, the cn helper and the base styles.

turn is a registry, not a package. The CLI copies the source into your project and installs the small number of dependencies each component needs (Base UI, and for a few components Shiki, marked or react-markdown).

1. add the theme

The components read eight color tokens, and one of them behaves differently from a stock shadcn theme: accent is the primary action color here, not a pale highlight. Install the theme to set all eight, or skip this step and define them yourself. See theming.

bash
npx shadcn@latest add https://turnui.xyz/r/theme.json

2. add components

Each command also installs the components it depends on. You can pass several at once.

bash
npx shadcn@latest add \
  https://turnui.xyz/r/message.json \
  https://turnui.xyz/r/prompt-composer.json

Files land in components/ui and hooks, using the aliases from your components.json. They are yours from that point: edit the markup, tokens and behavior freely.

3. build a chat

A complete page with a message list and a composer. It echoes your message back, so you can see everything working before you connect a model.

tsx
"use client";

import { useState } from "react";
import { Message, MessageContent } from "@/components/ui/message";
import { PromptComposer } from "@/components/ui/prompt-composer";

type Turn = { role: "user" | "assistant"; text: string };

export default function Page() {
  const [turns, setTurns] = useState<Turn[]>([]);

  return (
    <main className="mx-auto flex h-dvh max-w-2xl flex-col p-4">
      <div role="log" aria-live="polite" aria-label="Conversation" tabIndex={0} className="flex-1 space-y-4 overflow-y-auto outline-none focus-visible:ring-2 focus-visible:ring-ring">
        {turns.map((t, i) => (
          <Message key={i} role={t.role}>
            <MessageContent>{t.text}</MessageContent>
          </Message>
        ))}
      </div>
      <PromptComposer
        onSubmit={(text) =>
          setTurns((all) => [...all, { role: "user", text }, { role: "assistant", text: "You said: " + text }])
        }
      />
    </main>
  );
}

The container has role="log" and aria-live="polite" so screen readers announce new messages, and tabIndex={0} so keyboard users can scroll it. Enter sends, Shift+Enter adds a line.

next