Skip to content

Commit

Permalink
Improve mutually exclusive screen reader alerts (#263)
Browse files Browse the repository at this point in the history
* Prevent screen reader from reading Or

* Prevent screen reader from saying "Table end" when selecting a label in a field__item

* Improve aria-live for mutually exclusive

* Ensure alert message is read by screen reader and fix spacing of checkbox / radios

* Fix mutually exclusive fields layout

* Fix layout of mutually exclusive checkbox and change deselection message to only fire when relevent

* Ensure characters remaining isnt read out when textarea cleared programatically

* Ensure "Select all that apply" message isnt part of legend

* Fix layout of mutually exclusive prefixed/suffixed field

* Update all input types to be able to be run in "questionMode" as this will be required for mutually exclusive questions

* Fix mutually exlusive for inputs with prefix/suffix and no label

* Fix tagName detection

* Update radios and checkboxes to prevent screen reader from focusing on inner label elements, but still read the labels

* Fix clear meassage for prefixed/suffixed inputs not in a group

* Fix label for mutiple suffixed/prefixed and add unit test

* Fix char limit unit tests

* Improve char limit test

* Correct spelling

Co-Authored-By: bameyrick <[email protected]>
  • Loading branch information
bameyrick authored Mar 5, 2019
1 parent f5ad5c3 commit 47879b3
Show file tree
Hide file tree
Showing 32 changed files with 804 additions and 365 deletions.
5 changes: 3 additions & 2 deletions src/components/checkboxes/_checkbox-macro.njk
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{% macro onsCheckbox(params) %}
<div class="checkbox {{ params.classes }}">
<input class="checkbox__input {{ params.inputClasses }}" name="{{ params.name }}" id="{{ params.id }}" value="{{ params.value }}"{% if params.checked %} checked{% endif %} type="checkbox"{% for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
<label class="checkbox__label {{ params.label.classes }}" for="{{ params.id }}">
<input class="checkbox__input {{ params.inputClasses }}" name="{{ params.name }}" id="{{ params.id }}" value="{{ params.value }}" aria-labeledby="{{ params.id }}-label"{% if params.checked %} checked{% endif %} type="checkbox"{% for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
{# aria-hidden is used to prevent screen readers from being able to focus the individual elements within a label, and combined with aria-labeledby on the input to ensure the label is still read #}
<label id="{{ params.id }}-label" aria-hidden="true" class="checkbox__label {{ params.label.classes }}" for="{{ params.id }}">
{{ params.label.text | safe }}
{% if params.label.description %}
<br>
Expand Down
4 changes: 4 additions & 0 deletions src/components/checkboxes/_checkbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ $checkbox-label-vertical-padding: 0.75rem;
border: 1px solid $color-input;
border-radius: 3px;
}

* {
pointer-events: none;
}
}

&__description {
Expand Down
76 changes: 41 additions & 35 deletions src/components/checkboxes/_template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,48 @@
"description": params.description,
"classes": params.classes,
"mutuallyExclusive": params.mutuallyExclusive,
"legendClasses": params.legendClasses
"legendClasses": params.legendClasses,
"questionMode": params.questionMode
}) %}
{% for checkbox in params.checkboxes %}
{% set labelHTML = checkbox.label.text %}
{% if params.mutuallyExclusive %}
{% set exclusiveClass = ' js-exclusive-group' %}
{% endif %}

