AI components

Conversation

Display a live transcript. Follow new messages until the user scrolls away.

Installation

pnpm dlx @mielui/svelte add conversation

Usage

Give Root a bounded height so Content can scroll. Bind follow when the surrounding interface needs to reflect whether new output is being followed.

The scroll button runs your click handler first. Call preventDefault() to keep the current scroll position and follow state.

<script lang="ts">
    import * as Conversation from '@mielui/svelte/components/conversation';
    import * as Message from '@mielui/svelte/components/message';

    let follow = $state(true);
</script>

<Conversation.Root bind:follow class="h-[32rem]">
    <Conversation.Content aria-label="Support conversation">
        <Message.Root from="assistant">
            <Message.Content>I found the failed request.</Message.Content>
        </Message.Root>
    </Conversation.Content>
    <Conversation.ScrollButton />
</Conversation.Root>

Scroll ownership

Conversation follows new output while the reader is at the bottom. Scrolling up sets follow to false and keeps their position as messages arrive. threshold is the distance from the bottom, 80 pixels by default, that still counts as following. ScrollButton returns to the latest message and resumes following.

Examples

Incident review

A longer transcript with reasoning, markdown, and a code block inside the messages.

Empty state

Empty shows a chat icon, a title and a description by default. Pass icon, title, description and action to replace them. To only translate the default title and description, use labels on Root.

Follow live output

Layout and parts

Content is the scroll viewport, and the messages sit in a stack inside it. class styles the viewport, while transcriptClass styles that inner stack, which is where the gap between messages, the side padding and a maximum width belong.

ScrollButton reads its accessible name from labels.scrollToBottom on Root, which defaults to Scroll to latest message. Root, Content and Empty 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 ConversationLabels to Conversation.Root to translate the transcript's accessible name, the scroll button's name, and the default title and description of Empty. Every key is optional; omitted keys keep their default. An aria-label on Content and a title or description on Empty win over labels. Empty also reads labels when it renders outside Root, and falls back to English there.

<script lang="ts">
    import * as Conversation from '@mielui/svelte/components/conversation';
    import type { ConversationLabels } from '@mielui/svelte/components/conversation';

    const labels: ConversationLabels = {
        transcript: 'Conversation',
        scrollToBottom: 'Aller au dernier message',
        emptyTitle: 'Dรฉmarrer une conversation',
        emptyDescription: 'Posez une question ou dรฉcrivez votre travail.'
    };
</script>

<Conversation.Root {labels} class="h-[32rem]">
    <Conversation.Content>
        <Conversation.Empty />
    </Conversation.Content>
    <Conversation.ScrollButton />
</Conversation.Root>

API reference

Conversation.Root

Manages transcript scrolling.

  • labels
    ConversationLabels

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

  • follow Bindable
    boolean

    Whether new content should keep the transcript pinned to its latest message.

    Default true

  • threshold
    number

    Distance from the bottom, in pixels, that still counts as following.

    Default 80

  • children
    Snippet<[]>

    Content rendered inside.

  • class
    (string & (ClassValue | null))

    Classes added to the element.

Conversation.Content

Contains conversation messages.

  • transcriptClass
    string

    Classes applied to the inner transcript stack rather than the scroll viewport.

  • class
    (string & (ClassValue | null))

    Classes added to the element.

  • children
    Snippet<[]>

    Content rendered inside.

Conversation.Empty

Renders an empty conversation state.

  • icon
    Snippet<[]>

    Icon shown above the title.

  • title
    string

    Heading of the empty state.

  • description
    string

    Text under the heading.

  • action
    Snippet<[]>

    Action shown under the text.

  • children
    Snippet<[]>

    Content rendered inside.

  • class
    (string & (ClassValue | null))

    Classes added to the element.

Conversation.ScrollButton

Scrolls to the latest message.

  • class
    string

    Classes added to the element.