You Might Also Like
AI Prompt Composer — Chat Input HTML CSS JS Snippet
AI Prompt Composer · Forms · Plain HTML, CSS & JS · Live preview
What's included
Features
onInput sets height to auto then scrollHeight (capped at 180px), so it expands and — crucially — shrinks as you edit.onKey sends on Enter and preventDefaults the newline, while Shift+Enter inserts a line break — the chat convention.MAX plus an approximate token count (chars ÷ 4), turning red and blocking send over the limit.updateSend enables Send when there is text OR an attachment, and disables it past the character cap.:focus-within lights up the whole composer so the textarea, toolbar, and controls read as one input.About this UI Snippet
AI Prompt Composer — Auto-Grow Textarea, Model Picker, Attachments & Token Counter

Every AI product needs a great prompt composer — the input box where users type, attach context, pick a model, and send. It looks simple but has real interaction depth: the textarea must grow with the content (then cap and scroll), Enter should send while Shift+Enter inserts a newline, a counter should track length and approximate tokens, attachments need to be added and removed, and Send must be disabled when there is nothing to send. This snippet implements all of it in plain HTML, CSS, and vanilla JavaScript.
Auto-growing textarea
onInput resets the textarea height to auto and sets it to scrollHeight, capped at a max (180px) after which it scrolls. Resetting to auto first is the key trick — without it the textarea can only ever grow, never shrink when you delete lines. This gives the now-standard composer feel: one line to start, expanding smoothly as you type a paragraph, then scrolling for very long prompts.
Enter to send, Shift+Enter for newline
onKey intercepts Enter: pressing it alone calls send (and preventDefault stops the newline), while Shift+Enter falls through to insert a line break. This is the convention users expect from chat and AI interfaces, and getting it right is what makes the composer feel native.
Live character and token counter
onInput updates a counter showing characters against a MAX and an approximate token count (Math.ceil(length / 4) — the rough chars-per-token heuristic). It turns red past the limit and the Send button disables, mirroring how real AI apps cap context length.
Model picker and attachments
A model pill cycles through options (Opus 4.8, Sonnet 4.6, Haiku 4.5) on click — the model-selector affordance every multi-model app has. The attach button adds removable file chips that animate in with a pop; updateSend enables Send when there is either text or an attachment, so you can send a file with no message. Each chip has a × that removes it and re-evaluates the Send state.
Send and reset
send clears the text, collapses the textarea, removes attachments, resets the counter, disables Send, and shows a confirmation toast naming the chosen model — standing in for dispatching the message. The whole box lights up with a focus ring via :focus-within so the composite reads as one control.
Replace send with your real API call (stream the response into a message list) and you have a production composer. Pair this with an AI chat interface for the conversation view, a chat UI for messaging, or an auto-resize textarea for simpler inputs.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA prompt composer appears with a single-line input, a model pill ("Opus 4.8"), an attach button, a counter, and a disabled Send button.
- 2Type a messageThe textarea grows line by line as you type; the counter shows characters and an approximate token count, and Send enables.
- 3Press Enter to sendEnter sends and clears the box; Shift+Enter inserts a newline instead, exactly like a chat app.
- 4Switch modelsClick the model pill to cycle through Opus 4.8 → Sonnet 4.6 → Haiku 4.5.
- 5Attach filesClick the attach button to add removable file chips; you can send with just an attachment and no text.
- 6SendHit Send — the box resets and a toast confirms "Message sent to <model>".
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Replace the body of send with your request: read input.value, the selected MODELS[modelIdx], and any attachments, then call your endpoint and stream the response into a message list. Keep the optimistic reset (clear the box immediately) so the UI feels instant, and disable Send while a request is in flight to prevent duplicate submissions.
The chars / 4 estimate is a rough heuristic — real tokenisation depends on the model's tokenizer and the actual text (code and non-English vary a lot). For an accurate count, run the text through the provider's tokenizer (or a token-counting endpoint) and display that instead. The estimate here is fine for a soft "how long is my prompt" gauge.
scrollHeight only reports how tall the content needs to be relative to the current height. If you never reset to auto, the element can grow but never shrink, because its scrollHeight stays inflated by its own current height. Setting height = 'auto' first lets the browser recompute the true content height so the box both grows and collapses correctly.
Wire the attach button to a hidden <input type="file" multiple> and read event.target.files. Render a chip per file (name, size), validate type and size before accepting, and on send upload them (or send as multipart / base64) alongside the prompt. Revoke any object URLs you create for previews to avoid memory leaks.
In React, hold the text, model index, and attachments in useState; auto-grow in an effect or the onChange handler using a ref to the textarea, and handle Enter in onKeyDown. In Vue, use v-model with a watcher for auto-grow. In Angular, [(ngModel)] plus an input handler. The height-reset trick and key handling port unchanged.