AI Prompt Box (Dark Glass)
A sleek dark prompt box for AI apps: glowing hairline frame, auto-growing input, tool buttons, a metallic send button with a flying paper plane, and suggestion chips.
Preview and code
<!--
MIT License
Copyright (c) 2026 OwnlyPlans
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-->
<form class="ask" id="ask" novalidate>
<div class="ask-thread" id="ask-thread" aria-live="polite" aria-label="Sent messages"></div>
<div class="ask-frame"><div class="ask-card">
<label class="ask-sr" for="ask-in">Message</label>
<textarea class="ask-input" id="ask-in" rows="2" maxlength="2000" placeholder="Imagine something... ✦"></textarea>
<div class="ask-bar">
<div class="ask-tools">
<button type="button" class="ask-tool" data-tip="Attach a file" aria-label="Attach a file"><svg viewBox="0 0 24 24"><path d="M20.5 11.5 12.4 19.6a5 5 0 0 1-7.1-7.1l8.5-8.5a3.3 3.3 0 0 1 4.7 4.7l-8.5 8.5a1.7 1.7 0 0 1-2.4-2.4l7.8-7.8"/></svg></button>
<button type="button" class="ask-tool" data-tip="Add an image" aria-label="Add an image"><svg viewBox="0 0 24 24"><rect x="3.5" y="4.5" width="17" height="15" rx="3"/><circle cx="9" cy="10" r="1.8"/><path d="m20.5 16-4.6-4.6L6 19.5"/></svg></button>
<button type="button" class="ask-tool" data-tool="web" data-tip="Search the web" aria-label="Search the web" aria-pressed="false"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.4 2.3 2.4 14.7 0 17M12 3.5c-2.4 2.3-2.4 14.7 0 17"/></svg></button>
</div>
<div class="ask-right"><span class="ask-count" id="ask-count">0/2000</span>
<button type="button" class="ask-send" id="ask-send" aria-label="Send" disabled><span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 3 10.5 13.5M21 3l-6.5 18-4-7.5L3 9.5z"/></svg></span></button></div>
</div>
</div></div>
<div class="ask-chips" aria-label="Suggestions">
<button type="button" class="ask-chip">Create an image</button>
<button type="button" class="ask-chip">Analyse data</button>
<button type="button" class="ask-chip">Write a plan</button>
</div>
</form>
/*
* MIT License
*
* Copyright (c) 2026 OwnlyPlans
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
.ask{--ask-w:320px;--ask-ink:#f3f5fa;--ask-dim:rgba(255,255,255,.32);--ask-bg:#0d0d0f;
width:min(var(--ask-w),100%);display:grid;gap:12px;font:13px/1.45 system-ui,-apple-system,"Segoe UI",sans-serif;color:var(--ask-ink)}
.ask-thread{display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-end;gap:6px;max-height:84px;overflow:hidden;-webkit-mask-image:linear-gradient(to bottom,transparent,#000 30%);mask-image:linear-gradient(to bottom,transparent,#000 30%)}
.ask-thread:empty{display:none}
.ask-msg{max-width:85%;padding:7px 11px;border-radius:14px 14px 4px 14px;background:linear-gradient(180deg,#2c2c31,#1d1d21);border:1px solid #34343a;color:var(--ask-ink);font-size:12px;animation:ask-in .35s cubic-bezier(.2,.8,.2,1);overflow-wrap:anywhere}
@keyframes ask-in{from{transform:translateY(8px) scale(.96);opacity:0}}
/* the frame: a 1.5px hairline that is bright at the top-left and fades into the card */
.ask-frame{position:relative;padding:1.5px;border-radius:18px;background:linear-gradient(135deg,#8a8a92 0%,#3a3a40 30%,#2a2a2f 100%);box-shadow:0 18px 40px -18px rgba(0,0,0,.8)}
.ask-frame::before{content:"";position:absolute;top:-12px;left:-12px;width:34px;height:34px;border-radius:50%;pointer-events:none;
background:radial-gradient(circle,rgba(255,255,255,.95) 0%,rgba(255,255,255,.35) 22%,rgba(255,255,255,.08) 45%,transparent 70%);filter:blur(1px)}
.ask-frame:focus-within{background:linear-gradient(135deg,#b9b9c2 0%,#4a4a52 35%,#2d2d33 100%)}
.ask-card{border-radius:16.5px;background:radial-gradient(120% 140% at 0% 0%,#1f1f23 0%,var(--ask-bg) 60%);overflow:hidden}
.ask-input{display:block;width:100%;min-height:58px;max-height:120px;padding:13px 14px 4px;border:0;background:transparent;color:var(--ask-ink);font:inherit;resize:none;outline:none;
scrollbar-width:thin;scrollbar-color:#555 transparent}
.ask-input::placeholder{color:#e9ecf3;opacity:.9;transition:opacity .25s}
.ask-input:focus::placeholder{opacity:.28}
.ask-bar{display:flex;align-items:flex-end;justify-content:space-between;padding:6px 10px 10px 8px}
.ask-tools{display:flex;gap:2px}
.ask-tool{position:relative;display:grid;place-items:center;width:30px;height:30px;padding:0;border:0;border-radius:9px;background:none;color:var(--ask-dim);cursor:pointer;transition:color .25s,transform .25s,background .25s}
.ask-tool svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.ask-tool:hover,.ask-tool:focus-visible{color:#fff;transform:translateY(-3px);outline:none}
.ask-tool:focus-visible{background:rgba(255,255,255,.08)}
.ask-tool[aria-pressed=true]{color:#8ab4ff}
.ask-tool[data-tip]::after{content:attr(data-tip);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%) translateY(4px);padding:3px 7px;border-radius:6px;background:#2a2a2f;color:#fff;font-size:10.5px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .2s,transform .2s}
.ask-tool:hover::after,.ask-tool:focus-visible::after{opacity:1;transform:translateX(-50%)}
.ask-right{display:flex;align-items:center;gap:8px}
.ask-count{color:var(--ask-dim);font-size:10.5px;font-variant-numeric:tabular-nums;opacity:0;transition:opacity .2s}
.ask-card:focus-within .ask-count{opacity:1}
/* metallic send button with a glossy top edge */
.ask-send{display:grid;place-items:center;padding:2px;border:0;border-radius:11px;cursor:pointer;background:linear-gradient(to top,#28282c,#58585f,#28282c);
box-shadow:inset 0 6px 2px -4px rgba(255,255,255,.5),0 6px 14px -6px rgba(0,0,0,.8);transition:transform .15s,filter .2s}
.ask-send span{display:grid;place-items:center;width:32px;height:32px;border-radius:9px;background:rgba(0,0,0,.12);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);color:#8d8d94;overflow:hidden}
.ask-send svg{width:17px;height:17px;transition:transform .3s cubic-bezier(.3,1.4,.5,1),color .25s,filter .25s}
.ask-send:not(:disabled):hover svg,.ask-send:focus-visible svg{color:#f3f6fd;filter:drop-shadow(0 0 5px #fff);transform:rotate(45deg) translate(-1px,1px) scale(1.12)}
.ask-send:active:not(:disabled){transform:scale(.92)}
.ask-send:disabled{cursor:default;filter:grayscale(1) brightness(.75)}
.ask-send:focus-visible{outline:2px solid rgba(255,255,255,.7);outline-offset:2px}
.ask-send.is-fly svg{animation:ask-fly .55s cubic-bezier(.5,0,.75,0) forwards}
@keyframes ask-fly{0%{transform:rotate(45deg) scale(1.1)}35%{transform:rotate(45deg) translate(-4px,4px) scale(1)}100%{transform:rotate(45deg) translate(40px,-40px) scale(.6);opacity:0}}
.ask-chips{display:flex;flex-wrap:wrap;gap:6px}
.ask-chip{height:28px;padding:0 11px;border:1.5px solid #34343a;border-radius:10px;background:#18181b;color:#e9ecf3;font:500 11.5px system-ui,sans-serif;cursor:pointer;transition:background .2s,border-color .2s,transform .2s}
.ask-chip:hover,.ask-chip:focus-visible{background:#26262b;border-color:#55555d;transform:translateY(-2px);outline:none}
.ask-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
@media (prefers-reduced-motion:reduce){.ask-msg,.ask-send.is-fly svg{animation:none}.ask-tool:hover,.ask-chip:hover{transform:none}}
/*
* MIT License
*
* Copyright (c) 2026 OwnlyPlans
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* AI Prompt Box. Listen for "ask:submit" (detail: { text, tools }) to send the prompt to your AI. */
const form = document.getElementById('ask'), input = document.getElementById('ask-in'), send = document.getElementById('ask-send'),
count = document.getElementById('ask-count'), thread = document.getElementById('ask-thread');
const max = input.maxLength;
function fit() { input.style.height = 'auto'; input.style.height = Math.min(input.scrollHeight, 120) + 'px'; }
function sync() { const n = input.value.length; count.textContent = n + '/' + max; send.disabled = !input.value.trim(); fit(); }
input.addEventListener('input', sync);
input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) { e.preventDefault(); submit(); } });
send.addEventListener('click', submit);
form.querySelectorAll('.ask-tool[data-tool]').forEach((b) => b.addEventListener('click', () => b.setAttribute('aria-pressed', String(b.getAttribute('aria-pressed') !== 'true'))));
form.querySelectorAll('.ask-chip').forEach((c) => c.addEventListener('click', () => { input.value = c.textContent + ': '; sync(); input.focus(); input.setSelectionRange(input.value.length, input.value.length); }));
/* No real form submit: works inside sandboxed previews too. */
form.addEventListener('submit', (e) => { e.preventDefault(); submit(); });
function submit() {
const text = input.value.trim();
if (!text) return;
const tools = [...form.querySelectorAll('.ask-tool[aria-pressed=true]')].map((b) => b.dataset.tool);
send.classList.remove('is-fly'); void send.offsetWidth; send.classList.add('is-fly');
setTimeout(() => send.classList.remove('is-fly'), 600);
const m = document.createElement('div'); m.className = 'ask-msg'; m.textContent = text; thread.append(m);
while (thread.children.length > 3) thread.firstElementChild.remove();
input.value = ''; sync(); input.focus();
form.dispatchEvent(new CustomEvent('ask:submit', { bubbles: true, detail: { text, tools } }));
}
sync();
/*
* MIT License
*
* Copyright (c) 2026 OwnlyPlans
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
"use client";
import { useEffect, useRef, useState } from "react";
const css = `.ask{--ask-w:320px;--ask-ink:#f3f5fa;--ask-dim:rgba(255,255,255,.32);--ask-bg:#0d0d0f;
width:min(var(--ask-w),100%);display:grid;gap:12px;font:13px/1.45 system-ui,-apple-system,"Segoe UI",sans-serif;color:var(--ask-ink)}
.ask-thread{display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-end;gap:6px;max-height:84px;overflow:hidden;-webkit-mask-image:linear-gradient(to bottom,transparent,#000 30%);mask-image:linear-gradient(to bottom,transparent,#000 30%)}
.ask-thread:empty{display:none}
.ask-msg{max-width:85%;padding:7px 11px;border-radius:14px 14px 4px 14px;background:linear-gradient(180deg,#2c2c31,#1d1d21);border:1px solid #34343a;color:var(--ask-ink);font-size:12px;animation:ask-in .35s cubic-bezier(.2,.8,.2,1);overflow-wrap:anywhere}
@keyframes ask-in{from{transform:translateY(8px) scale(.96);opacity:0}}
/* the frame: a 1.5px hairline that is bright at the top-left and fades into the card */
.ask-frame{position:relative;padding:1.5px;border-radius:18px;background:linear-gradient(135deg,#8a8a92 0%,#3a3a40 30%,#2a2a2f 100%);box-shadow:0 18px 40px -18px rgba(0,0,0,.8)}
.ask-frame::before{content:"";position:absolute;top:-12px;left:-12px;width:34px;height:34px;border-radius:50%;pointer-events:none;
background:radial-gradient(circle,rgba(255,255,255,.95) 0%,rgba(255,255,255,.35) 22%,rgba(255,255,255,.08) 45%,transparent 70%);filter:blur(1px)}
.ask-frame:focus-within{background:linear-gradient(135deg,#b9b9c2 0%,#4a4a52 35%,#2d2d33 100%)}
.ask-card{border-radius:16.5px;background:radial-gradient(120% 140% at 0% 0%,#1f1f23 0%,var(--ask-bg) 60%);overflow:hidden}
.ask-input{display:block;width:100%;min-height:58px;max-height:120px;padding:13px 14px 4px;border:0;background:transparent;color:var(--ask-ink);font:inherit;resize:none;outline:none;
scrollbar-width:thin;scrollbar-color:#555 transparent}
.ask-input::placeholder{color:#e9ecf3;opacity:.9;transition:opacity .25s}
.ask-input:focus::placeholder{opacity:.28}
.ask-bar{display:flex;align-items:flex-end;justify-content:space-between;padding:6px 10px 10px 8px}
.ask-tools{display:flex;gap:2px}
.ask-tool{position:relative;display:grid;place-items:center;width:30px;height:30px;padding:0;border:0;border-radius:9px;background:none;color:var(--ask-dim);cursor:pointer;transition:color .25s,transform .25s,background .25s}
.ask-tool svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.ask-tool:hover,.ask-tool:focus-visible{color:#fff;transform:translateY(-3px);outline:none}
.ask-tool:focus-visible{background:rgba(255,255,255,.08)}
.ask-tool[aria-pressed=true]{color:#8ab4ff}
.ask-tool[data-tip]::after{content:attr(data-tip);position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%) translateY(4px);padding:3px 7px;border-radius:6px;background:#2a2a2f;color:#fff;font-size:10.5px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .2s,transform .2s}
.ask-tool:hover::after,.ask-tool:focus-visible::after{opacity:1;transform:translateX(-50%)}
.ask-right{display:flex;align-items:center;gap:8px}
.ask-count{color:var(--ask-dim);font-size:10.5px;font-variant-numeric:tabular-nums;opacity:0;transition:opacity .2s}
.ask-card:focus-within .ask-count{opacity:1}
/* metallic send button with a glossy top edge */
.ask-send{display:grid;place-items:center;padding:2px;border:0;border-radius:11px;cursor:pointer;background:linear-gradient(to top,#28282c,#58585f,#28282c);
box-shadow:inset 0 6px 2px -4px rgba(255,255,255,.5),0 6px 14px -6px rgba(0,0,0,.8);transition:transform .15s,filter .2s}
.ask-send span{display:grid;place-items:center;width:32px;height:32px;border-radius:9px;background:rgba(0,0,0,.12);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);color:#8d8d94;overflow:hidden}
.ask-send svg{width:17px;height:17px;transition:transform .3s cubic-bezier(.3,1.4,.5,1),color .25s,filter .25s}
.ask-send:not(:disabled):hover svg,.ask-send:focus-visible svg{color:#f3f6fd;filter:drop-shadow(0 0 5px #fff);transform:rotate(45deg) translate(-1px,1px) scale(1.12)}
.ask-send:active:not(:disabled){transform:scale(.92)}
.ask-send:disabled{cursor:default;filter:grayscale(1) brightness(.75)}
.ask-send:focus-visible{outline:2px solid rgba(255,255,255,.7);outline-offset:2px}
.ask-send.is-fly svg{animation:ask-fly .55s cubic-bezier(.5,0,.75,0) forwards}
@keyframes ask-fly{0%{transform:rotate(45deg) scale(1.1)}35%{transform:rotate(45deg) translate(-4px,4px) scale(1)}100%{transform:rotate(45deg) translate(40px,-40px) scale(.6);opacity:0}}
.ask-chips{display:flex;flex-wrap:wrap;gap:6px}
.ask-chip{height:28px;padding:0 11px;border:1.5px solid #34343a;border-radius:10px;background:#18181b;color:#e9ecf3;font:500 11.5px system-ui,sans-serif;cursor:pointer;transition:background .2s,border-color .2s,transform .2s}
.ask-chip:hover,.ask-chip:focus-visible{background:#26262b;border-color:#55555d;transform:translateY(-2px);outline:none}
.ask-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
@media (prefers-reduced-motion:reduce){.ask-msg,.ask-send.is-fly svg{animation:none}.ask-tool:hover,.ask-chip:hover{transform:none}}`;
const Icon = ({ d }: { d: string }) => <svg viewBox="0 0 24 24" dangerouslySetInnerHTML={{ __html: d }} />;
const ICONS = { attach: `<path d="M20.5 11.5 12.4 19.6a5 5 0 0 1-7.1-7.1l8.5-8.5a3.3 3.3 0 0 1 4.7 4.7l-8.5 8.5a1.7 1.7 0 0 1-2.4-2.4l7.8-7.8"/>`, image: `<rect x="3.5" y="4.5" width="17" height="15" rx="3"/><circle cx="9" cy="10" r="1.8"/><path d="m20.5 16-4.6-4.6L6 19.5"/>`, web: `<circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.4 2.3 2.4 14.7 0 17M12 3.5c-2.4 2.3-2.4 14.7 0 17"/>` };
export type PromptTool = "attach" | "image" | "web";
export function AIPromptBox({
onSubmit,
onTool,
placeholder = "Imagine something... ✦",
suggestions = ["Create an image", "Analyse data", "Write a plan"],
maxLength = 2000,
showThread = true,
disabled,
}: {
/** Called with the prompt and the toggled tools (e.g. ["web"]). */
onSubmit: (text: string, tools: PromptTool[]) => void;
/** Attach and image buttons call this so you can open your own picker. */
onTool?: (tool: PromptTool) => void;
placeholder?: string;
suggestions?: string[];
maxLength?: number;
showThread?: boolean;
disabled?: boolean;
}) {
const [text, setText] = useState("");
const [web, setWeb] = useState(false);
const [sent, setSent] = useState<{ id: number; text: string }[]>([]);
const [fly, setFly] = useState(0);
const input = useRef<HTMLTextAreaElement>(null);
const sendBtn = useRef<HTMLButtonElement>(null);
useEffect(() => {
const el = input.current;
if (!el) return;
el.style.height = "auto";
el.style.height = `${Math.min(el.scrollHeight, 120)}px`;
}, [text]);
useEffect(() => {
const b = sendBtn.current;
if (!fly || !b) return;
b.classList.remove("is-fly"); void b.offsetWidth; b.classList.add("is-fly");
const t = setTimeout(() => b.classList.remove("is-fly"), 600);
return () => clearTimeout(t);
}, [fly]);
const submit = () => {
const value = text.trim();
if (!value || disabled) return;
onSubmit(value, web ? ["web"] : []);
setSent((s) => [...s, { id: Date.now(), text: value }].slice(-3));
setText("");
setFly((n) => n + 1);
input.current?.focus();
};
return (
<form className="ask" onSubmit={(e) => { e.preventDefault(); submit(); }} noValidate>
<style>{css}</style>
{showThread && (
<div className="ask-thread" aria-live="polite" aria-label="Sent messages">
{sent.map((m) => <div key={m.id} className="ask-msg">{m.text}</div>)}
</div>
)}
<div className="ask-frame"><div className="ask-card">
<textarea
ref={input} className="ask-input" rows={2} maxLength={maxLength} placeholder={placeholder} aria-label="Message"
value={text} disabled={disabled} onChange={(e) => setText(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); submit(); } }}
/>
<div className="ask-bar">
<div className="ask-tools">
<button type="button" className="ask-tool" data-tip="Attach a file" aria-label="Attach a file" onClick={() => onTool?.("attach")}><Icon d={ICONS.attach} /></button>
<button type="button" className="ask-tool" data-tip="Add an image" aria-label="Add an image" onClick={() => onTool?.("image")}><Icon d={ICONS.image} /></button>
<button type="button" className="ask-tool" data-tip="Search the web" aria-label="Search the web" aria-pressed={web} onClick={() => setWeb((w) => !w)}><Icon d={ICONS.web} /></button>
</div>
<div className="ask-right">
<span className="ask-count">{text.length}/{maxLength}</span>
<button ref={sendBtn} type="button" className="ask-send" aria-label="Send" disabled={!text.trim() || disabled} onClick={submit}>
<span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 3 10.5 13.5M21 3l-6.5 18-4-7.5L3 9.5z" /></svg></span>
</button>
</div>
</div>
</div></div>
{suggestions.length > 0 && (
<div className="ask-chips" aria-label="Suggestions">
{suggestions.map((s) => (
<button key={s} type="button" className="ask-chip" onClick={() => { setText(`${s}: `); input.current?.focus(); }}>{s}</button>
))}
</div>
)}
</form>
);
}
/* Usage:
<AIPromptBox onSubmit={(text, tools) => sendToAI({ text, webSearch: tools.includes("web") })} onTool={(t) => t === "attach" && openFilePicker()} />
*/
Install
npx shadcn@latest add https://ownlyplans.com/r/bhaskar/ai-prompt-box-dark-glass.json
Adds the React files to components/ownlyplans/ in a project set up with shadcn and Tailwind CSS. Or copy them from the Code tab.
About
The message box for your chatbot, copilot or image generator, in a dark glass style. A hairline frame catches the light in the corner and brightens when you type, the input grows with the text, Enter sends and Shift+Enter adds a line, and the metallic send button only lights up when there is something to send. Press it and the paper plane flies off while the message lands in a small thread above.
Tool buttons for attaching a file and adding an image (wire them to your own picker), a web-search toggle with aria-pressed, a character counter, and suggestion chips that start a prompt for you. Everything works with the keyboard and screen readers.
Listen for the “ask:submit” event (detail.text and detail.tools) to send the prompt to your AI. In the React version pass onSubmit(text, tools), onTool, your own suggestions and maxLength.
MIT License
Copyright (c) 2026 OwnlyPlans
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.