Overlays

ChatComposerPopover

Chat composer-bound slash-command and mention listbox primitive.

import { ChatComposerPopover } from '@lostgradient/chat/composer-popover';
chatcommandoverlay
01

Overview

Chat composer-bound slash-command and mention listbox primitive.

Usage

svelte
<script lang="ts">
  import ChatComposerPopover from '@lostgradient/chat/composer-popover';
  import { ChatInput } from '@lostgradient/chat';

  const commands = [
    { value: 'help', label: 'Help', description: 'Show available commands' },
    { value: 'new', label: 'New conversation', description: 'Start over' },
  ];

  let value = $state('');
</script>

<ChatComposerPopover id="composer-commands" bind:value items={commands}>
  {#snippet composer(composerProps)}
    <ChatInput
      id="chat"
      bind:value
      composerRole={composerProps.composerRole}
      composerAriaExpanded={composerProps.composerAriaExpanded}
      composerAriaControls={composerProps.composerAriaControls}
      composerAriaActiveDescendant={composerProps.composerAriaActiveDescendant}
      composerAriaAutocomplete={composerProps.composerAriaAutocomplete}
      oncomposerinput={composerProps.oncomposerinput}
      oncomposerkeydown={composerProps.oncomposerkeydown}
      oncomposerselectionchange={composerProps.oncomposerselectionchange}
      oncomposerblur={composerProps.oncomposerblur}
    />
  {/snippet}
</ChatComposerPopover>

The default placement is top-start, which keeps suggestions above a composer anchored near the bottom of the viewport. Pass placement when a different starting side is appropriate; the underlying floating overlay still flips and shifts the menu when available space requires it.

When the composer is the full Chat surface, commit a selection with the public range API. insertAtRange() updates the popover's bound value through oncomposerinput, so no synthetic DOM event is needed:

svelte
<script lang="ts">
  import { Chat, createConversation } from '@lostgradient/chat';
  import ChatComposerPopover from '@lostgradient/chat/composer-popover';

  const conversation = createConversation({ id: 'assistant' });
  const commands = [
    { value: 'help', label: 'Help', insert: '/help ' },
    { value: 'new', label: 'New conversation', insert: '/new ' },
  ];
  let chat: ReturnType<typeof Chat> | undefined;
  let value = $state('');
</script>

<ChatComposerPopover
  id="composer-commands"
  bind:value
  items={commands}
  onSelect={(selection) => chat?.insertAtRange(selection.range, selection.item.insert)}
>
  {#snippet composer(composerProps)}
    <Chat bind:this={chat} id="assistant-chat" {conversation} {...composerProps} />
  {/snippet}
</ChatComposerPopover>

Related

  • Chat — full conversation surface and composer.
  • CommandMenu — generic caret-anchored command list.
  • CommandItem — selectable command row used by the popover.
Live preview
02

When to use

Use when
  • Adding slash commands, mentions, or autocomplete to ChatInput without re-implementing combobox ARIA.
  • Composer suggestions should stay anchored to the active token and leave command definitions in application code.
Avoid when
  • Opening a global command launcher detached from the composer — use command-palette instead.
  • Anchoring a generic command menu to an arbitrary input — use command-menu instead.
03

Examples

Slash commands in Chat

Wires ChatComposerPopover to Chat and commits selections through the public composer API.

04

Props

Props for chat-composer-popover
Name Type Default Required Bindable Description
id text req Unique identifier used for the listbox and item ids.
value text '' bind Current composer value. Keep this synchronized through ChatInput binding or Chat's oncomposerinput.
items readonly TItem[] req Consumer-owned command or mention definitions.
triggers readonly string[] ["/","@"] Trigger characters that open the popover. Default ['/', '@'].
label text 'Composer suggestions' Accessible listbox label. Default 'Composer suggestions'.
placement 'top''bottom''left''right''top-start''top-end''bottom-start''bottom-end' 'top-start' Caret-relative placement. Default 'top-start', which keeps the menu above a bottom composer.
offset number 6 Distance in px between the caret and popover. Default 6.
composer snippet req Render the ChatInput or compatible composer with the provided overlay props.
item snippet Optional custom row contents. Defaults to the item label and description.
empty snippet Optional empty state rendered when filtering produces no matching rows.
detectTrigger (value: string, selectionStart: number, selectionEnd: number, ) => ChatComposerPopoverTriggerMatchnull Override trigger detection.
filter (items: readonly TItem[], query: string, trigger: string) => readonly TItem[] Override filtering.
onSelect (selection: ChatComposerPopoverSelection<TItem>) => void Invoked when an enabled item is selected by keyboard or pointer.
onDismiss () => void Invoked when Escape, trigger loss, or outside pointerdown dismisses the popover.
05

Accessibility

ArrowUp/ ArrowDown
Moves the active suggestion.
Enter
Selects the active suggestion.
Escape
Dismisses the suggestion popover.
Passes combobox role and aria-expanded, aria-controls, aria-activedescendant, and aria-autocomplete through to ChatInput's composer overlay API.