Skip to main content
Clever Ops

AI Empty State

Agent session zero state for React AI apps, starter prompt cards, connected-tool chips, and an honest scope note, styled with Tailwind hairline restraint; composes above any composer.

Preview & code

Loading AI Empty State preview

Installation

pnpm dlx shadcn@latest add @cleverui/ai-empty-state

Requires the @cleverui registry in your components.json, a one-off, two-line setup.

Usage

tsx
import { AiEmptyState } from '@/components/ui/ai-empty-state'
Exampletsx
'use client'

import { useState } from 'react'
import { AnimatePresence, motion, useReducedMotion } from 'motion/react'
import { AiEmptyState } from '@/components/ui/ai-empty-state'

const SUGGESTIONS = [
  {
    id: 'reconcile',
    label: 'Reconcile May invoices',
    hint: 'Match Xero bills against Stripe payouts for Apex Holdings.',
    prompt:
      'Reconcile May invoices against Xero for Apex Holdings and flag any mismatches over $50.',
  },
  {
    id: 'digest',
    label: 'Draft the ops digest',
    hint: 'Weekly summary for #ops, incidents, spend, open approvals.',
    prompt:
      'Draft this week’s ops digest for #ops covering incidents, spend variance, and open approvals.',
  },
  {
    id: 'refund',
    label: 'Review pending refunds',
    hint: 'Surface Stripe refunds waiting on human approval.',
    prompt:
      'List pending Stripe refunds over $100 that still need human approval.',
  },
  {
    id: 'escalation',
    label: 'Escalate stalled tickets',
    hint: 'Find Linear issues older than 5 days with no assignee.',
    prompt:
      'Find Linear issues older than 5 days with no assignee and draft escalation notes.',
  },
]

const CAPABILITIES = [
  { id: 'xero', label: 'xero' },
  { id: 'slack', label: 'slack #ops' },
  { id: 'stripe', label: 'stripe' },
  { id: 'linear', label: 'linear' },
]

export default function AiEmptyStateDemo() {
  const reduced = useReducedMotion()
  const [receipt, setReceipt] = useState<string | null>(null)

  return (
    <div className="mx-auto flex min-h-[520px] w-full max-w-2xl flex-col justify-center">
      <AiEmptyState
        heading="What should meridian-ops handle?"
        subheading="Pick a starting point or describe the task in your own words."
        suggestions={SUGGESTIONS}
        capabilities={CAPABILITIES}
        scopeNote="reads invoices and ledgers, never sends email without approval"
        onSelect={(_id, prompt) => {
          const short =
            prompt.length > 48 ? `${prompt.slice(0, 48)}` : prompt
          setReceipt(`sent to composer: ${short}`)
        }}
      />

      <div className="relative mx-auto min-h-6 w-full max-w-2xl px-4 sm:px-6">
        <AnimatePresence mode="wait" initial={false}>
          {receipt && (
            <motion.p
              key={receipt}
              initial={reduced ? { opacity: 0 } : { opacity: 0, y: 4 }}
              animate={{ opacity: 1, y: 0 }}
              exit={
                reduced
                  ? { opacity: 0, transition: { duration: 0.15 } }
                  : {
                      opacity: 0,
                      y: -4,
                      transition: { duration: 0.2 },
                    }
              }
              transition={{ duration: reduced ? 0.15 : 0.2 }}
              className="text-center font-mono text-[11px] text-muted-foreground sm:text-xs"
            >
              {receipt}
            </motion.p>
          )}
        </AnimatePresence>
      </div>
    </div>
  )
}

API reference

AiEmptyState accepts the following props.

PropTypeDefaultDescription
headingstring'What should meridian-ops handle?'plain-language headline inviting the first instruction
subheadingstringone muted sentence of orientation
suggestions*AiEmptyStateSuggestion[]starter prompt cards
onSelect(id: string, prompt: string) => voidfires when a suggestion card is activated
capabilitiesAiEmptyStateCapability[]connected-tool chips rendered as mono hairline pills
scopeNotestringhonest access line in mono
labelstring'New session'mono micro-label above the heading

…plus everything from Omit<React.HTMLAttributes<HTMLElement>, 'onSelect'>: className, event handlers, aria attributes and the rest pass straight through.

Referenced types

Typestsx
export interface AiEmptyStateSuggestion {
  id: string
  label: string
  hint?: string
  /** full text handed to onSelect; falls back to label */
  prompt?: string
}

export interface AiEmptyStateCapability {
  id: string
  label: string
}

Frequently asked questions

How do I install the AI Empty State component?

Register the @cleverui registry in your components.json ("@cleverui": "https://cleverops.com.au/r/{name}.json"), then run `npx shadcn@latest add @cleverui/ai-empty-state`. The shadcn CLI copies the source into your project, you own the code from that point. You can also copy the source directly from this page.

What dependencies does AI Empty State require?

AI Empty State uses motion and lucide-react on top of React and Tailwind CSS. You don't need to install them manually, the shadcn CLI resolves and installs npm dependencies automatically when you add the component.

Can I theme AI Empty State to match my brand?

Yes. The component is styled entirely with Tailwind utilities over standard design tokens (primary, background, muted, border), so it inherits your existing shadcn/ui theme automatically. Change your CSS variables and the component follows, no source edits needed for palette, radius, or fonts.

Want this built for you?

CleverUI is built by the Clever Ops web team. If you'd rather ship a hand-coded, fast, custom website than assemble one, we build them for Australian businesses every week.

Explore web development services