← All components

composer

Composer bar

A compose bar pattern for generative AI input — prompt field, media attachment, and submit as standard features; use case, model and aspect ratio selection, smart prompt optimization, automatic model routing, and abort as advanced ones.

Use case selectionAuto-select model routingPrompt optimizationAbort generationVoice inputWorld model support

World

Preview stub

The world renders here once world models are available on Fal or OpenRouter.

WASD move←→ lookSpace jump1:00 left

Code snippet

The bare minimum is a prompt bar for text input to generate media with the ability to abort. With no Model menu, it runs the first suggested image model.

For the stylesheets and the server route that holds your API keys, see Develop.

Add more

FeaturePropsNotes
Input
Media attachmentsattachments onAttachmentsChange
Voice inputvoiceInput={{ model }}Any Fal speech-to-text model below, or { transcribe } for your own
Use cases
Use case pickeruseCase onAttachMenuSelectWorld, speech and music also need attachMenuActions
Text chatdefaultUseCaseLeave it out: with no use case picked, a submit is a text request. Needs ChaiProvider
Several at oncemultiSelectUseCases useCases onUseCasesChange modelsByKindImage and Video only, a Model menu each; other use cases run alone. Use nextUseCases
Create worldcharacter onCharacterChangeThe world's second prompt, the character
Models
Model menumodelId onModelChange modelsChai's suggested models unless you pass your own
Several modelsmultiSelectModels modelIds onModelIdsChangeOne result per model
Auto-selectshowAutoSelectToggle autoSelectModel onAutoSelectModelChangeNeeds ChaiProvider
Options
Aspect ratioaspectRatio onAspectRatioChangeImage and video, from the picked models' aspectRatios. With images attached too; startImageFit fits a video's start image
VoicespeechVoice onSpeechVoiceChangeCreate speech, from the picked model's voices
Genregenres onGenresChangeCreate music, from the picked models' genres
Frame rateframeRate onFrameRateChangeCreate world, from the models' frameRates
Layout
Menu directionmenuPlacementAuto by default. "above" for a bar docked to the bottom, "below" docked to the top
AI help
Prompt enhanceonEnhance enhancingUse useComposer's enhance. Needs ChaiProvider

Preview controls

Shows the "optimize prompt" icon button — a builder omits onEnhance entirely to hide it, e.g. before wiring an engine that can actually run the rewrite.

Shows the mic left of submit. Recording is real (your browser asks for the microphone the first time); the text it adds is a placeholder, since no speech-to-text model is connected here. In your app, voiceInput names the speech-to-text model, and there's no mic without it.

Shows an "Auto-select model" switch at the top of the Model menu so the end user can flip it themselves. Off by default — a builder can still force autoSelectModel without this, just with no visible control for the end user to change it.

Advanced2 options

multiSelectModels: the Model menu allows several picks, and each model produces its own result, paged inside one Result card. Off by default.

multiSelectUseCases: pick Create image and Create video together from the "+" menu. Each gets its own Model menu, and submit waits for a model in both. Every other use case runs alone. Off by default; see Image and video together.

menuPlacement: which way the bar's menus open. Auto, the default, opens below when the menu fits, as on a start screen, and above when it doesn't. Above for a bar docked to the bottom of the screen, below for one docked to the top. See Docking the bar.

A Create world submit opens a viewer for the world and its controls. It's a stub for now: no world model runs on Fal or OpenRouter yet. Off, the request shows below the bar like other use cases.

Make every submit — retries included — fail, to preview the retry state (the submit button becomes a danger-tinted refresh icon, with a short message above it) on demand instead of wiring a real failing engine.

Makes "Optimize prompt" return a 260+ character rewrite, to preview Composer's own common-sense bailout: past that length the rewrite just appears instantly, skipping the gradient reveal rather than dragging it out.

Suggested models

The Model menu's lists when you pass no models. They ship with the package, checked against Fal. Nothing is picked for the person: with a Model menu, they pick or turn on auto-select; with no menu, the first model runs.

Create image5 models

