smart-components
Components

SmartTextarea

Multiline ghost-text completion with mirror-div positioning and optional auto-resize.

Multiline variant of SmartTextbox. Uses a mirror <div> for accurate ghost positioning across wraps. Optional autoResize grows the textarea to fit content.

Preview

import { useState } from 'react';import { SmartProvider, SmartTextarea } from '@extedcoud/smart-components';import { createMockClient } from '@extedcoud/smart-components/adapters/mock';const client = createMockClient({complete: async (req) => '\n\nLooking forward to next steps.',});export function Example() {const [value, setValue] = useState('');return (  <SmartProvider client={client}>    <SmartTextarea      value={value}      onChange={setValue}      placeholder="Compose your email…"      autoResize      rows={4}    />  </SmartProvider>);}

Styled example

An email-composer chrome (window header, to/subject metadata, footer status) with a soft violet italic ghost.

New message
✦ AI assist
Toteam@yourco.comSubjectQuick update

0 chars
'use client';import { useState } from 'react';import { SmartProvider, SmartTextarea } from '@extedcoud/smart-components';import { makeGhostMock } from '@/lib/mock-client';const client = makeGhostMock();export default function SmartTextareaStylized() {  const [value, setValue] = useState('');  return (    <SmartProvider client={client}>      <div className="w-full max-w-xl">        <div className="overflow-hidden rounded-2xl border border-fd-border bg-fd-card shadow-sm">          <div className="flex items-center justify-between border-b border-fd-border bg-fd-secondary/40 px-4 py-2">            <div className="flex items-center gap-2 text-xs text-fd-muted-foreground">              <span className="flex h-2 w-2 rounded-full bg-rose-500/80" />              <span className="flex h-2 w-2 rounded-full bg-amber-500/80" />              <span className="flex h-2 w-2 rounded-full bg-emerald-500/80" />              <span className="ml-2 font-medium">New message</span>            </div>            <span className="rounded-full bg-violet-500/10 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-violet-600 dark:text-violet-300">              ✦ AI assist            </span>          </div>          <div className="px-4 pt-3">            <div className="grid grid-cols-[60px_1fr] gap-2 text-sm">              <span className="self-center text-fd-muted-foreground">To</span>              <span className="self-center text-fd-foreground">team@yourco.com</span>              <span className="self-center text-fd-muted-foreground">Subject</span>              <span className="self-center text-fd-foreground">Quick update</span>            </div>            <hr className="my-3 border-fd-border" />            <SmartTextarea              value={value}              onChange={setValue}              placeholder="Hi team, just wanted to say…"              autoResize              rows={4}              className="w-full resize-none bg-transparent text-base text-fd-foreground outline-none"              style={{ fontSize: 16 }}              ghostStyle={{ color: '#7c3aed', opacity: 0.55, fontStyle: 'italic' }}            />          </div>          <div className="flex items-center justify-between border-t border-fd-border bg-fd-secondary/30 px-4 py-2 text-xs text-fd-muted-foreground">            <span>{value.length} chars</span>            <span className="hidden sm:block">              <kbd className="rounded bg-fd-card px-1.5 py-0.5 font-mono">→</kbd> to accept ghost            </span>          </div>        </div>      </div>    </SmartProvider>  );}

Stop sequences

Multiline completions default to stopping at ['\n\n'] — one paragraph. Override per-component:

<SmartTextarea
  value={v}
  onChange={setV}
  stop={['\n\n', 'Sincerely', '\n---']}
/>

Auto-resize

<SmartTextarea value={v} onChange={setV} autoResize rows={2} />

Grows on input + on accept. Pair with a max-height in CSS if you want a scroll-after-N-lines behavior.

Props

PropTypeDefaultDescription
value*stringControlled textarea value.
onChange*(value: string) => voidCalled on user input or accept.
contextstringOptional context string included in the prompt.
minCharsnumber3Minimum chars before suggestions fetch.
debounceMsnumber300Debounce window in ms.
streambooleanfalseUse the streaming capability for faster first paint.
disableAIbooleanfalseDisable all AI calls.
acceptKeystring'ArrowRight'Accept key. Do NOT use "Enter" — it would hijack newline.
dismissKeystring'Escape'Key that dismisses the ghost.
maxTokensnumber64Max tokens for the completion call.
stopstring[]['\n\n']Stop sequences for the completion.
autoResizebooleanfalseAuto-grow the textarea to fit content.
renderGhost(suggestion: string) => ReactNodeRender-prop override for the ghost text.
ghostClassNamestringClass name applied to the ghost text span.
ghostStyleCSSPropertiesInline style applied to the ghost text span.
wrapperClassNamestringClass name applied to the outer wrapper.
onAccept(accepted, finalValue) => voidCalled when the suggestion is accepted.
onGhostChange(suggestion: string) => voidCalled whenever the visible ghost changes.
...restTextareaHTMLAttributes<HTMLTextAreaElement>All native textarea props minus value, onChange, defaultValue.

Mobile notes

  • Do not set acceptKey="Enter" — it hijacks newline.
  • For mobile, use the imperative accept() via the SmartTextareaHandle ref (same shape as SmartTextboxHandle).