<div class="field__item">
{{
onsCheckbox({
"id": checkbox.id,
"name": checkbox.name,
"value": checkbox.value,
"checked": checkbox.checked,
"classes": checkbox.classes,
"inputClasses": exclusiveClass,
"label": {
"for": checkbox.id,
"text": labelHTML,
"description": checkbox.label.description,
"classes": checkbox.label.classes | default('')
},
"attributes": checkbox.attributes,
"other": {
"id": checkbox.other.id,
"name": checkbox.other.name,
"type": checkbox.other.type,
"classes": checkbox.other.classes | default('') + exclusiveClass | default(''),
"attributes": checkbox.other.attributes,
<div class="field__label">{{ params.checkboxesLabel }}</div>
<div class="field__items">
{% for checkbox in params.checkboxes %}
{% set labelHTML = checkbox.label.text %}
{% if params.mutuallyExclusive %}
{% set exclusiveClass = ' js-exclusive-group' %}
{% endif %}
<div class="field__item">
{{
onsCheckbox({
"id": checkbox.id,
"name": checkbox.name,
"value": checkbox.value,
"checked": checkbox.checked,
"classes": checkbox.classes,
"inputClasses": exclusiveClass,
"label": {
"text": checkbox.other.label.text
"for": checkbox.id,
"text": labelHTML,
"description": checkbox.label.description,
"classes": checkbox.label.classes | default('')
},
"attributes": checkbox.attributes,
"other": {
"id": checkbox.other.id,
"name": checkbox.other.name,
"type": checkbox.other.type,
"classes": checkbox.other.classes | default('') + exclusiveClass | default(''),
"attributes": checkbox.other.attributes,
"label": {
"text": checkbox.other.label.text
}
}
}
})
}}
</div>
{% endfor %}
})
}}
</div>
{% if not loop.last %}
<br>
{% endif %}
{% endfor %}
</div>
{% endcall %}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{% from "components/checkboxes/_macro.njk" import onsCheckboxes %}
{{
onsCheckboxes({
"legend": "Select all that apply",
"legend": "What are your favourite pizza toppings?",
"checkboxesLabel": "Select all that apply",
"name": "food-other",
"checkboxes": [
{
Expand Down
3 changes: 2 additions & 1 deletion src/components/checkboxes/examples/checkboxes/index.njk
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{% from "components/checkboxes/_macro.njk" import onsCheckboxes %}
{{
onsCheckboxes({
"legend": "Select all that apply",
"legend": "What are your favourite pizza toppings?",
"checkboxesLabel": "Select all that apply",
"name": "food",
"checkboxes": [
{
Expand Down
15 changes: 4 additions & 11 deletions src/components/date-input/_template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,15 @@
{% set exclusiveClass = " js-exclusive-group" %}
{% endif %}

{% if params.questionMode %}
{% set legend = "<h1>" + params.legend + "</h1>" %}
{% set classes = "field--question" + (" " + params.classes if params.classes) %}
{% else %}
{% set legend = params.legend %}
{% set classes = "field--date" + (" " + params.classes if params.classes) %}
{% endif %}

{% call onsField({
"id": params.id,
"legend": legend,
"legend": params.legend,
"description": params.description,
"classes": classes,
"classes": "field--date" + (" " + params.classes if params.classes),
"descriptionClasses": descriptionClasses | default(""),
"legendClasses": params.legendClasses,
"mutuallyExclusive": params.mutuallyExclusive
"mutuallyExclusive": params.mutuallyExclusive,
"questionMode": params.questionMode
}) %}
<div class="field__group">
{% if params.day %}
Expand Down
33 changes: 18 additions & 15 deletions src/components/field/_field.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@
}

&__legend {
margin-bottom: 0.5rem;
font-size: 1rem;
font-weight: $font-weight-bold;
@extend .label;
}

&__label {
margin-bottom: 0.5rem;
}

&__description {
margin-bottom: 0.5em;
@extend .label__description;
}

&__group {
Expand All @@ -25,22 +23,25 @@
}

&__item {
display: table;
display: inline-block;
position: relative;
}

& > &__item {
margin: 1rem 0.5rem 0.5rem 0;
width: 100%;
& > &,
&__items > & {
&__item {
margin: 0 0 0.5rem;
width: 100%;

&:last-of-type {
margin-bottom: 0;
}
&:last-child {
margin-bottom: 0;
}

@include mq(m) {
&:not([class*='field__item--w-']) {
width: auto;
min-width: 20rem;
@include mq(m) {
&:not([class*='field__item--w-']) {
width: auto;
min-width: 20rem;
}
}
}
}
Expand Down Expand Up @@ -69,10 +70,12 @@
&--question & {
&__legend h1 {
@extend .u-fs-l;
display: inline-block;
margin-bottom: 0;
}

&__description {
@extend .u-fs-r;
@extend .u-mb-m;
}
}
Expand Down
32 changes: 23 additions & 9 deletions src/components/field/_template.njk
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
{% if params.mutuallyExclusive or params.legend %}
<fieldset{% if params.id %} id="{{ params.id }}"{% endif %} class="field{% if params.mutuallyExclusive %} field--exclusive{% endif %}{% if params.classes %} {{ params.classes }}{% endif %}"{% if params.description %} aria-describedby="{{ params.id }}-description"{% endif %}>
<legend class="field__legend {{ params.legendClasses }}">{{ params.legend | safe }}</legend>
{% if params.description %}
<div id="{{ params.id }}-description" class="field__description">{{ params.description }}</div>
{% endif %}
{% if params.questionMode %}
{% set legend = "<h1>" + params.legend + "</h1>" %}
{% set classes = "field--question" + (" " + params.classes if params.classes) %}
{% else %}
{% set legend = params.legend %}
{% set classes = params.classes %}
{% endif %}
<fieldset{% if params.id %} id="{{ params.id }}"{% endif %} class="field{% if params.mutuallyExclusive %} field--exclusive{% endif %}{% if classes %} {{ classes }}{% endif %}">
<legend class="field__legend {{ params.legendClasses }}">
{{ legend | safe }}
{% if params.description %}
<br>
<span class="field__description">{{ params.description }}</span>
{% endif %}
</legend>
{{ caller() }}
{% if params.mutuallyExclusive %}
{% from "components/checkboxes/_checkbox-macro.njk" import onsCheckbox %}
{% from "components/checkboxes/_checkbox-macro.njk" import onsCheckbox %}
<br>
<div class="field__item">
<div class="field__label u-fs-r--b">{{ params.mutuallyExclusive.or }}</div>
<div class="field__label u-fs-r--b" aria-hidden="true">{{ params.mutuallyExclusive.or }}</div>
{{
onsCheckbox({
"id": params.mutuallyExclusive.checkbox.id,
Expand All @@ -17,12 +28,15 @@
"classes": params.mutuallyExclusive.checkbox.classes,
"inputClasses": "js-exclusive-checkbox",
"label": {
"text": '<span class="u-vh">' + params.mutuallyExclusive.or + ', </span> ' + params.mutuallyExclusive.checkbox.label.text + '<span class="u-vh">. ' + params.mutuallyExclusive.deselectMessage + '</span>'
"text": '<span class="u-vh">' + params.mutuallyExclusive.or + ', </span> ' + params.mutuallyExclusive.checkbox.label.text
},
"attributes": {
"data-deselect-message": params.mutuallyExclusive.deselectMessage
}
})
}}
</div>
<span class="js-exclusive-alert u-vh" role="alert" aria-live="polite" data-adjective="{{ params.mutuallyExclusive.deselectAdjective }}"></span>
<span class="js-exclusive-alert u-vh" role="alert" aria-live="polite" data-group-adjective="{{ params.mutuallyExclusive.deselectGroupAdjective }}" data-checkbox-adjective="{{ params.mutuallyExclusive.deselectCheckboxAdjective }}"></span>
{% endif %}
</fieldset>
{% else %}
Expand Down
7 changes: 5 additions & 2 deletions src/components/input/_input-type.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
.input-type {
display: inline-flex;
position: relative;
// Keep the entire component display block, but use inline-flex on inner to prevent the orange focus from going full width
&__inner {
display: inline-flex;
position: relative;
}

// Double ampersand is needed to solve specificity issues
& &__input {
Expand Down
7 changes: 5 additions & 2 deletions src/components/input/_template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"noField": params.noField,
"legend": params.label.text if params.mutuallyExclusive,
"description": params.label.description if params.mutuallyExclusive,
"mutuallyExclusive": params.mutuallyExclusive
"mutuallyExclusive": params.mutuallyExclusive,
"questionMode": params.questionMode
}) %}
{% if params.label and not params.mutuallyExclusive %}
{{
Expand All @@ -41,6 +42,7 @@
{% set prefixClass = " input-type--prefix" %}
{% endif %}
<div class="input-type{{ prefixClass }}">
<div class="input-type__inner">
{% endif %}
<input
type="{{ type }}"
Expand All @@ -58,7 +60,8 @@
{% for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}
/>
{% if params.prefix !== undefined or params.suffix !== undefined %}
<abbr class="input-type__type" aria-hidden="true" title="{{ params.prefix.title }}{{ params.suffix.title }}">{{ params.prefix.text or params.prefix.title }}{{ params.suffix.text or params.suffix.title }}</abbr>
<abbr class="input-type__type" aria-hidden="true" title="{{ params.prefix.title }}{{ params.suffix.title }}">{{ params.prefix.text or params.prefix.title }}{{ params.suffix.text or params.suffix.title }}</abbr>
</div>
</div>
{% endif %}
{% endcall %}
1 change: 1 addition & 0 deletions src/components/label/_label.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
font-weight: 600;

&__description {
@extend .u-fs-s;
display: inline-block;
line-height: 1.4;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/label/_template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
{{ params.text | safe }}
{% if params.description %}
<br>
<span class="label__description u-fs-s">{{ params.description }}</span>
<span class="label__description">{{ params.description }}</span>
{% endif %}
</label>
10 changes: 6 additions & 4 deletions src/components/mutually-exclusive/examples/checkboxes/index.njk
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{% from "components/checkboxes/_macro.njk" import onsCheckboxes %}
{{
onsCheckboxes({
"legend": "Select all that apply",
"legend": "What type of central heating do you have?",
"checkboxesLabel": "Select all that apply",
"name": "mutually-exclusive",
"checkboxes": [
{
Expand Down Expand Up @@ -41,9 +42,10 @@
}
],
"mutuallyExclusive": {
"or": 'Or',
"deselectMessage": 'Selecting this will uncheck all other checkboxes',
"deselectAdjective": 'deselected',
"or": "Or",
"deselectMessage": "Selecting this will uncheck all other checkboxes",
"deselectGroupAdjective": "deselected",
"deselectCheckboxAdjective": "deselected",
"checkbox": {
"id": "no-central-heating",
"label": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
},
"mutuallyExclusive": {
"or": "Or",
"deselectMessage": 'Selecting this will clear the date if one has been inputted',
"deselectAdjective": "deselected",
"deselectMessage": "Selecting this will clear the date if one has been inputted",
"deselectGroupAdjective": "cleared",
"deselectCheckboxAdjective": "deselected",
"checkbox": {
"id": "date-exclusive-checkbox",
"name": "no-paid-job",
Expand Down
5 changes: 3 additions & 2 deletions src/components/mutually-exclusive/examples/duration/index.njk
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
"description": "If you have lived at this address for less than a year then enter 0 into the year input.",
"mutuallyExclusive": {
"or": "Or",
"deselectMessage": 'Selecting this will clear the date if one has been inputted',
"deselectAdjective": "deselected",
"deselectMessage": "Selecting this will clear the date if one has been inputted",
"deselectGroupAdjective": "cleared",
"deselectCheckboxAdjective": "deselected",
"checkbox": {
"id": "duration-exclusive-checkbox",
"name": "no-duration",
Expand Down
5 changes: 3 additions & 2 deletions src/components/mutually-exclusive/examples/email/index.njk
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
},
"mutuallyExclusive": {
"or": "Or",
"deselectMessage": 'Selecting this will clear your email',
"deselectAdjective": "deselected",
"deselectMessage": "Selecting this will clear your email",
"deselectGroupAdjective": "cleared",
"deselectCheckboxAdjective": "deselected",
"checkbox": {
"id": "email-checkbox",
"name": "no-email",
Expand Down
Loading

0 comments on commit 47879b3

Please sign in to comment.