Empty state
What a new chat shows before the first message: a greeting, a centred prompt, a project's home with its files and recent chats, a gallery of task templates, a return screen with unfinished work, an agent's own landing page, and a blank image studio.
Loading preview…
Install
- 1
Run this command in your Tailwind CSS project. It adds
components/ai/empty-state-greeting.tsxand installs its dependencies.$ npx shadcn@latest add https://gradientdeck.com/ai-components/r/empty-state-greeting.json
Usage & API
Example
import { EmptyStateGreeting } from "@/components/ai/empty-state-greeting"
<EmptyStateGreeting
greeting="…"
place="…"
lead="…"
starters={[]}
onPick={() => {}}
/>Props EmptyStateGreeting
| Prop | Type & description | Required |
|---|---|---|
greeting | stringe.g. "Tuesday evening, Maya" | Required |
place | stringe.g. "18:42 in Lisbon · 16° and clearing" | Required |
lead | string | Required |
starters | Starter[] | Required |
onPick | (starter: Starter) => void | Required |
scene | ReactNode | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Greeting exports EmptyStateGreeting and CyclingPlaceholder. The heading is greeting, the line above it is place, and lead sits underneath. starters render as buttons with a visual, a title, and a detail. onPick receives the whole starter, so you can drop starter.prompt into the composer. The optional scene is a node on the right, hidden below the md breakpoint. The preview's scene is DuskScene. CyclingPlaceholder takes items and paused, and swaps the visible suggestion every 3200ms unless paused or reduced motion is on. The preview's items include Ask about the board memo and What should I cook with leeks and feta? The other starters are a Saturday run from Graça, a reply to Tom about Porto, and three receipts of about £86.
Example
import { CenteredComposer } from "@/components/ai/empty-state-centered"
<CenteredComposer
modes={[]}
mode={mode}
onModeChange={() => {}}
value="…"
onChange={() => {}}
onSend={() => {}}
/>Props CenteredComposer
| Prop | Type & description | Required |
|---|---|---|
modes | ModeConfig[] | Required |
mode | Mode | Required |
onModeChange | (mode: Mode) => void | Required |
value | string | Required |
onChange | (value: string) => void | Required |
onSend | () => void | Required |
compact | booleanHide the title, tabs and starters once a chat has begun. | Optional |
disabled | boolean | Optional |
textareaRef | React.RefObject<HTMLTextAreaElement | null> | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Centered exports CenteredComposer. Props are modes, mode, onModeChange, value, onChange, onSend, compact, disabled, and textareaRef. A mode has an id of ask, research, or create, plus label, icon, title, placeholder, note, and starters. The tabs are a radiogroup in behaviour and a tablist in markup, and the arrow keys move between them. Ask shows a Search the web chip. Research shows Web and Drive chips. Create shows a format group: Doc, Slides, Email. Enter sends. Shift is not required to be held back because the handler sends on Enter without Shift. compact drops the title, the tabs, and the starters, and the placeholder becomes Reply. The textarea grows up to 140px.
Example
import { ProjectHome } from "@/components/ai/empty-state-workspace"
<ProjectHome
name="…"
instructions="…"
onInstructionsChange={() => {}}
files={[]}
selected={[]}
onToggleFile={() => {}}
chats={[]}
onOpenChat={() => {}}
starters={[]}
onPickStarter={() => {}}
/>Props ProjectHome
| Prop | Type & description | Required |
|---|---|---|
name | string | Required |
instructions | string | Required |
onInstructionsChange | (text: string) => void | Required |
files | ProjectFile[] | Required |
selected | string[]Files the person has pulled into the next message. | Required |
onToggleFile | (id: string) => void | Required |
chats | ProjectChat[] | Required |
onOpenChat | (chat: ProjectChat) => void | Required |
starters | FileStarter[] | Required |
onPickStarter | (starter: FileStarter) => void | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Project home exports ProjectHome. Props are name, instructions, onInstructionsChange, files, selected, onToggleFile, chats, onOpenChat, starters, and onPickStarter. A file has id, name, meta, and kind of doc, sheet, pdf, or image. The left column is Start from your files, each starter naming its file, then Recent in this project with title, last line, and when. The right column edits instructions: Save calls onInstructionsChange with the trimmed text, or the previous instructions if the trim is empty, and Escape cancels. Files shows the count. Clicking a file calls onToggleFile and sets aria-pressed. The preview project is Fieldnote launch. Add file is a button in the section header. It does not open a picker by itself.
Example
import { MiniPreview } from "@/components/ai/empty-state-templates"
<MiniPreview
kind={kind}
/>Props MiniPreview
| Prop | Type & description | Required |
|---|---|---|
kind | Preview | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Task templates exports TemplateGallery and TemplateComposer. A template has a category of Write, Analyse, Plan, or Code, a title, an output line, a preview kind, and segments that are either strings or fields with a key and a placeholder. The gallery takes templates, category, onCategoryChange, onPick, and activeId. Tabs are All plus the four categories. MiniPreview draws the kind, from email and outline through sql, trace, and tests. The composer takes template, values, onChange, onClear, and onSend. Fields are inline inputs. The hint reads Tab to move between fields. Send stays disabled until every field is non-empty, and Enter in a field sends only then. Escape calls onClear. The first field focuses when template.id changes.
Example
import { AwaySummary } from "@/components/ai/empty-state-resume"
<AwaySummary
task={task}
approval={approval}
onApprove={() => {}}
onDecline={() => {}}
/>Props AwaySummary
| Prop | Type & description | Required |
|---|---|---|
task | { title: string; repo: string; files: ChangedFile[]; took: string } | Required |
approval | { title: string; detail: string; status: "waiting" | "running" | "approved" | "declined" } | Required |
onApprove | () => void | Required |
onDecline | () => void | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Pick up where you left off exports AwaySummary with task, approval, onApprove, and onDecline. task has title, repo, files of path, added, and removed, and took. The left card says Finished, the took string, the title, the repo, the file count, and the summed plus and minus lines. Review expands the paths. The footer under that list always reads Draft PR #418 opened on GitHub. That string is in the component, not in the task prop. approval.status is waiting, running, approved, or declined. waiting shows Waiting for you, since Saturday, then Approve and send and Skip. approved replaces the detail with 4 emails sent from [email protected]. declined says Nothing was sent. The preview task is Refactor the invoice retry logic in tidewater/billing-service, and the heading above the cards is Since you were away.
Example
import { AgentMark } from "@/components/ai/empty-state-agent"
<AgentMark
mark={mark}
/>Props AgentMark
| Prop | Type & description | Required |
|---|---|---|
mark | AgentProfile["mark"] | Required |
size | number | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Agent landing exports AgentLanding and AgentMark. Props are agent and onPick. AgentProfile has id, name, description, creator, chats, a mark of tax, recipe, or lease, capabilities with a label and an icon, starters with title and detail, limits, and replies. The landing centres the mark, the name, the description, and a byline of creator and chats. Capabilities are chips. Starters are buttons that call onPick with the starter title. What it can't do expands limits. The preview's first agent is Tax Helper UK. The replies field is not rendered by AgentLanding. The preview uses it after a starter is picked. AgentMark takes the mark and an optional size, default 64.
Example
import { StyleArt } from "@/components/ai/empty-state-create"
<StyleArt
id={id}
uid="…"
/>Props StyleArt
| Prop | Type & description | Required |
|---|---|---|
id | StyleId | Required |
uid | string | Required |
v | number | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Image studio exports StyleGrid and StyleArt. StyleGrid takes styles, selected, and onSelect. A style id is film, iso, water, product, riso, clay, line, or pixel. The preview names them Film still, Isometric, Watercolour, Product shot, Risograph, Clay, Line drawing, and Pixel art. StyleArt draws one of those looks. selected null means no style. The rest of the file is the blank studio around that grid: a prompt labelled Describe the image, an aspect radiogroup of 1:1, 4:5, 16:9, and 9:16, a count from 1 to 4, Surprise me, and Generate. Surprise me fills a prompt such as a lighthouse keeper on an empty coast road and selects a style. A result tile has Like and Download. Those controls live in the preview, not on StyleGrid.
Choosing a layout
Use Greeting when the product knows who is sitting down and can suggest a next task from their own work. The preview says Tuesday evening, Maya, 18:42 in Lisbon, 16° and clearing, and offers Finish the Q3 board memo because they stopped at Risks yesterday, 1,240 words in. Use the centred composer when the first choice is the kind of task, ask, research, or create, not a personal history. Use Project home when the chat belongs to a project that already has files and instructions. Use templates when people should start from a shape, Write, Analyse, Plan, or Code, instead of a blank box. Use the return screen when they were away and a task finished or an approval is waiting. Use agent landing when the empty thread is one agent's front door. Use the studio when the empty screen is for pictures, not paragraphs.
Do not leave an empty state up after the first message. Greeting's onPick hands you the starter, including its prompt, and the centred composer's compact flag is how that layout gets out of the way. Do not use these screens to show a reply in progress. A wait with no tokens is Loader. A thread that should follow new text is Conversation. A list of old chats is Thread list. The studio's Generate button and the greeting's send button belong to the preview shells around the exported pieces. Wire them to your own send.
The return screen mentions Saturday and a draft PR. Can I change that copy?
Not through props. AwaySummary builds the task title, the repo, the file counts, and took from task. The line Waiting for you, since Saturday is fixed, and so is Draft PR #418 opened on GitHub under the file list. The approved state ignores approval.detail and shows 4 emails sent from [email protected]. The declined state shows Nothing was sent. You can still pass approval.detail for the waiting state, and you can pass the real paths in task.files. If that fixed copy is wrong for the product, this variant is the wrong screen.
What happens to the centred composer after the first message?
Set compact. The title, the mode tabs, and the starter suggestions unmount, and the placeholder becomes Reply. mode is still ask, research, or create, so the chips stay: Search the web, Web and Drive, or Doc, Slides, and Email. value and onChange still drive the textarea. disabled blocks Send. Enter without Shift calls onSend. The component does not clear value for you. Greeting has no compact flag. You replace it when onPick fires, using the starter's prompt, title, and detail.