Assistant Welcome
A first-run assistant hero — greeting, capability cards and starter chips that prefill the host's composer instead of sending, over a token-only animated accent.
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/assistant-welcome.jsonPrompt
Build a React + TypeScript + Tailwind "AssistantWelcome" block (lucide-react
ArrowUpRight + Check). It is the first screen of a chat surface: a greeting, a
grid of capability cards, a row of starter chips, a decorative token gradient,
and a bottom slot the host fills with its own composer.
Contract
- forwardRef<HTMLElement, AssistantWelcomeProps> over a <section>, spreading the
remaining native section props; cn() merges className.
- Props: greeting (required node), subtitle?, eyebrow?, capabilities?:
{ id, title, example, prompt?, icon? }[], starters?: { id, text, prompt? }[],
onPick?: (pick: { source: "capability" | "starter"; id: string; text: string })
=> void, children (the composer slot), footnote?, capabilitiesLabel?,
startersLabel?, density?: "comfortable" | "compact", accent? (default true),
disabled?, headingLevel?: 1 | 2, echoDuration? (ms, default 2400).
- The block owns no text state. `greeting` is a prop, never derived: time of
day, the user's first name and the workspace are resolved by the host, so the
component stays timezone- and identity-free.
- `pick.text` is pre-resolved (`prompt ?? example` for a card, `prompt ?? text`
for a chip), so a handler is one line: setDraft(pick.text).
Behavior
- Prefill, not send. onPick hands the text over; the host drops it into its
composer and the reader may edit, extend or discard it. Nothing latches, and
the same card may be picked again — the opposite of a click-to-send strip
where the first pick is irreversible and the group locks for good.
- Transient echo: the picked item is marked (primary border, tinted icon chip,
Check swapped in for the hover arrow) and a polite live region announces
"Added to the composer: <text>". A single setTimeout clears it after
echoDuration; the effect stores a fresh state object per pick so picking the
SAME item twice restarts the timer instead of inheriting the first one's
remaining time, and the cleanup clears the timer on unmount and on every
re-pick. echoDuration is clamped 600–10000 and NaN falls back to the default.
- Echo keys are `${source}:${id}` — a card and a chip may share an id without
lighting up together.
- No onPick → honest degrade: cards render as <div>, chips as <span>, with no
pointer cursor, no hover, no button role and no hover arrow. A control that
looks clickable and does nothing is worse than a static screenshot.
- disabled (session connecting, restoring, out of quota): every card and chip
keeps aria-disabled — never the native `disabled` attribute, which would blur
whatever the reader had tabbed to — and the click still reaches the handler,
which refuses it. The echo mark is derived away while disabled; the pending
timer still clears the state on its own. The composer slot keeps rendering:
what sits inside it is the host's call.
- Empty arrays render nothing at all: no label row, no gap, no zero-height
shell. Same for the composer block when neither children nor footnote exist.
- Keyboard: cards and chips are ordinary buttons in document order (a welcome
screen has a handful, and a roving group would hide the rest from sequential
navigation); focus-visible rings on both. The live region never steals focus —
after a pick the host is the one that should focus its composer.
Rendering & styling
- Semantic tokens only: bg-card surfaces, border/border-primary edges, bg-muted
icon chips, text-muted-foreground supporting copy, primary for the picked
state, ring for focus. The accent is three blurred radial blobs painted from
var(--chart-1), var(--chart-4) and var(--chart-2) — no hex anywhere, so a
theme swap re-tints it and dark mode is free.
- Accent motion: each blob carries `animate-pulse` with an inline
animation-duration/-delay (7s / 11s / 9s, staggered) so it beats the utility
shorthand in the cascade, wrapped in a fixed `opacity-*` parent so the breath
stays subtle. `motion-reduce:animate-none` drops the keyframes and leaves the
identical gradient perfectly still — decoration off, nothing else changes.
- The accent is aria-hidden, absolutely positioned and pointer-events-none; the
content column is merely `relative`, so positioned siblings paint in DOM order
and no negative z-index can slide the accent under the host's background. The
section is `isolate` to contain the whole thing.
- Capability grid is auto-fit: grid-cols-[repeat(auto-fit,minmax(min(15rem,100%),1fr))]
so three cards fill a row and seven wrap, with no hardcoded column count.
- Overflow discipline: greeting and every label use wrap-anywhere (not
break-words) so an unbreakable pasted token lowers min-content instead of
widening the block past the viewport; chips wrap with items-start so a short
chip never inherits a tall neighbour's height.
- The hover arrow is always mounted at opacity-0 and revealed on
hover/focus-visible, so it cannot reflow the title row.
Customization levers
- Density: `density="compact"` halves the paddings, drops one type step and
narrows the grid track — same anatomy, side-panel scale. Both maps live in one
DENSITY const; add a third row for a "roomy" variant.
- Anatomy: every block is independently omissible — drop capabilities for a
chips-only returning-user screen, drop starters for a pure capability grid,
drop children for a marketing screenshot, set capabilitiesLabel="" /
startersLabel="" to lose the eyebrows (the lists fall back to aria-label).
- Motion strength: accent={false} removes the gradient entirely; otherwise tune
ACCENT_BLOBS (count, size, opacity, duration) — it is data, not JSX.
- Palette: swap the three chart tokens for var(--primary) for a monochrome
accent, or re-point the picked state from primary to a chart token.
- Reach: headingLevel switches h1/h2 for pages that already own an h1;
echoDuration tunes how long the "added" mark lingers; the icon slot accepts
any node and the chip normalises svg children to 1rem.
- Composer: the slot is plain children, so a full prompt-input with attachments,
model picker and stop button drops in without touching this block.Concepts
- Prefill, not send — a pick only seeds the draft, so it is reversible: the reader edits or deletes it, and the same card stays live. That is why nothing here latches the way a click-to-send suggestion strip has to.
- Transient echo — the "added" mark is a receipt with a lifetime, not state. A fresh state object per pick restarts its timer, so picking one card twice reads as two acknowledgements rather than one that expires early.
- Honest degrade — with no
onPickthe cards lose their button role, cursor and hover entirely. The component would rather look static than render an affordance that does nothing. - aria-disabled over native disabled — while the session is connecting the click still reaches the handler and is refused there; the native attribute would blur the item the reader had just tabbed to and hide it from assistive tech.
- Composer as a slot — the block never owns the input. It shapes the screen around whatever composer the host already ships, so prefill is a one-line handler instead of a second source of draft state.
- Decoration that can be switched off — the gradient is token-painted, aria-hidden and pointer-events-none, and reduced motion freezes it in place: the screen loses its breathing accent and not one interaction.
Agent Dashboard
An agent-ops overview — KPI tiles whose trends are derived, a runs-over-time bar chart, a recent-runs table whose failing rows expand to the error line, and a live-agent rail that filters the table without moving the window figures.
API Error Card
An LLM API failure card whose recovery is chosen by error class — a countdown ring with one-shot opt-in auto-retry for 429s, a key-settings exit for auth, a status-page exit for overload, and trim / summarize exits when the context is too long.