UX Writing for Digital Services - Structuring forms and grouping questions
At the start of a form or service without optional fields, state that all fields are required.
At the start of a form with optional fields, state that all fields are required, except fields marked 'optional'.
Do not mark a required field with an asterisk (*). Screen readers may not announce asterisks as required.
One thing per screen
Only ask people one question or to do one thing on each screen in a form or service.
This helps people understand what you're asking them to do and to focus on the specific question. If there is only one choice to make, it reduces cognitive load and keeps the focus on the task.
<form>
<fieldset class="hse-fieldset">
<legend class="hse-fieldset__legend hse-fieldset__legend--xl">
<h1 class="hse-fieldset__heading">
Where were you born?
</h1>
</legend>
<div class="hse-form-group">
<label class="hse-label" for="country-of-birth">
Country of birth
</label>
<select class="hse-select" id="country-of-birth" name="country-of-birth">
<option value="" selected disabled>Select a county</option>
<option value="ireland">Ireland</option>
<option value="united-kingdom">United Kingdom</option>
<option value="united-states">United States</option>
<option value="canada">Canada</option>
<option value="australia">Australia</option>
</select>
</div>
<div class="hse-form-group">
<div class="hse-checkboxes">
<div class="hse-checkboxes__item">
<input class="hse-checkboxes__input" id="dont-know-1" name="dont-know" type="checkbox" value="dont-know">
<label class="hse-label hse-checkboxes__label" for="dont-know-1">
I do not know my place of birth
</label>
</div>
</div>
</div>
</fieldset>
<button class="hse-button hse-input--width-5 hse-button--secondary" type="submit">
Continue
</button>
</form>
Nunjucks macro options
Use options to customise the appearance, content and behaviour of a component when using a macro, for example, changing the text.
Some options are required for the macro to work; these are marked as "Required" in the option description.
If you're using Nunjucks macros in production with "html" options, or ones ending with "html", you must sanitise the HTML to protect against cross-site scripting exploits.
| Name | Type | Required | Description |
|---|---|---|---|
| Name id | Type string | Required true | Description Id for each select box. |
| Name name | Type string | Required true | Description Name property for the select. |
| Name items | Type array | Required true | Description Array of option items for the select. |
| Name items[].value | Type string | Required false | Description Value for the option item. Defaults to an empty string. |
| Name items[].text | Type string | Required true | Description Text for the option item. |
| Name items[].selected | Type boolean | Required false | Description Sets the option as the selected. |
| Name items[].disabled | Type boolean | Required false | Description Sets the option item as disabled. |
| Name items[].attributes | Type object | Required false | Description HTML attributes (for example data attributes) to add to the option. |
| Name label | Type object | Required false | Description Label text or HTML by specifying value for either text or html keys. |
| Name hint | Type object | Required false | Description Options for the hint component. |
| Name errorMessage | Type object | Required false | Description Options for the error message component. The error message component will not display if you use a falsy value for `errorMessage`, for example `false` or `null`. |
| Name classes | Type string | Required false | Description Classes to add to the select. |
| Name attributes | Type object | Required false | Description HTML attributes (for example data attributes) to add to the select. |
{% from 'fieldset/macro.njk' import fieldset %}
{% from 'select/macro.njk' import select %}
{% from 'checkboxes/macro.njk' import checkboxes %}
{% from 'button/macro.njk' import button %}
<form>
{% call fieldset({
legend: {
text: "Where were you born?",
classes: "hse-fieldset__legend--xl",
isPageHeading: true
}
}) %}
{{ select({
"id": "country-of-birth",
"name": "country-of-birth",
"label": {
"text": "Country of birth"
},
"items": [
{
"value": "",
"text": "Select a county",
"selected": true,
"disabled": true
},
{
"value": "ireland",
"text": "Ireland"
},
{
"value": "united-kingdom",
"text": "United Kingdom"
},
{
"value": "united-states",
"text": "United States"
},
{
"value": "canada",
"text": "Canada"
},
{
"value": "australia",
"text": "Australia"
}
]
}) }}
{{ checkboxes({
"idPrefix": "dont-know",
"name": "dont-know",
"items": [
{
"value": "dont-know",
"text": "I do not know my place of birth"
}
]
}) }}
{% endcall %}
{{ button({
"text": "Continue",
"classes": "hse-input--width-5 hse-button--secondary"
}) }}
</form>
This makes it easier to catch errors and for people to fix them. If there's only one thing to fix, it is easier to fix. This reduces the chance of people giving up. It is also better for accessibility. A simple, focused layout is helpful for people using assistive technologies. People who use a screen reader can navigate to the first heading and quickly interact.
Give people the option to answer 'I do not know' or 'I'm not sure' if they are valid responses.
Grouping questions
Sometimes it makes sense to group a number of related questions on the same screen. An example is the medical card details screen in image 2. It is OK to ask for the medical card number and the expiry date because they are directly related.
If you ask for more than one thing on a screen, use a statement as the h1 heading. For example, 'Medical card details'.
<fieldset class="hse-fieldset">
<legend class="hse-fieldset__legend hse-fieldset__legend--xl">
<h1 class="hse-fieldset__heading">
Medical card details
</h1>
</legend>
<div class="hse-form-group">
<label class="hse-label" for="example-with-hint-text">
Medical card number
</label>
<span class="hse-hint" id="example-with-hint-text-hint">
For example, 046 3000 A
</span>
<input class="hse-input hse-input--width-10" id="example-with-hint-text" name="example-with-hint-text" type="text" aria-describedby="example-with-hint-text-hint">
</div>
<div class="hse-form-group">
<fieldset class="hse-fieldset" aria-describedby="dob-with-autocomplete-attribute-hint" role="group">
<legend class="hse-fieldset__legend hse-fieldset__legend--s hse-u-margin-bottom-2">
Expiry date
</legend>
<span class="hse-hint" id="dob-with-autocomplete-attribute-hint">
For example, 31 3 1980
</span>
<div class="hse-date-input" id="dob-with-autocomplete-attribute">
<div class="hse-date-input__item">
<div class="hse-form-group">
<label class="hse-label hse-date-input__label" for="dob-with-autocomplete-attribute-day">
Day
</label>
<input class="hse-input hse-date-input__input hse-input--width-2" id="dob-with-autocomplete-attribute-day" name="dob-with-autocomplete-day" type="number" autocomplete="bday-day" pattern="[0-9]*">
</div>
</div>
<div class="hse-date-input__item">
<div class="hse-form-group">
<label class="hse-label hse-date-input__label" for="dob-with-autocomplete-attribute-month">
Month
</label>
<input class="hse-input hse-date-input__input hse-input--width-2" id="dob-with-autocomplete-attribute-month" name="dob-with-autocomplete-month" type="number" autocomplete="bday-month" pattern="[0-9]*">
</div>
</div>
<div class="hse-date-input__item">
<div class="hse-form-group">
<label class="hse-label hse-date-input__label" for="dob-with-autocomplete-attribute-year">
Year
</label>
<input class="hse-input hse-date-input__input hse-input--width-4" id="dob-with-autocomplete-attribute-year" name="dob-with-autocomplete-year" type="number" autocomplete="bday-year" pattern="[0-9]*">
</div>
</div>
</div>
</fieldset>
</div>
<button class="hse-button hse-input--width-5 hse-button--secondary" type="submit">
Submit
</button>
</fieldset>
Nunjucks macro options
Use options to customise the appearance, content and behaviour of a component when using a macro, for example, changing the text.
Some options are required for the macro to work; these are marked as "Required" in the option description.
If you're using Nunjucks macros in production with "html" options, or ones ending with "html", you must sanitise the HTML to protect against cross-site scripting exploits.
| Name | Type | Required | Description |
|---|---|---|---|
| Name id | Type string | Required true | Description The id of the input. |
| Name name | Type string | Required true | Description The name of the input, which is submitted with the form data. |
| Name type | Type string | Required false | Description Type of input control to render. Defaults to "text". |
| Name inputmode | Type string | Required false | Description Optional value for inputmode. |
| Name value | Type string | Required false | Description Optional initial value of the input. |
| Name describedBy | Type string | Required false | Description One or more element IDs to add to the `aria-describedby` attribute, used to provide additional descriptive information for screenreader users. |
| Name label | Type object | Required true | Description Options for the label component. |
| Name hint | Type object | Required false | Description Options for the hint component. |
| Name errorMessage | Type object | Required false | Description Options for the error message component. The error message component will not display if you use a falsy value for `errorMessage`, for example `false` or `null`. |
| Name prefix | Type object | Required false | Description Options for the prefix element. |
| Name prefix{}.text | Type string | Required true | Description Required. If `html` is set, this is not required. Text to use within the label. If `html` is provided, the `text` argument will be ignored. |
| Name prefix{}.html | Type string | Required true | Description Required. If `text` is set, this is not required. HTML to use within the label. If `html` is provided, the `text` argument will be ignored. |
| Name prefix{}.classes | Type string | Required false | Description Classes to add to the prefix. |
| Name prefix{}.attributes | Type object | Required false | Description HTML attributes (for example data attributes) to add to the prefix element. |
| Name suffix | Type object | Required false | Description Options for the suffix element. |
| Name suffix{}.text | Type string | Required true | Description Required. If `html` is set, this is not required. Text to use within the label. If `html` is provided, the `text` argument will be ignored. |
| Name suffix{}.html | Type string | Required true | Description Required. If `text` is set, this is not required. HTML to use within the label. If `html` is provided, the `text` argument will be ignored. |
| Name suffix{}.classes | Type string | Required false | Description Classes to add to the suffix element. |
| Name suffix{}.attributes | Type object | Required false | Description HTML attributes (for example data attributes) to add to the suffix element. |
| Name formGroup | Type object | Required false | Description Options for the form-group wrapper |
| Name formGroup{}.classes | Type string | Required false | Description Classes to add to the form group (for example to show error state for the whole group) |
| Name classes | Type string | Required false | Description Classes to add to the input. |
| Name autocomplete | Type string | Required false | Description autocomplete attribute to identify input purpose, for instance "postal-code" or "username". |
| Name pattern | Type string | Required false | Description pattern attribute to provide a regular expression pattern, used to match allowed character combinations for the input value. |
| Name spellcheck | Type boolean | Required false | Description Optional field to enable or disable the spellcheck attribute on the input. |
| Name attributes | Type object | Required false | Description HTML attributes (for example data attributes) to add to the input. |
{% from 'input/macro.njk' import input %}
{% from 'date-input/macro.njk' import dateInput %}
{% from 'fieldset/macro.njk' import fieldset %}
{% from 'button/macro.njk' import button %}
{% call fieldset({
legend: {
text: "Medical card details",
classes: "hse-fieldset__legend--xl",
isPageHeading: true
}
}) %}
{{ input({
"label": {
"text": "Medical card number"
},
"hint": {
"text": "For example, 046 3000 A"
},
"id": "example-with-hint-text",
"name": "example-with-hint-text",
"classes": "hse-input--width-10",
"inputmode": "text"
}) }}
{{ dateInput({
"id": "dob-with-autocomplete-attribute",
"namePrefix": "dob-with-autocomplete",
"fieldset": {
"legend": {
"text": "Expiry date",
"classes": "hse-fieldset__legend--s hse-u-margin-bottom-2"
}
},
"hint": {
"text": "For example, 31 3 1980"
},
"items": [
{
"name": "day",
"classes": "hse-input--width-2",
"autocomplete": "bday-day"
},
{
"name": "month",
"classes": "hse-input--width-2",
"autocomplete": "bday-month"
},
{
"name": "year",
"classes": "hse-input--width-4",
"autocomplete": "bday-year"
}
]
}) }}
{{ button({
text: "Submit",
"classes": "hse-input--width-5 hse-button--secondary",
type: "submit"
}) }}
{% endcall %}