# Questionnaire

> A form that asks one question at a time, with single and multiple choice, free text and skippable questions.

## Anatomy

Put each question in a `<fieldset>` inside a `<form class="questionnaire">`, and add a `<footer>` with the buttons that move between them. The form asks one question at a time.

Use the `<legend>` for the prompt and a `<p>` under it to describe the question. Answers are ordinary [radio buttons](/radio-buttons.html.md), [checkboxes](/checkboxes.html.md) or [text fields](/text-inputs.html.md). A `<small>` inside a choice's label describes that choice. Mark the footer buttons by their `value`: `previous`, `skip` and `next`, plus a submit button. The component adds the progress row and shows only the question and buttons that apply.

Use a questionnaire for a short survey, onboarding or intake form where each question deserves the whole screen. For a long form people need to scan and correct, keep every field on the page. The [stepper](/stepper.html.md) suits a few large stages, such as a checkout, each with several fields.

What should we prototype next?

Pick the direction you want to explore first.

SearchFind any page in two keystrokes SharingSend a view to a teammate Offline modeKeep working without a connection

Choose an answer to continue.

Where do you use the app?

Choose all that apply.

Web Desktop Mobile

Choose at least one platform.

Anything else we should know?

Optional. Skip it if nothing comes to mind.

Notes

Back Skip Next Submit

```
<form class="questionnaire" aria-label="Product survey">
  <fieldset>
    <legend>What should we prototype next?</legend>
    <p>Pick the direction you want to explore first.</p>
    <label><input type="radio" name="direction" value="search" required>Search<small>Find any page in two keystrokes</small></label>
    <label><input type="radio" name="direction" value="sharing" required>Sharing<small>Send a view to a teammate</small></label>
    <p class="questionnaire-error">Choose an answer to continue.</p>
  </fieldset>
  <fieldset data-required>
    <legend>Where do you use the app?</legend>
    <label><input type="checkbox" name="platforms" value="web">Web</label>
    <label><input type="checkbox" name="platforms" value="mobile">Mobile</label>
    <p class="questionnaire-error">Choose at least one platform.</p>
  </fieldset>
  <fieldset>
    <legend>Anything else we should know?</legend>
    <div class="field">
      <textarea id="notes" name="notes" class="expressive-textarea" placeholder=" "></textarea>
      <label for="notes">Notes</label>
    </div>
  </fieldset>
  <footer>
    <button type="button" class="text" value="previous">Back</button>
    <button type="button" class="text" value="skip">Skip</button>
    <button type="button" value="next">Next</button>
    <button type="submit">Submit</button>
  </footer>
</form>
```

## Questions

The answers decide the kind of question. Radio buttons with one `name` make a single choice, and checkboxes make a multiple choice. A text field or textarea takes a free answer, and a question can mix choices with a text field for "Other".

Add `required` to one of a question's controls to make the question required. HTML has no way for a group of checkboxes to say "at least one", so put `data-required` on its fieldset instead. Any other question is optional, and the Skip button appears for it. Skipping clears whatever was answered, so the form sends nothing for that question. Leave out the Skip button to make people press Next instead.

## Validation

Next checks the shown question with the browser's own validation, so `required`, `pattern`, `minlength` and the input types all work. When the answer is missing or wrong, the fieldset gets `invalid` and its controls get `aria-invalid="true"`. Its `.questionnaire-error` shows, and focus moves to the control to fix. Answering clears the error.

Write the error text in the question so it can be specific and in the page's language. A question without one shows the browser's validation message, or the `requiredMessage` option for a `data-required` group. Submitting checks every question and opens the first one that still needs an answer, whether it comes from the button, Enter or `requestSubmit()`. Only enabled checkboxes count toward `data-required`, since the browser does not send a disabled one.

## Conditional questions

To branch, enable or disable questions based on earlier answers. The component skips a disabled fieldset, and the progress count leaves it out. The browser does not send the controls of a disabled fieldset.

