AI components

Composer

A prompt input that grows with its content and tracks submission state.

Installation

pnpm dlx @mielui/svelte add composer

Usage

The theme setting chrome.borders chooses "single" or "double" framing. Single is the default. Single removes the extra frame while preserving content padding, composition, and inset variants.

Bind the prompt value on Root and handle submission with onSubmit. Composer waits for async handlers and shows the submitting state until they finish. Submit names its icon Send, Queue message, or Stop response to match the current action.

If submission rejects, Composer preserves the prompt and shows labels.error. Use onError to report the failure, then retry with the same submit button. If you set status="error" yourself, your application must clear it.

<script lang="ts">
    import * as Composer from '@mielui/svelte/components/composer';

    let value = $state('');

    async function send(prompt: string) {
        const response = await fetch('/api/messages', {
            method: 'POST',
            body: prompt
        });

        if (!response.ok) {
            throw new Error(response.statusText);
        }

        value = '';
    }
</script>

<Composer.Root bind:value onSubmit={send}>
    <Composer.Header>
        <!-- Optional context above the input -->
    </Composer.Header>
    <Composer.Input placeholder="Ask anything..." />
    <Composer.Toolbar>
        <Composer.Actions>
            <!-- Attachment, model or permission controls -->
        </Composer.Actions>
        <Composer.Submit />
    </Composer.Toolbar>
    <Composer.Footer>
        <!-- Optional strip under the writing surface -->
    </Composer.Footer>
</Composer.Root>

By default, submits and inserts a new line. Set submitOnEnter=false on Input when Enter should always create a new line.

Glass surface

Composer follows the theme's surface setting. Glass frosts the frame and keeps the writing surface on a more opaque fill. Set surface="glass" or surface="solid" on Root to force one regardless of the theme.

Submission and cancellation

Return a promise from onSubmit to keep the submit control pending until your request settles. Reject it to preserve the prompt and display labels.error. Use controlled status="submitting" or generating for a stoppable response. onStop must cancel your request or timer; it does not cancel application work automatically. The examples below include a real stop action and a failure you can retry.

Examples

Agent prompt

A composer with attachments, permission and model pickers, dictation, and a footer action.

Idle

Submitting

Error

Attachments

Wrap Composer.Root in Attachment.Root so files can be dropped anywhere on the composer. Place Attachment.List variant="chip" above Composer.Root, inside Attachment.Root, to show a scrolling row of chips outside the frame. The list disappears when there are no files.

Toolbar placement

The composer is a recessed frame holding a raised writing surface. The toolbar joins the input on that surface by default. Set variant="inset" to keep the input on its own surface with the toolbar on the frame under it. Toolbar controls are ghost buttons and triggers. Outline ones render as flat pills at the same height. Send is the one filled primary pill, and it turns neutral while it stops a response.

Empty messages and the disabled state

Submit is disabled while the input is empty. Set allowEmpty on Root when a message can be only attachments, so it can be sent with no text. disabled on Root turns off the input and the button together.

Submit changes job as the conversation moves. While a response is generating and the input is empty it stops the response, and with text typed it queues the next message. labels.stop and labels.queue on Root are its accessible names in those two states, and labels.sending names it while onSubmit is pending. Its onclick runs first, and calling preventDefault() there cancels the stop.

Parts

Input and Submit accept element to bind the textarea and the button.

Root, Header, Toolbar, Actions, Submit and Footer render their children and accept class and style like any element.

Labels

Built-in text is English by default. Pass a labels object of type ComposerLabels to Composer.Root to translate the input placeholder and accessible name, the toolbar name, the four names of Submit, and the message shown when submitting fails. Every key is optional; omitted keys keep their default. A placeholder or aria-label set directly on Input or Toolbar wins over labels.

<script lang="ts">
    import * as Composer from '@mielui/svelte/components/composer';
    import type { ComposerLabels } from '@mielui/svelte/components/composer';

    let value = $state('');

    const labels: ComposerLabels = {
        placeholder: 'ร‰crivez ร  lโ€™agentโ€ฆ',
        input: 'Message',
        toolbar: 'Actions du message',
        send: 'Envoyer',
        queue: 'Mettre en file',
        stop: 'Arrรชter la rรฉponse',
        sending: 'Envoi',
        error: "Le message n'a pas pu รชtre envoyรฉ."
    };

    function send() {
        value = '';
    }
</script>

<Composer.Root bind:value {labels} onSubmit={send}>
    <Composer.Input />
    <Composer.Toolbar>
        <Composer.Submit />
    </Composer.Toolbar>
</Composer.Root>

API reference

Composer.Root

Manages prompt submission.

  • labels
    ComposerLabels

    Overrides the built-in text and accessible names. Every key is optional and English is the fallback.

  • surface
    "solid" | "glass"

    Surface treatment. Omit it to inherit --mielui-surface from the theme.

  • value Bindable
    string

    Message text. Bindable.

    Default ''

  • status
    ComposerStatus

    Current state of the composer, which sets what the submit control does.

    Default 'idle'

  • generating
    boolean

    Whether a response is being generated independently of submission state.

  • disabled
    boolean

    Prevents interaction and dims the control.

    Default false

  • allowEmpty
    boolean

    Allows submitting with no text, for a message that is only attachments.

    Default false

  • onSubmit Required
    (value: string, event: SubmitEvent) => void | Promise<void>

    Called with the text on submit. Return a promise to show progress until it settles.

  • onError
    ((error: unknown) => void)

    Called with the error when onSubmit rejects.

  • onStop
    (() => void)

    Called when the stop control is pressed during a response.

  • class
    string

    Classes added to the element.

  • children
    Snippet<[]>

    Content rendered inside.

Composer.Actions

Groups composer actions.

  • class
    string

    Classes added to the element.

  • children
    Snippet<[]>

    Content rendered inside.

Composer.Footer

Holds a strip under the writing surface.

  • class
    string

    Classes added to the element.

  • children
    Snippet<[]>

    Content rendered inside.

Composer.Header

Holds chips above the input.

  • class
    string

    Classes added to the element.

  • children
    Snippet<[]>

    Content rendered inside.

Composer.Input

Accepts the prompt text.

  • submitOnEnter
    boolean

    Enter submits and Shift+Enter adds a line. Set false to make Enter add a line.

    Default true

  • element Bindable
    HTMLTextAreaElement

    Bindable reference to the DOM element.

  • class
    string

    Classes added to the element.

Composer.Submit

Submits or stops the prompt.

  • children
    Snippet<[Readonly<{ action: ComposerSubmitAction; generating: boolean; empty: boolean; }>]>

    Content rendered inside.

  • element Bindable
    HTMLButtonElement | HTMLAnchorElement

    Bindable reference to the DOM element.

  • onclick
    ((event: MouseEvent) => void)

    Called when it is clicked or activated.

  • class
    string

    Classes added to the element.

Composer.Toolbar

Groups composer controls.

  • variant
    "default" | "inset"

    inset puts the input on its own inset surface and the toolbar in the frame under it, like an inset Card. The default joins the toolbar to the input on one surface.

    Default 'default'

  • class
    string

    Classes added to the element.

  • children
    Snippet<[]>

    Content rendered inside.