Skip to content

Commit b3ab3a2

Browse files
react-crossword A11y improvements (#1966)
- Reduce the verbosity of the aria labels on the grid cells - Remove the application role from the parent div as it is making the crossword not very visible to screen readers - We have received feedback from a user that the labels are too verbose and make the crossword very difficult to use. The information that was previously being read out can in fact be inferred by the position on the grid and the previous navigation context. **previously** Every cell had information about what clue it was part of and how many letters there were in the word Every Cell -> `Letter 2 of 4-across: Life is in a mess (5 letters). Also, letter 1 of 5-down Life is always in a mess (2 letters).` **now** Only the cells at the start of a word have info about the clue and length. This gives the context for the surrounding letters. This should reduce the overload of information read out. First Cell -> `4-across: Life is in a mess (5 letters).`
1 parent 19d05f4 commit b3ab3a2

File tree

4 files changed

+26
-35
lines changed

4 files changed

+26
-35
lines changed

libs/@guardian/react-crossword/src/components/Cell.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { css } from '@emotion/react';
2+
import { isUndefined } from '@guardian/libs';
23
import { textSans12 } from '@guardian/source/foundations';
34
import type { FormEvent, KeyboardEvent, SVGProps } from 'react';
45
import { useEffect, useRef } from 'react';
@@ -48,9 +49,10 @@ const CellComponent = ({
4849
const theme = useTheme();
4950
const { getId } = useData();
5051
const cellRef = useRef<null | SVGGElement>(null);
51-
const cellDescription = data.description
52-
? (isIncorrect ? 'Incorrect letter. ' : '') + data.description
53-
: '';
52+
const cellDescription =
53+
isUndefined(data.description) && !isIncorrect
54+
? undefined
55+
: `${isIncorrect ? 'Incorrect Letter. ' : ''} ${data.description ?? ''}`;
5456

5557
const backgroundColor = isBlackCell
5658
? 'transparent'
@@ -137,7 +139,6 @@ const CellComponent = ({
137139
id={getId(`cell-input-${data.x}-${data.y}`)}
138140
onInput={handleInput ?? noop}
139141
tabIndex={isCurrentCell ? 0 : -1}
140-
aria-label="Crossword cell"
141142
aria-description={cellDescription}
142143
css={css`
143144
width: 100%;

libs/@guardian/react-crossword/src/components/Crossword.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ export const Crossword = ({
6565
return (
6666
<ContextProvider theme={theme} data={data} userProgress={progress}>
6767
<div
68-
role="application"
6968
data-link-name="Crosswords"
7069
css={css`
7170
*,

libs/@guardian/react-crossword/src/components/Grid.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ export const Grid = () => {
461461
viewBox={`0 0 ${maxWidth} ${maxHeight}`}
462462
tabIndex={-1}
463463
role={'grid'}
464+
aria-label={'Crossword Grid'}
464465
onKeyDown={navigateGrid}
465466
onFocus={handleGridFocus}
466467
onBlur={handleGridBlur}

libs/@guardian/react-crossword/src/utils/getCellDescription.ts

+20-30
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,40 @@ import { formatClueForScreenReader } from './formatClueForScreenReader';
55

66
/**
77
* Get the description of a cell in the format
8-
* "Letter 2 of 4-across: Life is in a mess (5 letters).
9-
* Also, letter 1 of 5-down Life is always in a mess (2 letters)."
8+
* in the format " 4-across: Life is in a mess (5 letters)."
109
*/
1110
export const getCellDescription = (cell: Cell, entries: Entries) => {
1211
const cellEntryIds = cell.group ?? [];
13-
const cellRelevantEntryId =
14-
cell.group?.length === 1
15-
? cell.group[0]
16-
: cellEntryIds.find((id) => id.endsWith('across'));
17-
if (isUndefined(cellRelevantEntryId)) {
18-
return 'Blank cell.';
12+
const cellNumber = cell.number;
13+
if (isUndefined(cellNumber)) {
14+
return undefined;
1915
}
20-
const additionalEntries = cellEntryIds
21-
.filter((id) => !id.endsWith('across') && id !== cellRelevantEntryId)
22-
.map((id) => entries.get(id))
23-
.filter((entry) => !isUndefined(entry));
24-
const relevantEntry = entries.get(cellRelevantEntryId);
25-
26-
return (
27-
`` +
28-
// ('Letter 2 of 4-across: Life is in a mess (5 letters).) | ('Blank cell.')
29-
`${relevantEntry ? `${getReadableLabelForCellAndEntry({ entry: relevantEntry, cell: cell })}. ` : 'Blank. '}` +
30-
// (Also, letter 1 of 5-down Life is always in a mess (2 letters).)
31-
`${additionalEntries.map((entry) => getReadableLabelForCellAndEntry({ entry, cell: cell, additionalEntry: true })).join('. ')}`
16+
const cellRelevantEntryIds = cellEntryIds.filter((id) =>
17+
id.startsWith(cellNumber.toString()),
3218
);
19+
if (cellRelevantEntryIds.length === 0) {
20+
return undefined;
21+
}
22+
return cellRelevantEntryIds
23+
.map((entryId) => {
24+
const entry = entries.get(entryId);
25+
if (entry) {
26+
return getReadableLabelForCellAndEntry({ entry, cell });
27+
}
28+
return undefined;
29+
})
30+
.join(' Also, ');
3331
};
3432

3533
/**
3634
* get the readable label for a cell and entry combination
37-
* in the format "Letter 2 of 4-across: Life is in a mess (5 letters)."
38-
* or "Also, letter 1 of 5-down Life is always in a mess (2 letters)."
35+
* in the format " 4-across: Life is in a mess (5 letters)."
3936
*/
4037
const getReadableLabelForCellAndEntry = ({
4138
entry,
42-
cell,
43-
additionalEntry = false,
4439
}: {
4540
entry: CAPIEntry;
4641
cell: Cell;
47-
additionalEntry?: boolean;
4842
}): string => {
49-
const cellPosition =
50-
entry.direction === 'across'
51-
? String(cell.x + 1 - entry.position.x)
52-
: String(cell.y + 1 - entry.position.y);
53-
return `${additionalEntry ? 'Also, letter' : 'Letter'} ${cellPosition} of ${entry.length}. ${entry.id}. ${formatClueForScreenReader(entry.clue)}`;
43+
return `${entry.id}: ${formatClueForScreenReader(entry.clue)}`;
5444
};

0 commit comments

Comments
 (0)