Skip to content
KONIGI Patterns

AI Assistants / Starting a request / First-run state

‹ 4 of 7 ›

First-run state

New users need to understand what the assistant can do and how to start.

Updated September 12, 2026

Problem

An empty conversation needs to help newcomers understand the assistant without slowing down people who already use it.

Solution

Briefly explain what the assistant can do and what content it can access. Include a few relevant prompt starters and the limits people need to know before asking.

Keep the message field prominent. Avoid tours or instructions that people must dismiss every time they start a conversation.

Give a short explanation of how conversation data is used, with a link to details. Provide access to earlier conversations. An empty history can also belong to an experienced user, so avoid assuming every visit is someone’s first.

Use when

People regularly return to an assistant’s empty conversation screen.

Don’t use when

The assistant is summoned to a specific object and dismissed, like an inline rewrite or a command-bar invocation. There the selection is the context, the invocation carries the intent, and a teaching screen in front of it’s an obstacle.

Trade-offs

Help text competes with the message field. Too little guidance encourages guessing, while too much becomes repetitive. Capability and privacy descriptions need updating when the product changes.

Checklist

  • Does the screen say what this assistant can see, not just what it can do?
  • Does it state a limitation, and does that survive review by someone protecting adoption numbers?
  • Is the data question answered here rather than in settings?
  • What does this look like on the four-hundredth visit?
  • Does anything require dismissing, and does the dismissal persist?
  • Is the composer the visual centre?
  • Is it pleasant for a returning user with an empty history?
  • Who owns updating the capability claims when the product changes?
  • Does it work at a phone width with the keyboard up?
  • Is there a path from here to previous conversations?

Compare

ChatGPT keeps the screen close to empty with the composer centred and a small rotating set of examples, betting that the product is well enough known that explanation costs more than it returns. Claude changes the screen inside a project, where it describes the project’s material and makes the assistant’s scope the subject rather than the product’s features. Perplexity fills the empty state with live example queries and trending searches, It teaches question shape, and it doubles as something to browse when the viewer hasn’t got a question yet. Microsoft Copilot inside an Office application spends the screen on what it can reach in the current document and tenant, which is the right call for an assistant whose main unknown is its scope rather than its ability.

Prompt starters demonstrate useful requests. Composer provides the main action, and Conversation history links to previous work.

Refusal should be consistent with the limits described here. Memory chip shows when the assistant saves information for later.

First-run state anatomy An empty conversation composed around the input: one line saying what the assistant can see, a few examples, a stated limitation, and a line about what happens to what gets typed. Nothing requires dismissing. Seen once by a newcomer, four hundred times by everyone else Ask about anything in this workspace it can read pages you have access to, not the whole company summarise a page find a decision we made draft from these notes It gets dates and figures wrong. Check anything you act on. Conversations are kept in this workspace and visible to admins. 1 2 3 4 1 SAY WHAT IT CAN SEE Scope is the thing a viewer cannot guess and the reason to use this over a chatbot. 2 THE INPUT IS THE CENTRE Everything else has to be quieter, so a veteran can skip the whole composition. 3 STATE A LIMITATION It reads as an apology and it prevents the failure that actually loses people. 4 ANSWER THE DATA QUESTION People want it at the moment of first input, not on a settings page. Nothing here should need dismissing. A dismissal is a cost paid by everyone who returns.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

An empty conversation composed around the input: what the assistant can see, a few examples, a stated limitation, and what happens to what you type. Nothing here needs dismissing, because everyone who is not new sees it four hundred times.

shadcn
npx shadcn@latest add button textarea
npm
lucide-react
Tokens
--background--card--card-foreground--primary--primary-foreground--accent--accent-foreground--muted-foreground--border--input--status-warn

Ask about anything in this workspace

it can read pages you have access to, not the whole company

It gets dates and figures wrong. Check anything you act on.

Conversations are kept in this workspace and visible to admins.

FirstRunState.tsxThe four lines are props, so the screen cannot ship without them. An example fills the input rather than sending it.

import { useRef, useState } from "react";
import { ArrowUp } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";

type Props = {
  /** One line on what the assistant can see. Scope is the thing a viewer
   *  cannot guess, and the reason to use this over a chatbot. */
  scope: string;
  /** Two or three. More than that is a menu, and a menu is a thing to read. */
  examples: string[];
  /** Stated up front. It reads as an apology and prevents the failure that
   *  actually loses people. */
  limitation: string;
  /** What happens to what gets typed. Asked at the moment of first input,
   *  not on a settings page. */
  dataNotice: string;
  onSend: (text: string) => void;
};

export function FirstRunState({ scope, examples, limitation, dataNotice, onSend }: Props) {
  const [draft, setDraft] = useState("");
  const ref = useRef<HTMLTextAreaElement>(null);

  const send = () => {
    const text = draft.trim();
    if (!text) return;
    onSend(text);
    setDraft("");
  };

  // An example fills the input rather than sending. A newcomer is here to
  // learn what a question looks like, and a question they can edit teaches
  // that better than one that fires on click.
  const useExample = (text: string) => {
    setDraft(text);
    ref.current?.focus();
  };

  return (
    <div className="rounded-lg border bg-card p-5 text-center">
      <p className="text-base text-card-foreground">Ask about anything in this workspace</p>
      <p className="mt-1.5 text-xs text-muted-foreground">{scope}</p>

      <div className="mt-4 flex flex-wrap justify-center gap-2">
        {examples.map((e) => (
          <Button key={e} variant="outline" size="sm" className="rounded-full" onClick={() => useExample(e)}>
            {e}
          </Button>
        ))}
      </div>

      {/* The input is the centre. Everything else on this screen has to be
          quieter than it, so a veteran can skip the whole composition. */}
      <div className="mx-auto mt-5 flex w-[464px] max-w-full items-end gap-2 rounded-lg border bg-background p-2">
        <Textarea
          ref={ref}
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); send(); }
          }}
          rows={1}
          placeholder="Ask a question"
          aria-label="Ask a question"
          className="min-h-0 resize-none border-0 px-2 py-1.5 text-left shadow-none focus-visible:ring-0"
        />
        <Button size="sm" onClick={send} disabled={!draft.trim()}>
          <ArrowUp className="size-4" /> send
        </Button>
      </div>

      <p className="mt-5 text-xs text-status-warn">{limitation}</p>
      <p className="mt-2.5 text-xs text-muted-foreground">{dataNotice}</p>
    </div>
  );
}

demo.tsxHow it is called: the four lines as props. Send shows what went.

import { useState } from "react";
import { FirstRunState } from "./FirstRunState";

/** The four lines a first run has to state, as props. Send echoes what was sent. */
export default function Demo() {
  const [sent, setSent] = useState<string | null>(null);
  return (
    <div>
      <FirstRunState
        scope="it can read pages you have access to, not the whole company"
        examples={["summarise a page", "find a decision we made", "draft from these notes"]}
        limitation="It gets dates and figures wrong. Check anything you act on."
        dataNotice="Conversations are kept in this workspace and visible to admins."
        onSend={setSent}
      />
      {sent && <p className="mt-3 text-xs text-muted-foreground">sent: “{sent}”</p>}
    </div>
  );
}
What it renders. Identical markup in both panes, with only the token values changing.

Examples

No screenshots yet for this pattern. Browse Products to see other examples.