AI Prompt Input
Overview
An auto-growing prompt input with a model switcher, attachment button and sending state. The placeholder cycles through things you can ask for.
Plain HTML, CSS and a little vanilla JS, with no framework dependency. Copy the prompt into Claude Code, Cursor, Trae or Codex and the model adds AI Prompt Input to your project in your colors.
Where to use it
Search boxes, AI chat inputs, form fields, file uploads, verification codes and anywhere else people type.
Install
With Builder, install it with the 8pt CLI or call it directly from an editor connected to 8pt MCP.
$ npm i -g @8pt-dev/cli && 8pt add ai-prompt-input
mcp › Use 8pt MCP to call get_component("ai-prompt-input")
Code
Source for AI Prompt Input. Motion uses only transform and opacity and respects the Reduce motion setting.
<form class="x-ai">
<textarea rows="1" aria-label="Prompt"></textarea>
<div class="x-ai__bar">
<button type="button" class="x-ai__icon" aria-label="Add attachment">+</button>
<div class="x-ai__model">
<button type="button" class="x-ai__pill">Claude <b>▾</b></button>
<ul class="x-ai__menu" role="listbox">
<li>Claude</li><li>GPT</li><li>DeepSeek</li><li>Kimi</li><li>Qwen</li><li>Doubao</li>
</ul>
</div>
<button type="submit" class="x-ai__send" aria-label="Send" disabled>↑</button>
</div>
….x-ai{width:min(440px,100%);padding:14px 14px 10px;border-radius:18px;background:var(--d-card,#101016);border:1px solid var(--d-line,rgba(255,255,255,.1));box-shadow:0 20px 50px -30px rgba(0,0,0,.6);transition:border-color .25s}
.x-ai:focus-within{border-color:color-mix(in srgb,var(--d-accent,#4D74FF) 60%,transparent)}
.x-ai textarea{width:100%;min-height:44px;max-height:140px;resize:none;border:0;outline:0;background:transparent;color:var(--d-fg,#fff);font:15px/1.5 var(--d-font,system-ui,sans-serif)}
.x-ai textarea::placeholder{color:var(--d-muted,#8a8a95)}
.x-ai__bar{display:flex;align-items:center;gap:8px;margin-top:6px}
.x-ai__icon,.x-ai__pill{height:30px;border-radius:999px;border:1px solid var(--d-line,rgba(255,255,255,.1));background:transparent;color:var(--d-muted,#a0a0ab);font:500 13px/1 var(--d-font,system-ui,sans-serif);cursor:pointer}
.x-ai__icon{width:30px}
.x-ai__pill{padding:0 12px}
.x-ai__pill b{font-size:10px;margin-left:4px}
.x-ai__model{position:relative}
.x-ai__menu{position:absolute;left:0;bottom:38px;z-index:5;min-width:140px;margin:0;padding:6px;list-style:none;border-radius:12px;background:var(--d-card,#101016);border:1px solid var(--d-line,rgba(255,255,255,.12));box-shadow:0 18px 40px -16px rgba(0,0,0,.6);opacity:0;transform:translateY(6px) scale(.98);transform-origin:bottom left;pointer-events:none;transition:opacity .18s,transform .18s}
.x-ai__model.open .x-ai__menu{opacity:1;transform:none;pointer-events:auto}
…const init = (root) => {
const f = root.querySelector('.x-ai'), ta = f.querySelector('textarea'), send = f.querySelector('.x-ai__send');
const model = f.querySelector('.x-ai__model'), pill = f.querySelector('.x-ai__pill');
const hints = ['Build a pricing section with a yearly toggle', 'Change this form to 8pt spacing', 'Generate a dashboard stat card'];
let hi = 0, ci = 0, timer;
const type = () => {
const s = hints[hi]; ta.placeholder = s.slice(0, ++ci);
timer = setTimeout(ci < s.length ? type : () => { ci = 0; hi = (hi + 1) % hints.length; type(); }, ci < s.length ? 70 : 1800);
};
type();
ta.addEventListener('input', () => { ta.style.height = 'auto'; ta.style.height = ta.scrollHeight + 'px'; send.disabled = !ta.value.trim(); });
pill.addEventListener('click', () => model.classList.toggle('open'));
…Subscribe to Builder to see the full source.See pricing