With images attached, each runs as its edit endpoint (withImages), still in the picked ratio.

  • Nano Banana Prostandard
  • Nano Banana 2fast
  • Flux 3 Imagestandard
  • GPT Image 2slow
  • Flux 3 Kleinfast
Create video4 models

With an image attached, each runs as its image-to-video endpoint. The image is fitted to the picked ratio first: cropped by default, or padded with startImageFit.

  • MiniMax H3 Maxslow
  • Seedance 2.5slow
  • Kling v3 Proslow
  • Veo 3.1 Faststandard
Edit image3 models
  • Flux 3 Imagestandard
  • Nano Banana Prostandard
  • GPT Image 2slow
Create world5 models

Examples for the preview, not shipped: no world model is hosted on Fal or OpenRouter yet. Pass your own with models.

  • LingBot-World 2.0fast
  • Gemini Genie 3fast
  • World-Labs-Marbleslow
  • Micro-Worldstandard
  • Matrix-Game 2.0fast
Create speech4 models

Fal text-to-speech models for the preview, not shipped: Create speech is opt-in, so you name the models. Pass them with models, each with the voices from its Fal schema.

  • Eleven v4standard
  • Gemini 3.8 Flash TTSstandard
  • xAI TTSstandard
  • Kokorofast
Create music4 models

Fal music models for the preview, not shipped: Create music is opt-in, so you name the models. Pass them with models, each with its genres. No music model takes genre as an input, so the picks are written into the prompt.

  • Lyria 3 Prostandard
  • MiniMax Music 2.6standard
  • Eleven Music v2.5slow
  • Stable Audio 2.5fast
Voice input5 models

Fal speech-to-text models, not shipped: you choose one. Paste its id into voiceInput={{ model: "…" }}.

  • Wizperfal-ai/wizper
  • ElevenLabs Scribe v2fal-ai/elevenlabs/speech-to-text/scribe-v2
  • Cohere Transcribefal-ai/cohere-transcribe
  • Nemotron ASRnvidia/nemotron-asr-multilingual/asr
  • Fal Speech-to-Text Turbofal-ai/speech-to-text/turbo

Image and video together

An advanced option for creative workflows: multiSelectUseCases makes an image and a video from one prompt. Only Image and Video combine, because they're the only use cases that can share a prompt and one Enhance rewrite; nextUseCases makes any other pick replace them. Each use case has its own Model menu and needs a model before submit. The image arrives in seconds and the video can take minutes, so show each run in its own Result card rather than paging them together.

import { useState } from "react";
import {
Composer,
ResultCard,
createFalEngine,
nextUseCases,
useComposer,
type ComposerAttachMenuAction,
type ComposerUseCase,
} from "@chai-ui/react";
const engineByKind = {
image: createFalEngine({ outputKind: "image" }),
video: createFalEngine({ outputKind: "video" }),
};
const USE_CASES: Partial<Record<ComposerAttachMenuAction, ComposerUseCase>> = {
"create-image": { kind: "image", label: "Image" },
"create-video": { kind: "video", label: "Video" },
};
export function CreateImageAndVideo() {
const [value, setValue] = useState("");
const [useCases, setUseCases] = useState<ComposerUseCase[]>([]);
const [modelIds, setModelIds] = useState<string[]>([]);
const { runs, submit, cancel } = useComposer({ engineByKind });
const busy = runs.some((r) => r.results.some((x) => x.status === "queued" || x.status === "running"));
return (
<>
<Composer
value={value}
onChange={setValue}
multiSelectUseCases
useCases={useCases}
onUseCasesChange={setUseCases}
onAttachMenuSelect={(action) => {
const picked = USE_CASES[action];
if (picked) setUseCases((prev) => nextUseCases(prev, picked));
}}
modelIds={modelIds}
onModelIdsChange={setModelIds}
onSubmit={submit}
submitting={busy}
onAbort={cancel}
/>
{/* One card per use case: the image lands in seconds, the video in minutes. */}
{runs.map((run) => (
<ResultCard key={run.id} results={run.results} prompt={run.request.prompt} onAction={() => {}} />
))}
</>
);
}