The chat composer: a › glyph before a normal, auto-growing textarea, with a visible send button.
import { Prompt } from "@meowerse/ui"; · source packages/ui/src/components/Prompt.tsx
variants and states
›
empty — send is aria-disabled
›
with text
›
busy — still editable; sends queue
›
floating label — labelPlacement="float": the label shows while empty
›
floating label, with text — the label stays visible on the border
try it
are we still on for tonight?
›
Nothing leaves this page: messages stay in this demo.
[wait]working: loading the demo…
demo source
tsx
import { useRef, useState } from "react";
import { Prompt } from "@meowerse/ui";
type Msg = { id: number; text: string; own: boolean };
// A tiny composer: sends land in the log below, exactly as typed. Nothing leaves the page.
export default function Demo() {
const [value, setValue] = useState("");
const [msgs, setMsgs] = useState<Msg[]>([{ id: 1, text: "are we still on for tonight?", own: false }]);
const seq = useRef(1);
return (
<div className="demo chat">
<ol className="chat__log" aria-label="messages" aria-live="polite">
{msgs.map((m) => <li key={m.id} className={m.own ? "bubble bubble--own" : "bubble"}>{m.text}</li>)}
</ol>
<Prompt label="message" value={value} onChange={setValue}
onSubmit={(text) => { seq.current += 1; const id = seq.current; setMsgs((ms) => [...ms, { id, text, own: true }]); setValue(""); }} />
<p className="mw-muted">Nothing leaves this page: messages stay in this demo.</p>
</div>
);
}
props
prop
type
default
required
label
string the label: visually hidden and repeated as the placeholder, unless labelPlacement is float.
—
yes
labelPlacement
"hidden" | "float" where the label sits: hidden (the placeholder repeats it), or float — shown inside the empty field and
moved up onto its top border once it is focused or filled, so it stays visible (CSS only).
"hidden"
no
placeholder
string overrides the placeholder text shown while empty (with a floating label, only while focused).
—
no
value
string the textarea's current value.
—
yes
onChange
(v: string) => void called with the new value on every keystroke.
—
yes
onSubmit
(v: string) => void called with the trimmed value when it is sent.
—
yes
sendLabel
string the send button's accessible name; a blank one falls back to "send".
"send"
no
maxRows
number the tallest the textarea grows before it scrolls.
6
no
busy
boolean shows the composer as busy; the field stays editable so sends can queue.
false
no
enterSends
"auto" | "always" | "never" whether Enter sends: auto only on non-touch pointers, always, or never (the button always sends).
"auto"
no
className
string extra class names to append.
—
no
css custom properties
variable
dark
light
--c-accent
#00ff82
#0a6e3c
--c-accent-fill
#00ff82
same
--c-bg-elev
#111113
#f1f0ec
--c-fg
#ededeb
#1a1a1c
--c-fg-muted
#b3b3af
#46464a
--c-fg-subtle
#8c8c88
#5e5e5b
--c-line-input
#6a6a70
#7a7973
--c-on-accent
#06170d
same
--control-height
44px
same
--d-fast
120ms
same
--disabled-opacity
0.5
same
--ease
cubic-bezier(.16,1,.3,1)
same
--fs-2
0.8125rem
same
--fs-4
1rem
same
--r-m
4px
same
--sp-1
4px
same
--sp-2
8px
same
Classes: .mw-prompt. Set any of these variables on an ancestor (or wrap a subtree in .mw-theme--dark / .mw-theme--light) to restyle just that part of a page.
import { Prompt } from "@meowerse/ui";
<Prompt label="message" labelPlacement="float" value="see you at 7" onChange={handleChange} onSubmit={handleSubmit} />
accessibility
A labelled textarea: the label is visually hidden and the placeholder repeats it. With labelPlacement="float" the same label is shown instead, inside the empty field and then on its border, so it never disappears while typing.
On desktop, Enter sends and Shift+Enter adds a line; on phones, Enter adds a line and the button sends. Nothing is sent while an input method is composing.
The field is never disabled; the send button is 44×44 px and named "send".
key
does
Enter
sends (desktop)
Shift+Enter
adds a line
Tab
moves to the send button
do and don't
[ ok ] do
Queue sends while offline instead of disabling the field.
[fail] don't
Don't make it a borderless command line or add a blinking block cursor (spec §7).