# Conversation

Conversation threads, answers, citations, composers and floating chat surfaces.

- Group: Website
- Import: `import { ConversationLayout } from '@misoto22/folio/website'`
- Page: https://ui.misoto22.com/components/conversation/

## When to reach for it

Compose a localized assistant interface while the host owns streaming, history and service calls.

## Best practices

### Do

- Supply localized labels, descriptive links and meaningful image alternatives.

### Don’t

- Do not put service credentials, routing logic or backend models inside presentation components.

## ConversationLayout

A reading-width conversation with a separate, sticky evidence rail.

### Props

- `evidence` (required) — `ReactNode`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## ConversationFacts

### Props

- `items` (required) — `DescriptionListItem[]`.

Also accepts: `HTMLAttributes<HTMLDListElement>`.

## ConversationSuggestions

### Props

- `label` (required) — `string`.
- `items` (required) — `ConversationSuggestion[]`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## ConversationThread

Host owns turns and folding; this composition owns their hierarchy and controls.

### Props

- `label` (required) — `string`.
- `intro` — `{ title: ReactNode; description: ReactNode; suggestionsLabel: string; suggestions: ConversationSuggestion[] }`.
- `summary` — `ReactNode`.
- `controls` — `ConversationControl[]` default `[]`.
- `composer` (required) — `ReactNode`.
- `proposal` — `ReactNode`.
- `hasTurns` (required) — `boolean`.

Also accepts: `HTMLAttributes<HTMLElement>`.

## ConversationLog

### Props

- `compact` — `boolean`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## QuestionComposer

A controlled, labelled composer. Request limits and sending remain host concerns.

### Props

- `id` (required) — `string`.
- `label` (required) — `string`.
- `hideLabel` — `boolean`.
- `value` (required) — `string`.
- `onValueChange` (required) — `(value: string) => void`.
- `onSubmit` (required) — `() => void`.
- `inputRef` — `Ref<HTMLInputElement>`.
- `placeholder` — `string`.
- `maxLength` — `number`.
- `disabled` — `boolean`.
- `submitDisabled` — `boolean`.
- `pending` — `boolean`.
- `submitLabel` (required) — `string`.
- `hint` — `ReactNode`.
- `hideHint` — `boolean`.
- `counter` — `ReactNode`.
- `actions` — `ReactNode`.
- `compact` — `boolean`.

Also accepts: `Omit<HTMLAttributes<HTMLFormElement>, 'onSubmit'>`.

## ConversationTurn

### Props

- `title` (required) — `ReactNode`.
- `metadata` — `ReactNode`.
- `expanded` (required) — `boolean`.
- `onToggle` (required) — `() => void`.
- `bodyId` (required) — `string`.
- `preview` — `ReactNode`.

Also accepts: `Omit<HTMLAttributes<HTMLElement>, 'title'>`.

## ConversationQuote

### Props

- `label` (required) — `string`.
- `compact` — `boolean`.

Also accepts: `HTMLAttributes<HTMLElement>`.

## ConversationAnswer

### Props

- `question` (required) — `ReactNode`.
- `questionLabel` (required) — `string`.
- `quote` — `ReactNode`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## ConversationProse

Trusted React nodes from the host's sanitized answer parser, never raw model HTML.

## ConversationAnswerHeading

Model-generated headings are prose labels, so they do not alter the page outline.

## StreamingCaret

## ConversationSources

### Props

- `label` (required) — `string`.
- `count` (required) — `ReactNode`.
- `items` (required) — `ConversationSource[]`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## ActionProposal

### Props

- `label` (required) — `string`.
- `destination` (required) — `ReactNode`.
- `detail` — `ReactNode`.
- `approveLabel` (required) — `string`.
- `dismissLabel` (required) — `string`.
- `onApprove` (required) — `() => void`.
- `onDismiss` (required) — `() => void`.
- `approvalAttributes` — `Record<`data-${string}`, string>`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## ConversationLauncher

### Props

- `visible` (required) — `boolean`.
- `buttonRef` — `Ref<HTMLButtonElement>`.

Also accepts: `ButtonHTMLAttributes<HTMLButtonElement>`.

## ConversationDock

The host supplies placement, dismissal and stream state; the dock draws them.

### Props

- `label` (required) — `string`.
- `open` (required) — `boolean`.
- `card` (required) — `boolean`.
- `corner` (required) — `ConversationDockCorner`.
- `dragging` — `boolean`.
- `moving` — `boolean`.
- `panelRef` — `Ref<HTMLElement>`.
- `logRef` — `Ref<HTMLDivElement>`.
- `roomLink` (required) — `ReactElement`.
- `closeLabel` (required) — `string`.
- `onClose` (required) — `() => void`.
- `onHeaderPointerDown` — `(event: PointerEvent<HTMLElement>) => void`.
- `handles` — `ReactNode`.
- `proposal` — `ReactNode`.
- `notice` — `ReactNode`.
- `composer` (required) — `ReactNode`.

Also accepts: `HTMLAttributes<HTMLElement>`.

## ConversationDockWelcome

### Props

- `lead` (required) — `ReactNode`.
- `suggestionsLabel` (required) — `string`.
- `suggestions` (required) — `ConversationSuggestion[]`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## ConversationResizeHandle

Keyboard equivalents for the host's pointer resize controller.

### Props

- `axis` (required) — `'size-vertical' | 'size-horizontal'`.
- `side` (required) — `'top' | 'bottom' | 'left' | 'right'`.
- `label` (required) — `string`.
- `dragging` (required) — `boolean`.
- `onStart` (required) — `(axis: 'size-vertical' | 'size-horizontal', event: PointerEvent<HTMLElement>) => void`.
- `onNudge` (required) — `(axis: 'size-vertical' | 'size-horizontal', delta: number) => void`.

## SelectionToolbar

Selection actions with a real toolbar's single tab stop and arrow navigation.

### Props

- `label` (required) — `string`.
- `actions` (required) — `SelectionToolbarAction[]`.
- `floating` (required) — `boolean`.
- `toolbarRef` — `Ref<HTMLDivElement>`.
- `style` — `CSSProperties`.

Also accepts: `HTMLAttributes<HTMLDivElement>`.

## ConversationRoomLink

A router link keeps its own destination and behavior inside a standard action.

## Example — a local conversation

```tsx
import { ConversationAnswer, ConversationProse, ConversationThread, QuestionComposer } from '@misoto22/folio/website'

<ConversationThread label="Library conversation" hasTurns={questions.length > 0}
  summary={`${questions.length} questions`}
  intro={questions.length ? undefined : {
    title: 'Explore the library', description: 'Try a question to preview the conversation layout.',
    suggestionsLabel: 'Start with', suggestions: [{ id: 'recent', label: 'What has been added recently?', onSelect: () => setQuestion('What has been added recently?') }],
  }}
  composer={<QuestionComposer id="conversation-example-question" label="Your question" value={question} onValueChange={setQuestion}
    placeholder="Ask about the collection" submitLabel="Add question" submitDisabled={!question.trim()}
    hint="Local demonstration; no service request is made."
    onSubmit={() => { if (question.trim()) { setQuestions((items) => [...items, question.trim()]); setQuestion('') } }} />}>
  {questions.map((item, index) => <ConversationAnswer key={`${index}-${item}`} questionLabel="Question" question={item}>
    <ConversationProse><p>The question is now part of this local conversation. A connected application would supply its answer here.</p></ConversationProse>
  </ConversationAnswer>)}
</ConversationThread>
```
