--- title: Grok Clone description: Open-source Grok clone in React — pill composer with paperclip, animated Mic↔Send, functional model picker dropdown, and message timing tooltip. --- import { Grok } from "@/components/examples/grok";
## Overview The Grok Clone demonstrates how to customize assistant-ui to match xAI's Grok interface. The empty state centers the Grok wordmark above a pill composer with an animated trailing button that swaps between Mic, Send, and Cancel based on composer state. Models are picked from a real dropdown that animates open/collapse along with the composer. ## Features - **Centered Grok Wordmark**: Empty state shows the SVG wordmark above the composer - **Pill Composer**: `rounded-4xl` with thin ring border, paperclip on the leading edge - **Functional Model Picker**: Fast / Grok 4.1 / Think dropdown with descriptions, plus "Subscribe to SuperGrok" - **Expand/Collapse Model Pill**: When the composer is empty the pill shows the model name; when typing it collapses to just the model icon - **Animated Send Slot**: Mic when empty, Send (white-on-dark) when typing, Cancel while running — all via `group-data-[empty]` / `group-data-[running]` - **Inverted Primary**: Dark button on light mode, light button on dark mode - **Message Timing**: Streaming responses show a hover tooltip with first-token, total time, tokens/sec, chunk count - **Hover-only Action Bars**: Edit/Copy on user messages, Reload/Copy/👍/👎 on assistant messages ## Quick Start ```bash npx assistant-ui add thread ``` ## Code The composer carries `data-empty` and `data-running` attributes so the trailing button transitions smoothly: ```tsx import { AuiIf, ThreadPrimitive, ComposerPrimitive, useAuiState, } from "@assistant-ui/react"; export const Grok = () => ( s.thread.isEmpty}>
!s.thread.isEmpty}> {() => }
); const Composer = () => { const isEmpty = useAuiState((s) => s.composer.isEmpty); const isRunning = useAuiState((s) => s.thread.isRunning); return (
); }; ``` ### Functional Model Picker ```tsx const MODELS = [ { id: "fast", name: "Fast", description: "Default. Quick responses", Icon: Zap }, { id: "grok-4.1", name: "Grok 4.1", description: "Standard reasoning", Icon: Moon }, { id: "think", name: "Think", description: "Multi-step reasoning", Icon: Moon }, ]; {/* Name + chevron collapse to nothing while typing */}
{current.name}
{MODELS.map((m) => ( setModel(m.id)}> {m.id === model ? : } {m.name} — {m.description} ))}
``` ### Animated Send Slot ```tsx
{/* Send when typing */} {/* Stop while running */}
``` ### Color Palette | Element | Light | Dark | |---------|-------|------| | Background | `#fdfdfd` | `#141414` | | Composer surface | `#f8f8f8` | `#212121` | | Ring border | `#e5e5e5` | `#2a2a2a` | | Primary text | `#0d0d0d` | `white` | | Muted text | `#9a9a9a` | `#6b6b6b` | | Send button (inverted) | `#0d0d0d` | `white` | ### Animation Technique The composer uses `data-*` attributes for state-based animations: ```tsx