# Chat Empty State

> A first-run greeting with staggered suggestion chips.

A shadcn/ui block from the [@spectrumui](https://ui.spectrumhq.in) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/spectrumui/chat-empty-state).

- Page, with a live preview: https://shadcn.exchange/r/spectrumui/chat-empty-state
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/spectrumui/chat-empty-state-light-84e372a3057ae7a0.png
- Registry item: https://ui.spectrumhq.in/r/chat-empty-state.json
- Listed: 2026-10-11, source last changed 2026-10-11

## Install

```bash
npx shadcn@latest add https://ui.spectrumhq.in/r/chat-empty-state.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`

## Files

### `components/spectrumui/blocks/ai-assistants/chat-empty-state.tsx`

```tsx
'use client';

import { Sparkles } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { SuggestedPrompt } from './types';

const KEYFRAMES = `
@keyframes su-msg-in { from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: none } }
`;

export type ChatEmptyStateVariant = 'Default' | 'Centered';

export interface ChatEmptyStateProps {
  title?: string;
  subtitle?: string;
  prompts?: SuggestedPrompt[];
  onSelectPrompt?: (prompt: SuggestedPrompt) => void;
  variant?: ChatEmptyStateVariant;
  className?: string;
}

export function ChatEmptyState({
  title = 'What do you want to know?',
  subtitle = 'Ask a question, or start from one of these.',
  prompts = [],
  onSelectPrompt,
  variant = 'Default',
  className,
}: ChatEmptyStateProps) {
  const centered = variant === 'Centered';

  return (
    <div
      className={cn(
        'w-full max-w-[520px]',
        centered && 'flex flex-col items-center text-center',
        className,
      )}
    >
      <style dangerouslySetInnerHTML={{ __html: KEYFRAMES }} />

      <div className="grid size-9 place-items-center rounded-xl border border-black/[0.07] bg-black/[0.02] dark:border-white/[0.08] dark:bg-white/[0.04]">
        <Sparkles className="size-4 text-neutral-500 dark:text-neutral-400" />
      </div>

      <h3 className="mt-4 text-[16px] font-semibold tracking-[-0.2px] text-neutral-900 dark:text-neutral-50">
        {title}
      </h3>
      <p className="mt-1.5 text-[13px] leading-[1.6] text-neutral-500 dark:text-neutral-400">
        {subtitle}
      </p>

      {prompts.length > 0 && (
        <div
          className={cn(
            'mt-5 flex flex-wrap gap-1.5',
            centered && 'justify-center',
          )}
        >
          {prompts.map((prompt, index) => (
            <button
              key={prompt.id}
              type="button"
              onClick={() => onSelectPrompt?.(prompt)}
              style={{ animationDelay: `${Math.min(index, 5) * 50}ms` }}
              className={cn(
                'rounded-full border border-black/[0.08] bg-white px-3 py-1.5 text-[12.5px] text-neutral-600',
                'transition-[color,border-color,transform] duration-150 active:scale-[0.96]',
                'hover:border-black/[0.16] hover:bg-black/[0.02] hover:text-neutral-900',
                'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-neutral-400',
                'motion-safe:animate-[su-msg-in_240ms_cubic-bezier(0.23,1,0.32,1)_both]',
                'dark:border-white/[0.1] dark:bg-white/[0.03] dark:text-neutral-300 dark:hover:border-white/[0.2] dark:hover:bg-white/[0.06] dark:hover:text-neutral-50',
              )}
            >
              {prompt.label}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

export default ChatEmptyState;
```

### `components/spectrumui/blocks/ai-assistants/types.ts`

```ts
export type MessageRole = 'user' | 'assistant' | 'system';

export type MessageState = 'streaming' | 'complete' | 'error';

export interface Attachment {
  id: string;
  name: string;
  size: number;
  type: string;
  url?: string;
  previewUrl?: string;
}

export interface Citation {
  id: string;
  index: number;
  url: string;
  title: string;
  snippet?: string;
  favicon?: string;
}

export type ToolCallStatus = 'pending' | 'running' | 'success' | 'error' | 'cancelled';

export interface ToolCall {
  id: string;
  name: string;
  args?: Record<string, unknown>;
  result?: string;
  status: ToolCallStatus;
  startedAt?: number;
  completedAt?: number;
  children?: ToolCall[];
  parallel?: boolean;
}

export interface ReasoningStep {
  id: string;
  content: string;
}

export interface Reasoning {
  steps: ReasoningStep[];
  status: 'thinking' | 'complete';
  durationMs?: number;
}

export interface Message {
  id: string;
  role: MessageRole;
  content: string;
  createdAt?: number;
  state?: MessageState;
  attachments?: Attachment[];
  citations?: Citation[];
  toolCalls?: ToolCall[];
  reasoning?: Reasoning;
}

export interface Conversation {
  id: string;
  label: string;
}

export interface ModelOption {
  id: string;
  name: string;
  description?: string;
  badge?: string;
  disabled?: boolean;
}

export interface SuggestedPrompt {
  id: string;
  label: string;
  prompt?: string;
}

export type MessageFeedback = 'positive' | 'negative';

export interface UsageState {
  promptTokens: number;
  completionTokens: number;
  contextWindow: number;
  estimatedCostUsd?: number;
}
```
