Gemini Compose
A copy-paste block that recreates the Gemini prompt stage with Google's rail, brand mark, prompt surface, model menu, and tool toggles. The block is not a package component: copy the file, or install it with the shadcn CLI, then edit the layout like any other file in your project. It is assembled entirely from Agent UI compose primitives, and every control is keyboard accessible.
Source you own: install the block with the shadcn CLI or copy components/blocks/composes/gemini-compose.tsx. The preview below is the real file, and the code tab is the exact source.
Happy Diwali!
Google Terms and the Google Privacy Policy apply. Gemini is AI and can make mistakes.
The code tab contains the full block, ready to copy.
Installation
Add the block with the shadcn CLI. It writes components/blocks/composes/gemini-compose.tsx and installs its dependencies.
npx shadcn@latest add https://agent-ui-27t.pages.dev/r/gemini-compose.jsonPrefer to own the file manually? Install the dependencies, then copy the source from the Files section.
pnpm add @agent-ui/react lucide-reactTailwind CSS v4 users: add @source "../node_modules/@agent-ui/react/src" to the global stylesheet so the library utilities are generated.
Usage
Import the copied file and render it anywhere. The block accepts onSend, disabled, and className; wire onSend to any backend.
import { GeminiCompose } from "@/components/blocks/composes/gemini-compose"; <GeminiCompose onSend={(text) => console.log(text)} />Files
One file. The same source is served by the shadcn registry endpoint, so the CLI and the docs can never drift.
components/blocks/composes/gemini-compose.tsxtsx"use client"; import * as React from "react";import { ArrowUp, Image as ImageIcon, Mic, PanelLeft, Plus, Settings, Sparkles,} from "lucide-react";import { ComposeDisclaimer, ComposeSendButton, ComposeTextarea, ModelChip, createCompose, useCompose, type ComposeProps,} from "@agent-ui/react"; const Root = createCompose({ id: "gemini", maxWidth: "max-w-none", placeholder: "Type @ for connectors, skills and more", models: [ { id: "flash-lite", name: "Flash-Lite" }, { id: "pro", name: "Pro" }, { id: "ultra", name: "Ultra" }, ], defaultModelId: "flash-lite",}); function GeminiSpark({ className }: { className?: string }) { return ( <svg viewBox="0 0 24 24" className={className} aria-hidden="true"> <defs> <linearGradient id="gemini-spark" x1="2" y1="2" x2="22" y2="22" gradientUnits="userSpaceOnUse" > <stop stopColor="#4285f4" /> <stop offset="0.5" stopColor="#9b72cb" /> <stop offset="1" stopColor="#d96570" /> </linearGradient> </defs> <path d="M12 1.8c.65 5.7 4.5 9.55 10.2 10.2-5.7.65-9.55 4.5-10.2 10.2C11.35 16.5 7.5 12.65 1.8 12 7.5 11.35 11.35 7.5 12 1.8Z" fill="url(#gemini-spark)" /> </svg> );} /** Gemini app: cool grey rail, warm halo canvas, floating capsule composer. */function GeminiComposeLayout() { const { send } = useCompose(); return ( <div className="flex min-h-[400px] w-full bg-white text-[13px] text-[#1f1f1f]"> <aside className="hidden w-[172px] shrink-0 flex-col @[560px]:flex bg-[#f0f1f3] px-2.5 py-3"> <div className="flex items-center gap-2 px-1"> <GeminiSpark className="h-[18px] w-[18px]" /> <span className="text-[15px] font-medium tracking-tight">Gemini</span> <PanelLeft className="ml-auto h-4 w-4 opacity-50" aria-hidden="true" /> </div> <nav className="mt-5 flex flex-col gap-0.5"> <button type="button" disabled className="flex items-center gap-2.5 rounded-full px-2 py-1.5 text-left text-[12px] text-black/35" > <Sparkles className="h-[15px] w-[15px]" aria-hidden="true" /> New chat </button> <button type="button" className="flex items-center gap-2.5 rounded-full px-2 py-1.5 text-left text-[12px] text-[#1f1f1f] hover:bg-black/[0.05]" > <ImageIcon className="h-[15px] w-[15px]" aria-hidden="true" /> Images </button> <button type="button" className="mt-1 flex items-center gap-1.5 rounded-full px-2 py-1.5 text-left text-[10.5px] text-[#444746] underline decoration-dotted underline-offset-2" > Sign in to save activity </button> </nav> <div className="mt-auto flex flex-col gap-1.5 px-0.5"> <button type="button" className="flex items-center gap-2 rounded-full px-2 py-1.5 text-[12px] hover:bg-black/[0.05]" > <Settings className="h-[15px] w-[15px]" aria-hidden="true" /> Settings </button> <button type="button" className="w-full rounded-full bg-[#a8c7fa] py-1.5 text-[11.5px] font-medium text-[#062e6f]" > Sign in </button> <button type="button" className="w-full rounded-full bg-[#e3e5e8] py-1.5 text-[11.5px] font-medium text-[#1f1f1f]" > Get App </button> </div> </aside> <div className="relative flex min-w-0 flex-1 flex-col items-center overflow-hidden px-5 pb-7 pt-8"> <div aria-hidden="true" className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_62%_60%_at_52%_46%,#fde68a_0%,#fef3c7_38%,rgba(255,255,255,0)_72%)]" /> <div className="mb-2 flex items-center gap-3 self-end text-[11px]"> <span className="text-[#0b57d0]">About Gemini</span> <span className="text-[#0b57d0]">Subscriptions</span> <span className="text-[#0b57d0]">For Business</span> <span className="rounded-full bg-[#e9eef6] px-3 py-1.5 text-[#1f1f1f]">Get App</span> <span className="rounded-full bg-[#a8c7fa] px-3 py-1.5 font-medium text-[#062e6f]"> Sign in </span> </div> <div className="relative mt-auto flex w-full flex-col items-center"> <GeminiSpark className="h-8 w-8" /> <h1 className="mt-3 text-center text-[25px] font-normal tracking-[-0.01em] text-[#1f1f1f]"> Happy Diwali! </h1> <div data-agent-id="compose-card" className="mt-5 flex w-full max-w-[452px] items-center gap-1.5 rounded-full bg-white py-1.5 pl-3 pr-1.5 shadow-[0_1px_3px_rgba(0,0,0,0.12),0_4px_12px_rgba(0,0,0,0.06)] transition-shadow duration-200 focus-within:shadow-[0_1px_4px_rgba(0,0,0,0.14),0_6px_20px_rgba(11,87,208,0.12)]" > <Plus className="h-5 w-5 shrink-0 text-[#1f1f1f]" aria-hidden="true" /> <ComposeTextarea aria-label="Ask Gemini" placeholder="Type @ for connectors, skills and more" rows={1} className="min-w-0 flex-1 px-1 py-1 text-[13px] leading-5 text-[#1f1f1f] placeholder:text-[#5f6368]/70" /> <ModelChip tone="light" className="shrink-0 [&>button]:h-7 [&>button]:gap-0.5 [&>button]:px-1 [&>button]:text-[11.5px] [&>button]:text-[#3c4043] [&>button]:hover:bg-transparent" /> <button type="button" aria-label="Use microphone" className="grid h-7 w-7 shrink-0 place-items-center rounded-full hover:bg-black/5" > <Mic className="h-[16px] w-[16px] text-[#3c4043]" aria-hidden="true" /> </button> <ComposeSendButton aria-label="Send message" className="h-7 w-7 shrink-0 bg-[#0b57d0] text-white hover:bg-[#0b57d0]/90 disabled:opacity-0 disabled:pointer-events-none" icon={<ArrowUp className="h-[15px] w-[15px]" strokeWidth={2.4} />} /> </div> <div className="mt-2 w-full max-w-[452px] pl-2"> <button type="button" onClick={() => send("What can you do?")} className="flex items-center gap-2 rounded-lg px-1 py-1 text-[12px] text-[#444746] hover:bg-black/5" > <span aria-hidden="true" className="text-[15px] leading-none"> ↳ </span> What can you do? </button> </div> </div> <ComposeDisclaimer className="relative mt-auto pt-6 text-[9.5px] text-[#5f6368]"> Google Terms and the Google Privacy Policy apply. Gemini is AI and can make mistakes. </ComposeDisclaimer> </div> </div> );} export function GeminiCompose(props: ComposeProps) { return ( <Root {...props}> <GeminiComposeLayout /> </Root> );} Built with
The block imports these Agent UI primitives. Each one is a package export with its own docs page, so you can change the layout without giving up the behaviour.
Accessibility
Gemini also has a full product page.
Metadata, model list, and the same block in a dedicated route.
GeminiCompose composes with MessageList, ChatShell, useChat, and the provider compose blocks. Browse the full reference or see all components in the gallery.
193 lines · copy-ready