Question
Installation
npx @mielui/svelte add questionyarn dlx @mielui/svelte add questionbunx @mielui/svelte add questionUsage
Render Question.Root in the slot Composer.Root would occupy. Keep the prompt value in their shared parent so swapping the two never
clears an unsent draft.
<script lang="ts">
import * as Question from '@mielui/svelte/components/question';
let {
onAnswer,
onSkip
}: {
onAnswer: (answer: string) => Promise<void>;
onSkip: () => void;
} = $props();
let answer = $state('');
</script>
<Question.Root variant="inset" bind:value={answer} onSubmit={onAnswer} onCancel={onSkip}>
<Question.Content>
<Question.Title>Which environment should I use?</Question.Title>
<Question.Description>Your prompt draft stays as it is.</Question.Description>
<Question.Options>
<Question.Option value="preview" label="Preview" />
<Question.Option value="production" label="Production" />
</Question.Options>
</Question.Content>
<Question.Actions>
<Question.Cancel>Skip</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 an async onSubmit and ignores repeat submissions while it is pending. A rejected submission keeps the answer and shows the error message until the next attempt. Use onError to report failures. Changing mode or unmounting ignores an unfinished submission's result.
Options use the same indicators as Checkbox and RadioGroup: square checkboxes in multiple mode and round radios otherwise. An error shows one message above the frame and tints the frame edge.
Composition
variant="inset" frames the question with a recessed content surface; the default variant uses a plain Card. Place Question.Actions after Question.Content to keep the controls in the footer. Both the description and the actions are optional.
Question.Content groups the title, description, and answer controls in a fieldset. It does not manage step navigation. 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.
To use Cancel as a Back button, call event.preventDefault() in its onclick before navigating. This keeps Root's onCancel from running.
<Question.Root
variant="inset"
bind:value={answers[step]}
labels={{ submit: 'Next' }}
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>
<Question.Cancel
disabled={step === 0}
onclick={(event) => {
event.preventDefault();
step -= 1;
}}
>
Back
</Question.Cancel>
<Question.Submit />
</Question.Actions>
</Question.Root>Examples
Question series
Three questions in a row with a back button, a step count, and a summary at the end.
Multiple choice
No checks queued
Free text
Waiting for an answer
Skip and multi-line answers
submitOnEnter={false} makes Enter add a line, so only the button sends. onCancel on Root runs when Skip is pressed.
Waiting for an answer
Composer takeover
Answer or skip the question to restore the composer with its draft intact.
Translated labels
Submit without choosing to see the translated validation message.
Text answers
Question.Input is a textarea that grows as the person types. rows sets its starting height, 2 by default, and autoresize={false} keeps that height and scrolls instead. Enter submits and Shift+Enter adds a line; submitOnEnter={false} makes Enter add a line. readonly shows the answer without allowing edits.
In a question with options, pressing 1 to 9 picks the option with that number, and each option shows its key. Digits typed into a text answer are left alone.
Parts
Question.Submit reads labels.submit for its text and labels.submitting for the text it shows while onSubmit is pending; pass children to replace its content. Question.Input takes placeholder and aria-label, which win over labels.placeholder and labels.answer. Submit, Input and Option accept bind:element for their DOM node.
Root, Content, Title, Description, Options, Actions, Cancel and Submit render their children and accept class and other element attributes.
Labels
Built-in text is English by default. Pass a labels object of type QuestionLabels to Question.Root to translate the three validation messages, the message shown when submitting fails, the submit button text, and the answer field's placeholder and accessible name. Every key is optional; omitted keys keep their default.
<script lang="ts">
import * as Question from '@mielui/svelte/components/question';
import type { QuestionLabels } from '@mielui/svelte/components/question';
const labels: QuestionLabels = {
selectAnswer: 'Choisissez une rรฉponse.',
selectAnswers: 'Choisissez au moins une rรฉponse.',
enterAnswer: 'Saisissez une rรฉponse.',
submitError: "La rรฉponse n'a pas pu รชtre envoyรฉe.",
submit: 'Envoyer',
submitting: 'Envoiโฆ',
answer: 'Rรฉponse',
placeholder: 'Saisissez votre rรฉponseโฆ'
};
</script>API reference
Question.Root
Renders a full question form.
labelsQuestionLabelsOverrides the built-in text and accessible names. Every key is optional and English is the fallback.
variant"default" | "inset"insetframes the question with its actions on the frame.Default
'default'statusQuestionStatusCurrent state, which drives the submit control.
Default
'idle'disabledbooleanPrevents interaction and dims the control.
Default
falserequiredbooleanRequires an answer before submitting.
Default
trueautofocusbooleanFocuses the first control when it mounts.
Default
falsenamestringField name of the answer.
Default
'answer'onError((error: unknown) => void)Called with the error when
onSubmitrejects.onCancel((event: MouseEvent) => void)Called when Cancel is pressed.
classstringClasses added to the element.
childrenSnippet<[]>Content rendered inside.
type"single" | "text" | "multiple"singleandmultipleoffer options.texttakes a written answer.Default
'single'valueBindablestring | string[]Current answer. Bindable.
onSubmitRequired(answer: string, event: SubmitEvent) => void | Promise<void> | (answer: string[], event: SubmitEvent) => void | Promise<void>Called with the answer. Return a promise to show progress until it settles.
Question.Actions
Groups cancellation and submit actions.
classstringClasses added to the element.
childrenSnippet<[]>Content rendered inside.
Question.Cancel
Cancels question submission.
childrenSnippet<[]>Content rendered inside.
onclick((event: MouseEvent) => void)Called when it is clicked or activated.
Question.Content
classstringClasses added to the element.
childrenSnippet<[]>Content rendered inside.
Question.Description
Explains the question context.
classstringClasses added to the element.
childrenSnippet<[]>Content rendered inside.
Question.Input
Collects a free-form response.
submitOnEnterbooleanEnter submits and Shift+Enter adds a line. Set false to make Enter add a line.
Default
trueautoresizebooleanGrows with its content instead of scrolling.
Default
trueplaceholderstringText shown while there is no value.
aria-labelstringAccessible name of the answer field.
rowsnumberStarting height in text rows.
Default
2disabledbooleanPrevents interaction and dims the control.
Default
falsereadonlybooleanShows the value without allowing edits. The value is still submitted.
Default
falseelementBindableHTMLTextAreaElementBindable reference to the DOM element.
classstringClasses added to the element.
Question.Option
Represents a question option.
valueRequiredstringValue this option answers with.
labelRequiredstringVisible label of the option.
descriptionstringSupporting text under the option label.
disabledbooleanPrevents interaction and dims the control.
Default
falseelementBindableHTMLInputElementBindable reference to the DOM element.
classstringClasses added to the element.
Question.Options
Wraps answer option items.
classstringClasses added to the element.
childrenSnippet<[]>Content rendered inside.
Question.Submit
Submits the selected answer.
childrenSnippet<[]>Content rendered inside.
elementBindableHTMLButtonElement | HTMLAnchorElementBindable reference to the DOM element.
Question.Title
Shows the question heading.
classstringClasses added to the element.
childrenSnippet<[]>Content rendered inside.
pnpm dlx @mielui/svelte add question