Skip to content

ChatComposer

ChatComposer provides an accessibly named, growing textarea inside a rounded field with a prominent surface, a focus-colored frame and a circular send control. It sends trimmed nonempty text through a callback.

01Preview

02Usage

Load @dg/ui/styles.css once in the root layout.

<script>
  import { ChatComposer } from "@dg/ui";
  let draft = $state("");
  let lastSent = $state("");
</script>

<ChatComposer
  id="question"
  bind:value={draft}
  onsend={(text) => {
    lastSent = text;
    draft = "";
  }}
/>
<p class="feedback" aria-live="polite">{lastSent}</p>

03Notes

  • While pending, the draft stays focusable and read-only; activating stop calls onstop and returns focus to the draft.
  • The field outline uses --text-muted, keeping the 3:1 contrast required for a control boundary in both themes.
  • The field has rounded 1rem corners, unlike square native fields. When the textarea has focus, the entry surface's frame takes --focus and thickens inward to --focus-width, without an outer outline; forced-colors mode shows a system-colored outline. The send control keeps its own focus indicator.
  • The placeholder and single-line draft are vertically centered within the minimum control height. Additional lines grow the textarea normally, including automatic wrapping.
  • The composer keeps its height in a flex column while the thread yields space. The textarea grows to 7.5rem (120px at a 16px root), then scrolls internally; maxlength separately limits the character count.
  • A translucent, blurred fade above the composer softens messages scrolling underneath without intercepting pointer input. The field and controls remain opaque.
  • The send and stop controls use built-in names in the Page language.
  • Native HTML attributes, DOM callbacks, class and style are forwarded to the form; it owns submission, and id names the textarea.
  • Forwarding uses native attribute types; unknown props are not supported. Pass class as a string.

04Parameters

NameTypeDefaultDescription
idstringRequiredUnique textarea ID, also used to connect its hint.
valuestring · bindable""Draft text. Use bind:value; the parent clears it after a successful send.
onsend(text: string) => voidundefinedReceives trimmed, nonempty text on Enter or submit. Shift+Enter adds a newline; IME composition does not send.
labelstringundefinedAccessible textarea name; no visible label is rendered. Defaults to the built-in text in the Page language.
placeholderstringundefinedPlaceholder shown for an empty draft. Defaults to the built-in text in the Page language.
hintstringundefinedText linked with aria-describedby and set as .feedback; an empty string hides it. Defaults to the built-in keyboard hint.
disabledbooleanfalseDisable input and sending, for example while a response is loading.
maxlengthnumber2000Native maximum textarea length in characters.
pendingbooleanfalseMake the draft read-only, keeping its focus, and replace sending with a stop action while a request is pending.
onstop() => voidundefinedStop action callback; the parent owns cancellation and pending state.

05Methods

NameDescription
focus()Focus the textarea without scrolling. Access with bind:this.

06Accessibility

The label supplies the textarea’s accessible name through aria-label. Use a unique id to connect its hint. Enter sends; Shift+Enter adds a newline; IME composition does not send. Disabled prevents editing and sending. The parent clears the draft and manages request errors.

Related subjects

distributed·gallery