Skip to content

FreeIn reviewv1.0.0

Prompt Composer

A prompt box that grows with its text, with a model chip and a send button that wakes when there is text.
npx shadcn@latest add @vibebox/prompt-composer

Controls

<PromptComposer />

About

A small island: state for the text, auto-grow by scrollHeight, Enter to send. It has no model or network code.

Use it when

  • Any chat or command surface needs an input.

Skip it when

  • You need attachments or voice: wrap this and add them beside the model chip.

Install

  1. 1Add the VibeBox registry to components.json:
    {
      "registries": {
        "@vibebox": "https://motion.vibeboxph.com/r/{name}.json"
      }
    }
  2. 2Add the component with the shadcn CLI:
    npx shadcn@latest add @vibebox/prompt-composer
  3. 3Use it:
    import { PromptComposer } from "@/components/vibebox/prompt-composer";
    
    <PromptComposer accent="#7c5cff" />

AI prompt

Everything an AI coding tool needs to rebuild this component the way it looks here: colours, fonts, the art it draws, props and rules.

Read the prompt
Build the VibeBox Motion component "Prompt Composer" (AI and agents) in a Next.js App Router project with Tailwind CSS v4.

What it is
- A prompt box that grows with its text, with a model chip and a send button that wakes when there is text.
- The input of an assistant: a textarea that grows to a limit, a chip for the model, and a send button in the accent that is disabled while the box is empty. Enter sends and Shift+Enter breaks the line; it calls your `onSend` with the trimmed text and clears itself.

Install
- `npx shadcn@latest add @vibebox/prompt-composer`
- Registry: add `"@vibebox": "https://motion.vibeboxph.com/r/{name}.json"` to components.json, then run the command. The source lands in `components/vibebox/prompt-composer` and is yours to edit.

Visual spec
- Colours (the VibeBox spectrum, the same in light and dark):
    - --spectrum-1: #00c5fc (cyan)
    - --spectrum-2: #1d6ef0 (blue)
    - --spectrum-3: #5225ed (indigo)
    - --spectrum-4: #e434dc (magenta)
    - --spectrum-5: #fd3aa9 (pink)
    - --spectrum-6: #f98472 (coral)
    - --spectrum-7: #fcbe72 (amber)
- Ink surface (dark): background #05070c, card #0a0d15, text #f5f6fa, muted text #a3abbd, border rgba(255,255,255,0.08).
- Paper surface (light): background #f3f0ea, card #fbfaf7, text #0c0e14, muted text #4a4f5c, border rgba(12,14,20,0.12).
- Fonts:
    - Display and body: Figtree (400, 500, 600, 700), falling back to ui-sans-serif, system-ui.
    - Emphasis: Fraunces italic, for one or two words of a heading.
    - Labels and code: JetBrains Mono (400, 500), uppercase labels with 0.14em tracking.
- Shape: radius 8 to 12px on cards and panels; pills may be fully round. Hairline borders, soft glow instead of heavy shadow.
- Motion: animate transform and opacity only; fast ease-out (cubic-bezier(0.16, 1, 0.3, 1)), 140ms for micro-interactions, 320 to 520ms for reveals.

Art and media
- Built from HTML, CSS and inline SVG only; no image files or video.

Props
- `placeholder`: `string` (default "Ask anything"). The empty field's hint.
- `model`: `string` (default "Assistant"). The label on the model chip.
- `onSend`: `(text: string) => void`. Called with the trimmed text when the visitor sends.
- `accent`: `string` (default "blue"). The accent colour: blue, violet, emerald, amber, rose, or any CSS colour. It sets --app-accent on the root, so a parent can set it once for a whole screen.
- `className`: `string`. Merged with cn(); give it a width.

Behaviour and access
- Reduced motion: Nothing animates on its own.
- Keyboard: Enter sends; Shift+Enter inserts a line break; Tab reaches the send button.
- Screen readers: Semantic elements with accessible names; decorative marks are hidden.
- Touch: Targets are 36 px or larger; nothing depends on hover.
- Budget: about 1 KB of JavaScript, WebGL none, GPU none. It pauses while off screen.

Theming
Everything reads `--app-accent` (set by the `accent` prop, or by a parent) and the mode's tokens, so light and dark and any brand colour work without code changes. Pass a named accent or any CSS colour; Launchpad sets it once per app from the client's theme.

Usage
```tsx
import { PromptComposer } from "@/components/vibebox/prompt-composer";

<PromptComposer accent="#7c5cff" />
```

