Hero composerPro
A landing hero whose main object is the composer itself: example prompts take turns in the placeholder, files attach, the model chip opens a menu and Send routes to your app.
On this page
Installation
Usage
import { HeroComposer } from "@/components/ui/chatcn/pro/hero-composer"
<HeroComposer
title="Ask the whole company one question."
examples={["Why did weekly active teams drop last month…", "Draft the customer note…"]}
models={[{ id: "m2", label: "Claude Sonnet 5.5" }]}
onSubmit={({ text, model }) => router.push(`/new?q=${encodeURIComponent(text)}&m=${model}`)}
/>The visitor can start typing at once. While the field is empty and not focused, examples take turns in the placeholder: the current one moves up and out, the next comes in from below, with no letters typing in. The same examples show as chips under the composer; a chip fills the field and puts the caret at the end. Rotation holds while the field has focus or text, and stops offscreen.
onSubmit gets { text, model, files }. Route to your app with the text in the URL, or keep it through sign up, so the first message survives the trip. Pass status="submitting" while you route. Enter sends, Shift+Enter adds a line, and nothing sends during IME composition.
Every section is full width with a 16 px gutter on phones and 32 px from 768 px, content capped at 1200 px. Layout follows the section's own width through a container query, not the viewport, so a section in a narrow column lays out like a phone.
Examples
States
Submitting with the text still in place, disabled during maintenance, a single model without examples, and a right to left page.
Props
| Prop | Type | Notes |
|---|---|---|
title | ReactNode | Required. One idea; it balances over two or three lines |
description | ReactNode | The paragraph under or beside the headline |
examples | string[] | Example prompts, in the placeholder and as chips |
interval | number | Ms each example stays. Default 3200 |
examplesLabel | string | Before the chips. Default "Try" |
defaultValue | string | Text already in the field, for example from the URL |
placeholder | string | Without examples. Default "Ask anything…" |
inputLabel | string | Accessible name of the field. Default "Message" |
models, model, defaultModel, onModelChange | { id, label, description? }[] | One model shows as a plain label, two or more as a menu, none hides the chip |
onSubmit | ({ text, model, files }) => void | Not called while empty or disabled |
status | "idle" | "submitting" | Spinner in the send button while you route |
disabled | boolean | The field, chips and model chip stop taking input |
attachments, accept, maxFiles | The attach row. Default on, 5 files | |
footnote | ReactNode | A line under the composer, for example the free tier |
children | ReactNode | Media or a small live object under everything |
Keyboard
| Key | Action |
|---|---|
Enter | Sends. Never during IME composition |
Shift+Enter | New line |
Tab | Moves to attach, the model chip, send and the example chips |
Enter / Space on a chip | Fills the field with that example |
Accessibility
- The section is labelled by its headline. Heroes use an
h1, other sections anh2; render one hero per page. - The rotating placeholder is
aria-hidden; the chips carry the same examples as real buttons, so nothing depends on watching the swap. - The model chip is a menu button named
Model: <name>, with a radio group inside. - Every control is at least 32 px, 44 px on touch screens.
Motion
- The incoming example rises 0.45 em and fades in over 280 ms; the outgoing one leaves upward over 210 ms. Both are transitions, so a fast click through the chips never stacks animations.
- Under reduced motion the swap is a 150 ms crossfade with no movement.
- Chips press to 0.97 over 100 ms and never scale on hover.