Studio
2

Question

Installation

pnpm dlx @mielui/svelte add question

Usage

Render Question.Root in the same layout slot as Composer.Root . Keep the prompt value in their shared parent so swapping the forms never clears an unsent draft.

import * as Question from '@mielui/svelte/components/question';

let answer = $state('');

async function submitAnswer(value: string) {
  await continueAgent(value);
}

<Question.Root variant="inset" bind:value={answer} onSubmit={submitAnswer} onError={reportError}>
  <Question.Content>
    <Question.Title>Which environment should I use?</Question.Title>
    <Question.Description>Your prompt draft remains untouched.</Question.Description>
    <Question.Options>
      <Question.Option value="preview" label="Preview" />
      <Question.Option value="production" label="Production" />
    </Question.Options>
  </Question.Content>
  <Question.Actions>
    <Question.Cancel onclick={() => skipQuestion()}>Skip question</Question.Cancel>
    <Question.Submit />
  </Question.Actions>
</Question.Root>

Use type="single" for one option, type="multiple" for several, or type="text" with Question.Input. Single and text modes use a string answer; multiple mode uses a string array. Changing type clears the answer to the new mode's empty value.

Question waits for async submit handlers and prevents duplicate submissions while pending. A rejected submission keeps the answer and shows errorMessage until the next attempt. Use onError to report failures. Changing mode or unmounting ignores an unfinished submission's result.

Composition

Set variant="inset" on Question.Root for the shared Card frame and recessed content surface. The default variant uses a plain Card. Place Question.Actions after Question.Content to keep controls in the footer. Both parts are optional; omit the description or restyle the actions to suit the space.

<Question.Root variant="inset" bind:value={answer} onSubmit={next}>
  <Question.Content>
    <Question.Title>{question.title}</Question.Title>
    <Question.Options>
      {#each question.options as option (option.value)}
        <Question.Option {...option} />
      {/each}
    </Question.Options>
  </Question.Content>
  <Question.Actions class="justify-between">
    <Question.Cancel disabled={index === 0} onclick={(event) => {
      event.preventDefault();
      back();
    }}>Back</Question.Cancel>
    <Question.Submit label="Next" />
  </Question.Actions>
</Question.Root>

Question.Content groups the title, description, and answer controls in a fieldset. It does not manage step navigation or transitions.

Store the step index and answers in the parent. When a flow mixes answer types, key Root by question so each step gets its own state. Changing type on the same Root clears its answer.

To use Cancel as a Back button, call event.preventDefault() before navigating. This prevents Root's cancellation handler from running.

Answer ownership

Bind value when the answer must survive navigation. Keep submission results in application state and show what was accepted. A text answer is a string; a multiple-choice answer is a string array. Cancel should return the surrounding interface to a usable state, as in the composer takeover example.

Examples

Multiple choice

Free text

Conversation takeover

Answer or skip the question to restore the composer with its draft intact.

API reference

Question.Root

Renders a full question form.

Prop Type Default
variant "inset" | "default" | undefined 'default'
status QuestionStatus | undefined 'idle'
disabled boolean | undefined false
required boolean | undefined true
autofocus boolean | undefined false
name string | undefined 'answer'
errorMessage string | undefined 'Answer could not be submitted.'
onError ((error: unknown) => void) | undefined —
onCancel ((event: MouseEvent) => void) | undefined —
class string | undefined —
children Snippet<[]> | undefined —
type "text" | "single" | undefined | "multiple" 'single'
value Bindable string | undefined | string[] | undefined —
onSubmit Required (answer: string, event: SubmitEvent) => void | Promise<void> | (answer: string[], event: SubmitEvent) => void | Promise<void> —

Question.Actions

Groups cancellation and submit actions.

Prop Type Default
class string | undefined —
children Snippet<[]> | undefined —

Question.Cancel

Cancels question submission.

Prop Type Default
children Snippet<[]> | undefined —
onclick ((event: MouseEvent) => void) | undefined —

Question.Content

Prop Type Default
class string | undefined —
children Snippet<[]> | undefined —

Question.Description

Explains the question context.

Prop Type Default
class string | undefined —
children Snippet<[]> | undefined —

Question.Input

Collects a free-form response.

Prop Type Default
submitOnEnter boolean | undefined true
autoresize boolean | undefined true
placeholder string | undefined 'Type your answer...'
aria-label string | undefined 'Answer'
rows number | undefined 2
disabled boolean | undefined false
readonly boolean | undefined false
element Bindable HTMLTextAreaElement | undefined —
class string | undefined —

Question.Option

Represents a question option.

Prop Type Default
value Required string —
label Required string —
description string | undefined —
disabled boolean | undefined false
element Bindable HTMLInputElement | undefined —
class string | undefined —

Question.Options

Wraps answer option items.

Prop Type Default
class string | undefined —
children Snippet<[]> | undefined —

Question.Submit

Submits the selected answer.

Prop Type Default
label string | undefined 'Submit answer'
loadingLabel string | undefined 'Submitting...'
children Snippet<[]> | undefined —
element Bindable HTMLButtonElement | HTMLAnchorElement | undefined —

Question.Title

Shows the question heading.

Prop Type Default
class string | undefined —
children Snippet<[]> | undefined —