```
<fieldset>
  <legend>Do you work in a team?</legend>
  <label><input type="radio" name="team" value="yes" required>Yes</label>
  <label><input type="radio" name="team" value="no" required>No</label>
</fieldset>
<fieldset id="team-size" disabled>
  <legend>How many people are on your team?</legend>
  …
</fieldset>

<script>
  form.addEventListener('change', () => {
    document.getElementById('team-size').disabled = form.elements.team.value !== 'yes';
  });
</script>
```

## Submitting

The submit button shows on the last question. Pressing it sends the form like any other, to its `action` or to a `submit` listener that reads the answers with `FormData`. Use `getAll()` for checkboxes. Without JavaScript, every question shows at once, the navigation buttons stay hidden, and the submit button still sends the form. In that case nothing checks a `data-required` group.

```
form.addEventListener('submit', (event) => {
  event.preventDefault();
  const answers = new FormData(event.currentTarget);
  answers.get('direction');    // 'search'
  answers.getAll('platforms'); // ['web', 'mobile']
});
```

## Shortcuts, review and saving

Add any of these three attributes to the form:

-   `data-shortcuts` lets a letter or a digit pick the answer at that place in the shown question, A or 1 for the first. It ends each choice with its letter, which screen readers skip. Keys typed into a text field stay text.
-   `data-summary` adds a review step after the last question. It lists every question with its answer and an Edit button back to it. The submit button shows there instead.
-   `data-save="key"` keeps the answers and the shown question in `localStorage` under that key. It restores them when the page comes back and forgets them once the form is submitted. A restored answer fires `change`, so conditional questions open as they did. File inputs are not saved. Keep personal answers out of saved questionnaires on shared devices.

```
<form class="questionnaire" aria-label="Shortcut survey" data-shortcuts data-summary data-save="survey">
  <fieldset>
    <legend>How did you hear about us?</legend>
    <label><input type="radio" name="heard" value="search" required>Search engine</label>
    <label><input type="radio" name="heard" value="friend" required>A friend</label>
    <label><input type="radio" name="heard" value="ad" required>An ad</label>
  </fieldset>
  <!-- More questions -->
  <footer>
    <button type="button" class="text" value="previous">Back</button>
    <button type="button" class="text" value="skip">Skip</button>
    <button type="button" value="next">Next</button>
    <button type="submit">Submit</button>
  </footer>
</form>
```

## Accessibility

Each question is a native group named by its legend. The component attaches the description and any error with `aria-describedby`. Hidden questions use `hidden`, so they are out of the tab order and the accessibility tree. After Next, Back or Skip, focus moves to the selected answer or the first control of the new question.

Name the form with `aria-label` or a heading. Enter in a field moves to the next question, and on the last one it submits. The progress row is a `<progress>` named by its text, "Question 2 of 3".

## Options

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `progressLabel` | String | `'Question {current} of {total}'` | Text of the progress row. `{current}` and `{total}` become the question number and count. `data-progress-label` on the form sets it. |
| `requiredMessage` | String | `'Choose an answer to continue.'` | Error for a question with no `.questionnaire-error` when the browser has no message, as for a `data-required` group. |
| `summaryLabel` | String | `'Review your answers'` | With `data-summary`, the review step's name and the text of the progress row there. |
| `editLabel` | String | `'Edit'` | With `data-summary`, the text of each answer's button back to its question. |
| `skippedLabel` | String | `'Not answered'` | With `data-summary`, the answer shown for a question left blank. |

## Methods

```
const questionnaire = Expressive.Questionnaire.getInstance(document.querySelector('form.questionnaire'));
questionnaire.items;     // the fieldsets that apply, in order
questionnaire.index;     // position of the shown question
questionnaire.next();    // checks the shown question, then moves on
questionnaire.previous();
questionnaire.skip();    // clears the shown answer, then moves on
questionnaire.goTo(2);   // shows a question without checking, e.g. to resume
questionnaire.review();  // with data-summary, shows the review step
questionnaire.destroy(); // shows every question and removes what it added
```

## Tokens

Override these on the `.questionnaire`.

| Token | Default |
| --- | --- |
| `--md-comp-questionnaire-choice-shape` | 12px |
| `--md-comp-questionnaire-choice-outline-color` | `--md-sys-color-outline-variant` |
| `--md-comp-questionnaire-choice-selected-color` | `--md-sys-color-secondary-container` |