Notes
Add the VibeBox Motion Prompt Composer with `npx shadcn@latest add @vibebox/prompt-composer`. Wire `onSend` to your request function. Set the brand colour once with `--app-accent` on a parent (or the `accent` prop). It follows light and dark through the tokens; keep the licence header.

Rules
- Keep the licence header on every installed file.
- Original code only: do not copy components from other libraries, and do not add GSAP.
- Use tokens for colour, type and radius; no hard-coded brand values.
- Server Components by default; `"use client"` only for the island.

Code

2 files
components/vibebox/prompt-composer/index.tsx
/**
 * VibeBox Motion · Prompt Composer
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
export { PromptComposer, type PromptComposerProps } from "./prompt-composer.client";
components/vibebox/prompt-composer/prompt-composer.client.tsx
/**
 * VibeBox Motion · Prompt Composer
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
"use client";

import { useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { accentStyle } from "@/lib/vibebox/app-accent";

export type PromptComposerProps = { placeholder?: string; model?: string; onSend?: (text: string) => void; accent?: string; className?: string };

/** A growing text box with a send button that wakes when there is text; Enter sends, Shift+Enter breaks the line. */
export function PromptComposer({ placeholder = "Ask anything", model = "Assistant", onSend, accent, className }: PromptComposerProps) {
  const [text, setText] = useState("");
  const ref = useRef<HTMLTextAreaElement>(null);
  const send = () => {
    const value = text.trim();
    if (!value) return;
    onSend?.(value);
    setText("");
    if (ref.current) ref.current.style.height = "";
  };
  return (
    <form
      data-vb="prompt-composer"
      style={accentStyle(accent)}
      className={cn("flex flex-col gap-2 rounded-xl border border-border bg-card p-2.5 shadow-sm focus-within:border-[var(--app-accent)]", className)}
      onSubmit={(event) => {
        event.preventDefault();
        send();
      }}
    >
      <label className="sr-only" htmlFor="vb-composer">Message</label>
      <textarea
        id="vb-composer"
        ref={ref}
        rows={1}
        value={text}
        placeholder={placeholder}
        onChange={(event) => {
          setText(event.target.value);
          event.target.style.height = "auto";
          event.target.style.height = `${Math.min(event.target.scrollHeight, 176)}px`;
        }}
        onKeyDown={(event) => {
          if (event.key === "Enter" && !event.shiftKey) {
            event.preventDefault();
            send();
          }
        }}
        className="max-h-44 min-h-10 w-full resize-none bg-transparent px-2 py-1.5 text-[1rem] outline-none placeholder:text-subtle-foreground"
      />
      <div className="flex items-center justify-between gap-2">
        <span className="rounded-md bg-muted px-2 py-1 font-mono text-caption text-muted-foreground">{model}</span>
        <button
          type="submit"
          disabled={!text.trim()}
          aria-label="Send"
          className="inline-grid size-9 place-items-center rounded-lg bg-[var(--app-accent)] text-[var(--app-accent-fg,#fff)] transition-opacity enabled:hover:opacity-90 disabled:opacity-35 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
        >
          <svg viewBox="0 0 24 24" aria-hidden="true" className="size-4" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
        </button>
      </div>
    </form>
  );
}

Props

PropTypeDefaultDescription
placeholderstring"Ask anything"The empty field's hint.
modelstring"Assistant"The label on the model chip.
onSend(text: string) => void—Called with the trimmed text when the visitor sends.
accentstring"blue"The accent colour: blue, violet, emerald, amber, rose, or any CSS colour. It sets --app-accent on the root, so a parent can set it once for a whole screen.
classNamestring—Merged with cn(); give it a width.

Accessibility and motion

Keyboard
Enter sends; Shift+Enter inserts a line break; Tab reaches the send button.
Screen readers
Semantic elements with accessible names; decorative marks are hidden.
Touch
Targets are 36 px or larger; nothing depends on hover.
Reduced motion
Nothing animates on its own.

Edge cases and limits

  • IME composition: Enter during composition still sends in some browsers; pass your own guard if needed.
  • The button is the only control; there is no stop button.
  • A vertical timeline with two ticked steps, one spinning step and one queued step

    A timeline of what an agent is doing: done steps ticked, the running one spinning, the rest queued.

    AI and agents · Server

  • A chat with an accent-coloured message on the right and grey replies on the left

    A chat conversation: your messages in the accent, replies on a card, and a typing indicator.

    AI and agents · Server

  • A collapsed card with a tool name, a status dot and a chevron, opening to arguments and a result

    A tool call an agent made: name and status on a line, arguments and result under a disclosure.

    AI and agents · Server

↑ ↓ to move↵ to openesc to close