@workday/canvas-kit-docs 16.0.13 → 16.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es6/lib/docs.js +61 -4
- package/dist/es6/lib/specs.js +42 -0
- package/dist/es6/lib/stackblitzFiles/packageJSONFile.js +5 -5
- package/dist/es6/lib/stackblitzFiles/packageJSONFile.ts +5 -5
- package/dist/mdx/accessibility/Popups.mdx +1 -5
- package/dist/mdx/accessibility/tables/ExpandableRows.mdx +27 -0
- package/dist/mdx/accessibility/tables/FilterableColumnHeaders.mdx +37 -0
- package/dist/mdx/accessibility/tables/NestedRows.mdx +38 -0
- package/dist/mdx/accessibility/tables/Overview.mdx +28 -0
- package/dist/mdx/accessibility/tables/SelectableRows.mdx +27 -0
- package/dist/mdx/accessibility/tables/SortableColumnHeaders.mdx +23 -0
- package/dist/mdx/accessibility/{TestingTableWithFormFields.mdx → tables/WithFormFields.mdx} +2 -1
- package/dist/mdx/react/table/Table.mdx +181 -6
- package/package.json +6 -6
- package/dist/mdx/accessibility/InlinePortals.mdx +0 -21
- package/dist/mdx/accessibility/TablesAdvanced.mdx +0 -126
package/dist/es6/lib/docs.js
CHANGED
|
@@ -13367,6 +13367,26 @@ export const docs = (typeof window !== 'undefined' && window.__docs) ||
|
|
|
13367
13367
|
"kind": "string",
|
|
13368
13368
|
"value": "tables"
|
|
13369
13369
|
},
|
|
13370
|
+
{
|
|
13371
|
+
"kind": "string",
|
|
13372
|
+
"value": "expandable-rows"
|
|
13373
|
+
},
|
|
13374
|
+
{
|
|
13375
|
+
"kind": "string",
|
|
13376
|
+
"value": "nested-rows"
|
|
13377
|
+
},
|
|
13378
|
+
{
|
|
13379
|
+
"kind": "string",
|
|
13380
|
+
"value": "selectable-rows"
|
|
13381
|
+
},
|
|
13382
|
+
{
|
|
13383
|
+
"kind": "string",
|
|
13384
|
+
"value": "filterable-column-headers"
|
|
13385
|
+
},
|
|
13386
|
+
{
|
|
13387
|
+
"kind": "string",
|
|
13388
|
+
"value": "sortable-column-headers"
|
|
13389
|
+
},
|
|
13370
13390
|
{
|
|
13371
13391
|
"kind": "string",
|
|
13372
13392
|
"value": "popups"
|
|
@@ -13431,6 +13451,26 @@ export const docs = (typeof window !== 'undefined' && window.__docs) ||
|
|
|
13431
13451
|
"kind": "string",
|
|
13432
13452
|
"value": "tables"
|
|
13433
13453
|
},
|
|
13454
|
+
{
|
|
13455
|
+
"kind": "string",
|
|
13456
|
+
"value": "expandable-rows"
|
|
13457
|
+
},
|
|
13458
|
+
{
|
|
13459
|
+
"kind": "string",
|
|
13460
|
+
"value": "nested-rows"
|
|
13461
|
+
},
|
|
13462
|
+
{
|
|
13463
|
+
"kind": "string",
|
|
13464
|
+
"value": "selectable-rows"
|
|
13465
|
+
},
|
|
13466
|
+
{
|
|
13467
|
+
"kind": "string",
|
|
13468
|
+
"value": "filterable-column-headers"
|
|
13469
|
+
},
|
|
13470
|
+
{
|
|
13471
|
+
"kind": "string",
|
|
13472
|
+
"value": "sortable-column-headers"
|
|
13473
|
+
},
|
|
13434
13474
|
{
|
|
13435
13475
|
"kind": "string",
|
|
13436
13476
|
"value": "popups"
|
|
@@ -13459,6 +13499,23 @@ export const docs = (typeof window !== 'undefined' && window.__docs) ||
|
|
|
13459
13499
|
}
|
|
13460
13500
|
}
|
|
13461
13501
|
},
|
|
13502
|
+
{
|
|
13503
|
+
"name": "TABLE_PATTERN_SCENARIOS",
|
|
13504
|
+
"fileName": "/home/runner/work/canvas-kit/canvas-kit/modules/mcp/lib/accessibility-enums.ts",
|
|
13505
|
+
"description": "Pattern pages under Guides/Accessibility/Table Patterns, including the overview index.",
|
|
13506
|
+
"declarations": [
|
|
13507
|
+
{
|
|
13508
|
+
"name": "TABLE_PATTERN_SCENARIOS",
|
|
13509
|
+
"filePath": "/home/runner/work/canvas-kit/canvas-kit/modules/mcp/lib/accessibility-enums.ts"
|
|
13510
|
+
}
|
|
13511
|
+
],
|
|
13512
|
+
"tags": {},
|
|
13513
|
+
"type": {
|
|
13514
|
+
"kind": "unknown",
|
|
13515
|
+
"value": "unknown",
|
|
13516
|
+
"text": "readonly AccessibilityScenario[]"
|
|
13517
|
+
}
|
|
13518
|
+
},
|
|
13462
13519
|
{
|
|
13463
13520
|
"name": "ACCESSIBILITY_COMPONENTS",
|
|
13464
13521
|
"fileName": "/home/runner/work/canvas-kit/canvas-kit/modules/mcp/lib/accessibility-enums.ts",
|
|
@@ -13960,7 +14017,7 @@ export const docs = (typeof window !== 'undefined' && window.__docs) ||
|
|
|
13960
14017
|
"value": {
|
|
13961
14018
|
"kind": "symbol",
|
|
13962
14019
|
"name": "AccessibilityScenario",
|
|
13963
|
-
"value": "\"aria-live\" | \"overview\" | \"page-structure\" | \"tables\" | \"
|
|
14020
|
+
"value": "\"aria-live\" | \"overview\" | \"page-structure\" | \"tables\" | \"expandable-rows\" | \"nested-rows\" | \"selectable-rows\" | \"filterable-column-headers\" | \"sortable-column-headers\" | ... 5 more ... | \"color-contrast\""
|
|
13964
14021
|
}
|
|
13965
14022
|
}
|
|
13966
14023
|
}
|
|
@@ -14009,7 +14066,7 @@ export const docs = (typeof window !== 'undefined' && window.__docs) ||
|
|
|
14009
14066
|
"type": {
|
|
14010
14067
|
"kind": "symbol",
|
|
14011
14068
|
"name": "AccessibilityScenario",
|
|
14012
|
-
"value": "\"aria-live\" | \"overview\" | \"page-structure\" | \"tables\" | \"
|
|
14069
|
+
"value": "\"aria-live\" | \"overview\" | \"page-structure\" | \"tables\" | \"expandable-rows\" | \"nested-rows\" | \"selectable-rows\" | \"filterable-column-headers\" | \"sortable-column-headers\" | ... 5 more ... | \"color-contrast\""
|
|
14013
14070
|
},
|
|
14014
14071
|
"description": "",
|
|
14015
14072
|
"declarations": [
|
|
@@ -14040,7 +14097,7 @@ export const docs = (typeof window !== 'undefined' && window.__docs) ||
|
|
|
14040
14097
|
"value": {
|
|
14041
14098
|
"kind": "symbol",
|
|
14042
14099
|
"name": "AccessibilityScenario",
|
|
14043
|
-
"value": "\"aria-live\" | \"overview\" | \"page-structure\" | \"tables\" | \"
|
|
14100
|
+
"value": "\"aria-live\" | \"overview\" | \"page-structure\" | \"tables\" | \"expandable-rows\" | \"nested-rows\" | \"selectable-rows\" | \"filterable-column-headers\" | \"sortable-column-headers\" | ... 5 more ... | \"color-contrast\""
|
|
14044
14101
|
}
|
|
14045
14102
|
}
|
|
14046
14103
|
}
|
|
@@ -47918,7 +47975,7 @@ export const docs = (typeof window !== 'undefined' && window.__docs) ||
|
|
|
47918
47975
|
"type": {
|
|
47919
47976
|
"kind": "enhancedComponent",
|
|
47920
47977
|
"componentType": "subcomponent",
|
|
47921
|
-
"elemPropsHook": "
|
|
47978
|
+
"elemPropsHook": "useMultiSelectSearchInput",
|
|
47922
47979
|
"props": [
|
|
47923
47980
|
{
|
|
47924
47981
|
"kind": "property",
|
package/dist/es6/lib/specs.js
CHANGED
|
@@ -2719,6 +2719,48 @@ module.exports = {specifications: [
|
|
|
2719
2719
|
}
|
|
2720
2720
|
]
|
|
2721
2721
|
},
|
|
2722
|
+
{
|
|
2723
|
+
"type": "file",
|
|
2724
|
+
"name": "MultiSelect",
|
|
2725
|
+
"children": [
|
|
2726
|
+
{
|
|
2727
|
+
"type": "describe",
|
|
2728
|
+
"name": "MultiSelect",
|
|
2729
|
+
"children": [
|
|
2730
|
+
{
|
|
2731
|
+
"type": "describe",
|
|
2732
|
+
"name": "given the Searching story is rendered",
|
|
2733
|
+
"children": [
|
|
2734
|
+
{
|
|
2735
|
+
"type": "describe",
|
|
2736
|
+
"name": "when Enter is pressed before typing",
|
|
2737
|
+
"children": [
|
|
2738
|
+
{
|
|
2739
|
+
"type": "it",
|
|
2740
|
+
"name": "should keep the listbox hidden"
|
|
2741
|
+
}
|
|
2742
|
+
]
|
|
2743
|
+
},
|
|
2744
|
+
{
|
|
2745
|
+
"type": "describe",
|
|
2746
|
+
"name": "when search text is entered using the keyboard",
|
|
2747
|
+
"children": [
|
|
2748
|
+
{
|
|
2749
|
+
"type": "it",
|
|
2750
|
+
"name": "should not have any axe errors"
|
|
2751
|
+
},
|
|
2752
|
+
{
|
|
2753
|
+
"type": "it",
|
|
2754
|
+
"name": "should open the listbox with the filtered result"
|
|
2755
|
+
}
|
|
2756
|
+
]
|
|
2757
|
+
}
|
|
2758
|
+
]
|
|
2759
|
+
}
|
|
2760
|
+
]
|
|
2761
|
+
}
|
|
2762
|
+
]
|
|
2763
|
+
},
|
|
2722
2764
|
{
|
|
2723
2765
|
"type": "file",
|
|
2724
2766
|
"name": "Pagination",
|
|
@@ -18,11 +18,11 @@ export const packageJSONFile = `{
|
|
|
18
18
|
"@emotion/react": "11.11.4",
|
|
19
19
|
"@types/react": "18.2.60",
|
|
20
20
|
"@types/react-dom": "18.2.19",
|
|
21
|
-
"@workday/canvas-kit-labs-react": "16.0.
|
|
22
|
-
"@workday/canvas-kit-preview-react": "16.0.
|
|
23
|
-
"@workday/canvas-kit-react": "16.0.
|
|
24
|
-
"@workday/canvas-kit-react-fonts": "^16.0.
|
|
25
|
-
"@workday/canvas-kit-styling": "16.0.
|
|
21
|
+
"@workday/canvas-kit-labs-react": "16.0.15",
|
|
22
|
+
"@workday/canvas-kit-preview-react": "16.0.15",
|
|
23
|
+
"@workday/canvas-kit-react": "16.0.15",
|
|
24
|
+
"@workday/canvas-kit-react-fonts": "^16.0.15",
|
|
25
|
+
"@workday/canvas-kit-styling": "16.0.15",
|
|
26
26
|
"@workday/canvas-system-icons-web": "^5.0.3",
|
|
27
27
|
"@workday/canvas-expressive-icons-web": "1.0.1",
|
|
28
28
|
"@workday/canvas-tokens-web": "4.4.0-beta.11"
|
|
@@ -19,11 +19,11 @@ export const packageJSONFile = `{
|
|
|
19
19
|
"@emotion/react": "11.11.4",
|
|
20
20
|
"@types/react": "18.2.60",
|
|
21
21
|
"@types/react-dom": "18.2.19",
|
|
22
|
-
"@workday/canvas-kit-labs-react": "16.0.
|
|
23
|
-
"@workday/canvas-kit-preview-react": "16.0.
|
|
24
|
-
"@workday/canvas-kit-react": "16.0.
|
|
25
|
-
"@workday/canvas-kit-react-fonts": "^16.0.
|
|
26
|
-
"@workday/canvas-kit-styling": "16.0.
|
|
22
|
+
"@workday/canvas-kit-labs-react": "16.0.15",
|
|
23
|
+
"@workday/canvas-kit-preview-react": "16.0.15",
|
|
24
|
+
"@workday/canvas-kit-react": "16.0.15",
|
|
25
|
+
"@workday/canvas-kit-react-fonts": "^16.0.15",
|
|
26
|
+
"@workday/canvas-kit-styling": "16.0.15",
|
|
27
27
|
"@workday/canvas-system-icons-web": "^5.0.3",
|
|
28
28
|
"@workday/canvas-expressive-icons-web": "1.0.1",
|
|
29
29
|
"@workday/canvas-tokens-web": "4.4.0-beta.11"
|
|
@@ -44,10 +44,6 @@ hooks. **Tradeoff:** the popup is constrained by ancestor `overflow` and positio
|
|
|
44
44
|
|
|
45
45
|
<ExampleCodeBlock code={InlinePopupNoPortal} />
|
|
46
46
|
|
|
47
|
-
For the same reading-order goal using a **portaled** popup mounted into a sentinel next to the
|
|
48
|
-
trigger (with `PopupStack.pushStackContext`), see
|
|
49
|
-
[**Testing > Inline Portals**](?path=/docs/guides-accessibility-testing-inline-portals--docs).
|
|
50
|
-
|
|
51
47
|
## 2. Reading order with `aria-owns`
|
|
52
48
|
|
|
53
49
|
You can keep the default portal (content at the bottom of `body`) and still try to **re-parent** the
|
|
@@ -67,6 +63,6 @@ that card as “owned” by the trigger for browsing and announcements.
|
|
|
67
63
|
The Canvas Kit [**Dialog**](?path=/docs/components-popups-dialog--docs) builds this pattern in.
|
|
68
64
|
|
|
69
65
|
Another `aria-owns` example:
|
|
70
|
-
[
|
|
66
|
+
[Table Patterns > Filterable Column Headers](?path=/docs/guides-accessibility-table-patterns-filterable-column-headers--docs).
|
|
71
67
|
|
|
72
68
|
<ExampleCodeBlock code={PopupAriaOwns} />
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {ExampleCodeBlock} from '@workday/canvas-kit-docs';
|
|
2
|
+
|
|
3
|
+
import {ExpandableRows} from '../examples/Table/WithExpandableRows';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Expandable Rows
|
|
7
|
+
|
|
8
|
+
Expandable Rows combines the likes of an accordion with tabular data tables. Column 1 renders icon
|
|
9
|
+
buttons with 2 states, a collapsed and expanded state. A new row that spans the entire width of the
|
|
10
|
+
table is added to the table just after the expanded row.
|
|
11
|
+
|
|
12
|
+
- The `aria-expanded` property is added to the chevron button to communicate this state to screen
|
|
13
|
+
reader users.
|
|
14
|
+
- A Canvas accessible `Tooltip` component is used to assign names to each icon button based on the
|
|
15
|
+
most useful value in the row. In this example, we combined the car make (in column 1) and model
|
|
16
|
+
(in column 2) together. This allows everyone to view the name of the icon buttons by hovering the
|
|
17
|
+
mouse or focusing with the keyboard.
|
|
18
|
+
- The expanded row uses `colspan` to span the entire width of the table and support screen readers.
|
|
19
|
+
This space provides flexibility to show headings, lists, and other structured content for the
|
|
20
|
+
table row above.
|
|
21
|
+
- There is no explicit relationship between a row of cells and the spanned content below it. The
|
|
22
|
+
spanned content is assumed to belong to the row of cells above it, based on established accordion
|
|
23
|
+
patterns and logical reading order of content rendered to the screen.
|
|
24
|
+
- Outlining hierarchy with additional nested rows in the table is not supported for screen readers
|
|
25
|
+
in this example.
|
|
26
|
+
|
|
27
|
+
<ExampleCodeBlock code={ExpandableRows} />
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {ExampleCodeBlock} from '@workday/canvas-kit-docs';
|
|
2
|
+
|
|
3
|
+
import {FilterableColumnHeaders} from '../examples/Table/WithColumnHeaderFilters';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Filterable Column Headers
|
|
7
|
+
|
|
8
|
+
In this example, we demonstrate using the `Popup` component in each column header allowing users to
|
|
9
|
+
search and filter the data on the table. The `Popup` component relies on React Portals to render the
|
|
10
|
+
popup elements at the bottom of the browser's DOM presenting 2 key challenges for accessibility:
|
|
11
|
+
|
|
12
|
+
1. Keyboard focus order of the elements in the popup,
|
|
13
|
+
2. Screen readers' reading order of the content rendered in the browser.
|
|
14
|
+
|
|
15
|
+
Here's what we did about it:
|
|
16
|
+
|
|
17
|
+
- Canvas Kit includes a `usePopupModel` hook, with quite a few additional hooks developers can add
|
|
18
|
+
to their models. In particular, the `useFocusRedirect` hook manages keyboard focus between the
|
|
19
|
+
`<Popup.Target>` button and the popup content.
|
|
20
|
+
- The `useInitialFocus` hook allows developers to specify which element receives keyboard focus when
|
|
21
|
+
the popup appears. In this example, we auto-focused the search input field.
|
|
22
|
+
- To address the reading order of content, we set the `aria-owns` property onto the parent
|
|
23
|
+
`<Table.Header>` component (`<th>` DOM element) with 2 unique `id` values. The first `id` refers
|
|
24
|
+
to the `<Popup.Target>` button and the second refers to the `<Popup.Card>` container element. This
|
|
25
|
+
manually reassigns the column header's `<Popup.Target>` button and the `Popup` contents as
|
|
26
|
+
siblings in the browser's accessibility tree hierarchy. Screen readers **should** read the column
|
|
27
|
+
header buttons and the popup content in sequential order even though they are not siblings in the
|
|
28
|
+
DOM.
|
|
29
|
+
- The `type='description'` variant of the Canvas `Tooltip` is used to communicate the filtered state
|
|
30
|
+
of the column header, and assigned to the accessible description of the column header
|
|
31
|
+
`<TertiaryButton>` component.
|
|
32
|
+
- The `AriaLiveRegion` component is used to render the "X of Y items" status inside the table
|
|
33
|
+
caption. This enables screen readers to automatically describe the filter state changes of the
|
|
34
|
+
table content to users in real time. We recommend validating whether this use of a live region is
|
|
35
|
+
well supported for your screen reader and browser combinations first.
|
|
36
|
+
|
|
37
|
+
<ExampleCodeBlock code={FilterableColumnHeaders} />
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {ExampleCodeBlock} from '@workday/canvas-kit-docs';
|
|
2
|
+
|
|
3
|
+
import {NestedRows} from '../examples/Table/WithNestedRows';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Nested Rows
|
|
7
|
+
|
|
8
|
+
Nested Rows shows a hierarchy of related records in **one table**, using additional `<tr>` elements
|
|
9
|
+
for child rows. Expanding a project reveals its phases; expanding a phase reveals its tasks. The
|
|
10
|
+
chevron and name share the Name cell so they indent together. Collapsing a parent hides its
|
|
11
|
+
descendants even if a child was previously expanded.
|
|
12
|
+
|
|
13
|
+
This is a different pattern from
|
|
14
|
+
[Expandable Rows](?path=/docs/guides-accessibility-table-patterns-expandable-rows--docs). That
|
|
15
|
+
example inserts a `colspan` panel with extra content for a single parent row. It does **not** add
|
|
16
|
+
nested table rows. Use Nested Rows when the children are themselves tabular records (same columns at
|
|
17
|
+
every level). Use Expandable Rows when the extra content is not a row of the same table.
|
|
18
|
+
|
|
19
|
+
- Child rows are siblings in the same `<tbody>`, not a nested `<table>` and not extra `<tbody>`
|
|
20
|
+
elements used to fake a tree.
|
|
21
|
+
- The Name cell is the tree column: it holds the chevron `TertiaryButton` and the row name together
|
|
22
|
+
so the control stays next to the label it expands. Leaf rows keep an empty slot the same width as
|
|
23
|
+
the button so names line up with their siblings.
|
|
24
|
+
- The `aria-expanded` property is added to the chevron button to communicate this state to screen
|
|
25
|
+
reader users.
|
|
26
|
+
- A Canvas Kit `Tooltip` names each chevron **Project**, **Phase**, or **Task** based on the row's
|
|
27
|
+
depth. The visible name stays in the row header, so the button name describes the _kind_ of row
|
|
28
|
+
rather than repeating the label.
|
|
29
|
+
- Since those button names are not unique, we added `aria-describedby` to each chevron, referencing
|
|
30
|
+
the unique `id` on the name text in the same cell. That gives screen readers the specific project
|
|
31
|
+
or phase the control belongs to, similar to the
|
|
32
|
+
[Selectable Rows](?path=/docs/guides-accessibility-table-patterns-selectable-rows--docs)
|
|
33
|
+
checkboxes.
|
|
34
|
+
- `aria-level` is set on each `Table.Row` (`1` = project, `2` = phase, `3` = task) to describe
|
|
35
|
+
depth. Support for `aria-level` on HTML table rows is uneven across screen readers and browsers.
|
|
36
|
+
Validate the combinations you support. This is a research example, not a Canvas Kit primitive.
|
|
37
|
+
|
|
38
|
+
<ExampleCodeBlock code={NestedRows} />
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Advanced Table Examples
|
|
2
|
+
|
|
3
|
+
Tables should only be used to organize data that has a clear relationship between rows and columns,
|
|
4
|
+
like a calendar or a schedule. Never use a table just for page layout.
|
|
5
|
+
|
|
6
|
+
When you use the proper HTML table markup, a screen reader can help a user navigate the table. It
|
|
7
|
+
will automatically read the column and row headers as they move through the data, so they always
|
|
8
|
+
know what information they're looking at.
|
|
9
|
+
|
|
10
|
+
- All tables should have a clear header and a descriptive title.
|
|
11
|
+
- Keep your tables simple. If a table is too complex, it might be better to break it up into several
|
|
12
|
+
smaller tables or use a different format.
|
|
13
|
+
|
|
14
|
+
Out of the box, `Table` is a lightweight compound component with a high degree of flexibility, but
|
|
15
|
+
not much functionality outside of providing a basic table layout. This flexibility lets developers
|
|
16
|
+
implement common features, such as selecting rows and sorting columns, on top of `Table` to meet
|
|
17
|
+
their specific application needs.
|
|
18
|
+
|
|
19
|
+
The Workday Accessibility Team has researched and developed the following examples to demonstrate
|
|
20
|
+
how to build these accessible table patterns. We've listed the specific considerations and decisions
|
|
21
|
+
we've made for each of the examples.
|
|
22
|
+
|
|
23
|
+
- [Expandable Rows](?path=/docs/guides-accessibility-table-patterns-expandable-rows--docs)
|
|
24
|
+
- [Nested Rows](?path=/docs/guides-accessibility-table-patterns-nested-rows--docs)
|
|
25
|
+
- [Selectable Rows](?path=/docs/guides-accessibility-table-patterns-selectable-rows--docs)
|
|
26
|
+
- [Filterable Column Headers](?path=/docs/guides-accessibility-table-patterns-filterable-column-headers--docs)
|
|
27
|
+
- [Sortable Column Headers](?path=/docs/guides-accessibility-table-patterns-sortable-column-headers--docs)
|
|
28
|
+
- [With Form Fields](?path=/docs/guides-accessibility-table-patterns-with-form-fields--docs)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {ExampleCodeBlock} from '@workday/canvas-kit-docs';
|
|
2
|
+
|
|
3
|
+
import {SelectableRows} from '../examples/Table/WithSelectableRows';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Selectable Rows
|
|
7
|
+
|
|
8
|
+
Using a `Checkbox` labeled "Select All" inside of a column header can be a confusing experience for
|
|
9
|
+
screen reader users. Screen readers will automatically announce the "Select All" label in the column
|
|
10
|
+
header each time users are reading any of the Check boxes in the first column. For instance, the
|
|
11
|
+
`Checkbox` in row 4 is definitely not going to select all of the rows. Here is what we did about it:
|
|
12
|
+
|
|
13
|
+
- We intentionally rendered row 1, column 1 as a standard `<td>` element so screen readers won't
|
|
14
|
+
automatically announce the "Select All" label while reading cells in column 1.
|
|
15
|
+
- Our research found that VoiceOver (MacOS v12.7, Safari v17.1) persistently announce "Select All"
|
|
16
|
+
despite using the `<td>` element because of the optional `<thead>` element in the table. We
|
|
17
|
+
omitted the optional `<thead>` and `<tbody>` elements from this example for that reason.
|
|
18
|
+
- We used Canvas Kit's `Tooltip` component to assign concise names to each Checkbox, describing
|
|
19
|
+
their purpose of selecting rows. This allows everyone to view the name of the checkboxes by
|
|
20
|
+
hovering the mouse or focusing with the keyboard.
|
|
21
|
+
- Since each checkbox is not uniquely labeled, we added `aria-describedby` to the checkbox,
|
|
22
|
+
referencing the unique `id` of the row header cell. This practice gives screen readers more
|
|
23
|
+
context about which value each checkbox is refering to.
|
|
24
|
+
- We rendered the cells in column 2 as the row headers for the table, enabling screen readers to
|
|
25
|
+
automatically announce the topping name even while reading down the Amounts in column 3.
|
|
26
|
+
|
|
27
|
+
<ExampleCodeBlock code={SelectableRows} />
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {ExampleCodeBlock} from '@workday/canvas-kit-docs';
|
|
2
|
+
|
|
3
|
+
import {SortableColumnHeaders} from '../examples/Table/WithSortableColumnHeaders';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Sortable Column Headers
|
|
7
|
+
|
|
8
|
+
The challenge in this example is to provide all of the necessary information about the interactive
|
|
9
|
+
column headers, the sort state of the column, and instructions about how the table will be sorted
|
|
10
|
+
without giving too much information to users while reading the data cells below.
|
|
11
|
+
|
|
12
|
+
- The `aria-sort` property has been added to each of the `<Table.Header>` components (`<th>` DOM
|
|
13
|
+
element) and updated to `ascending` or `descending` to reflect the current sort state. We
|
|
14
|
+
recommend validating whether this property is well supported for your screen reader and browser
|
|
15
|
+
combinations first.
|
|
16
|
+
- A `<TertiaryButton>` describing the column name is used inside of the `<Table.Header>` component.
|
|
17
|
+
- The `description` variant of the Canvas `Tooltip` component is applied to the button in the column
|
|
18
|
+
header and applied to the accessible description of the button with the `aria-description`
|
|
19
|
+
property. This is used to describe how the column will be sorted when pressed and screen readers
|
|
20
|
+
will only read this description while focusing on the column headers, not while reading the data
|
|
21
|
+
cells below.
|
|
22
|
+
|
|
23
|
+
<ExampleCodeBlock code={SortableColumnHeaders} />
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {ExampleCodeBlock, SymbolDoc} from '@workday/canvas-kit-docs';
|
|
2
|
+
import BaseHtmlTable from './examples/BaseHtmlTable';
|
|
2
3
|
import Basic from './examples/Basic';
|
|
3
4
|
import BasicWithHeading from './examples/BasicWithHeading';
|
|
4
5
|
import FixedColumn from './examples/FixedColumn';
|
|
5
6
|
import RightToLeft from './examples/RightToLeft';
|
|
6
|
-
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
# Canvas Kit Table
|
|
9
10
|
|
|
@@ -77,12 +78,16 @@ use the `BaseTable` component.
|
|
|
77
78
|
|
|
78
79
|
### Advanced
|
|
79
80
|
|
|
80
|
-
You can also find several advanced Table examples in our
|
|
81
|
+
You can also find several advanced Table examples in our
|
|
82
|
+
[Guides > Accessibility > Table Patterns](?path=/docs/guides-accessibility-table-patterns--docs)
|
|
83
|
+
section.
|
|
81
84
|
|
|
82
|
-
- [Expandable Rows](
|
|
83
|
-
- [
|
|
84
|
-
- [
|
|
85
|
-
- [
|
|
85
|
+
- [Expandable Rows](?path=/docs/guides-accessibility-table-patterns-expandable-rows--docs)
|
|
86
|
+
- [Nested Rows](?path=/docs/guides-accessibility-table-patterns-nested-rows--docs)
|
|
87
|
+
- [Selectable Rows](?path=/docs/guides-accessibility-table-patterns-selectable-rows--docs)
|
|
88
|
+
- [Filterable Column Headers](?path=/docs/guides-accessibility-table-patterns-filterable-column-headers--docs)
|
|
89
|
+
- [Sortable Column Headers](?path=/docs/guides-accessibility-table-patterns-sortable-column-headers--docs)
|
|
90
|
+
- [With Form Fields](?path=/docs/guides-accessibility-table-patterns-with-form-fields--docs)
|
|
86
91
|
|
|
87
92
|
### Custom Styles
|
|
88
93
|
|
|
@@ -90,6 +95,176 @@ Table and its subcomponents support custom styling via the `cs` prop. For more i
|
|
|
90
95
|
our
|
|
91
96
|
["How To Customize Styles"](https://workday.github.io/canvas-kit/?path=/docs/styling-guides-customizing-styles--docs).
|
|
92
97
|
|
|
98
|
+
## Accessibility
|
|
99
|
+
|
|
100
|
+
`Table` is a styled compound wrapper around native HTML table elements (`<table>`, `<caption>`,
|
|
101
|
+
`<thead>`, `<tbody>`, `<tfoot>`, `<tr>`, `<th>`, `<td>`). Assistive technology can announce column
|
|
102
|
+
and row headers as users move through cells **only when** the markup is a real data table with
|
|
103
|
+
correct headers and an accessible name. `Table` does **not** include a model, roving tabindex, or
|
|
104
|
+
built-in sort, filter, selection, or expand behavior.
|
|
105
|
+
|
|
106
|
+
Use `Table` for tabular data with a relationship between rows and columns—not for page layout.
|
|
107
|
+
Prefer **`Table`** (CSS Grid) unless the design needs a standard HTML table layout without Grid;
|
|
108
|
+
then use **`BaseTable`**. The same accessibility requirements apply to both. For interactive table
|
|
109
|
+
patterns, follow
|
|
110
|
+
[Guides > Accessibility > Table Patterns](?path=/docs/guides-accessibility-table-patterns--docs)
|
|
111
|
+
instead of inventing ARIA. See also
|
|
112
|
+
[Table Pattern | APG | WAI | W3C](https://www.w3.org/WAI/ARIA/apg/patterns/table/).
|
|
113
|
+
|
|
114
|
+
### Minimum accessible structure
|
|
115
|
+
|
|
116
|
+
The following matches the [Example with Caption](#example-with-caption) (`Basic` story): native
|
|
117
|
+
table markup with **`Table.Caption`** as the accessible name and **`scope="col"`** on column
|
|
118
|
+
headers. The [Basic Example](#basic-example) uses a visible **`Heading`** and **`aria-labelledby`**
|
|
119
|
+
instead of a caption; that is an equally valid naming pattern (see **Accessibility Requirements**).
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import {Table} from '@workday/canvas-kit-react/table';
|
|
123
|
+
|
|
124
|
+
<Table>
|
|
125
|
+
<Table.Caption>Coffee Drinks and Sizes</Table.Caption>
|
|
126
|
+
<Table.Head>
|
|
127
|
+
<Table.Row>
|
|
128
|
+
<Table.Header scope="col">Drink</Table.Header>
|
|
129
|
+
<Table.Header scope="col">Size</Table.Header>
|
|
130
|
+
</Table.Row>
|
|
131
|
+
</Table.Head>
|
|
132
|
+
<Table.Body>
|
|
133
|
+
<Table.Row>
|
|
134
|
+
<Table.Cell>Espresso</Table.Cell>
|
|
135
|
+
<Table.Cell>1 oz</Table.Cell>
|
|
136
|
+
</Table.Row>
|
|
137
|
+
<Table.Row>
|
|
138
|
+
<Table.Cell>Macchiato</Table.Cell>
|
|
139
|
+
<Table.Cell>2 oz Espresso</Table.Cell>
|
|
140
|
+
</Table.Row>
|
|
141
|
+
</Table.Body>
|
|
142
|
+
</Table>;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Put **`Table.Caption`** first when you use it (HTML requires `<caption>` as the first child of
|
|
146
|
+
`<table>`). Pair every column header with **`scope="col"`**. Do not render empty **`Table.Row`**
|
|
147
|
+
elements.
|
|
148
|
+
|
|
149
|
+
### Built-in Behaviors
|
|
150
|
+
|
|
151
|
+
Canvas Kit applies these automatically when you compose `Table` (or `BaseTable`) with its
|
|
152
|
+
subcomponents. **Do not duplicate them** in consuming code.
|
|
153
|
+
|
|
154
|
+
**Native table semantics** (_applied by `createComponent` element mapping_):
|
|
155
|
+
|
|
156
|
+
- `Table`: `<table>`
|
|
157
|
+
- `Table.Caption`: `<caption>`
|
|
158
|
+
- `Table.Head`: `<thead>`
|
|
159
|
+
- `Table.Body`: `<tbody>`
|
|
160
|
+
- `Table.Footer`: `<tfoot>`
|
|
161
|
+
- `Table.Row`: `<tr>` (`Table.Row` also sets CSS Grid column tracks from the count of valid child
|
|
162
|
+
cells; this is layout only)
|
|
163
|
+
- `Table.Header`: `<th>`
|
|
164
|
+
- `Table.Cell`: `<td>`
|
|
165
|
+
|
|
166
|
+
**Keyboard** (_standard `Table` behavior_):
|
|
167
|
+
|
|
168
|
+
`Table` uses native `<table>` keyboard behavior. Do not add custom key handlers or grid-widget keys
|
|
169
|
+
on a data table.
|
|
170
|
+
|
|
171
|
+
When **`Table`** overflows (the root stencil uses `overflow: auto`), add **`tabIndex={0}`** so
|
|
172
|
+
keyboard users can focus and scroll it, as in the [Fixed Column](#fixed-column) example. Omit
|
|
173
|
+
**`tabIndex`** when the table does not scroll.
|
|
174
|
+
|
|
175
|
+
**Screen reader expectations** (_when built-in behaviors are used as intended_):
|
|
176
|
+
|
|
177
|
+
- The table is announced as a table, with its accessible name from **`Table.Caption`** or
|
|
178
|
+
**`aria-labelledby`** / **`aria-label`** on **`Table`**
|
|
179
|
+
- Moving across a row announces the **column** header (`scope="col"`)
|
|
180
|
+
- Moving down a column announces the **row** header when body cells use **`Table.Header`** with
|
|
181
|
+
**`scope="row"`**
|
|
182
|
+
- Interactive controls inside cells are announced with their own name and role when focused
|
|
183
|
+
|
|
184
|
+
### Accessibility Requirements
|
|
185
|
+
|
|
186
|
+
Required in application code for an accessible `Table`. There is **no** `useTableModel`. Canvas Kit
|
|
187
|
+
does **not** set `scope`, an accessible name, `tabIndex`, or `id` / `headers` cell associations —
|
|
188
|
+
supply those in application code when the table below requires them. Rows marked _(conditional)_
|
|
189
|
+
apply only when the situation matches—otherwise omit.
|
|
190
|
+
|
|
191
|
+
**If no design spec is provided:** generate a simple data table with **`Table.Caption`**,
|
|
192
|
+
**`Table.Head`** / **`Table.Body`**, **`scope="col"`** on column headers, and **`Table.Cell`** for
|
|
193
|
+
body data. Omit **`Table.Footer`**, row headers, **`tabIndex`**, `id` / `headers` associations,
|
|
194
|
+
**`aria-sort`**, **`aria-expanded`**, **`aria-level`**, selection checkboxes, and filter/sort popups
|
|
195
|
+
unless the spec calls for those patterns.
|
|
196
|
+
|
|
197
|
+
**Heading instead of caption** _(conditional)_:
|
|
198
|
+
|
|
199
|
+
When the design uses a visible heading (or other text) instead of **`Table.Caption`**, give that
|
|
200
|
+
heading a unique `id` and set **`aria-labelledby`** on **`Table`**. Do not also render
|
|
201
|
+
**`Table.Caption`** unless the design needs both a caption and extra labelling. See
|
|
202
|
+
[Basic Example](#basic-example).
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<Heading as="h3" id={headingId}>
|
|
206
|
+
Pizza Toppings
|
|
207
|
+
</Heading>
|
|
208
|
+
<Table aria-labelledby={headingId}>{/* … */}</Table>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Interactive and advanced patterns** _(conditional)_:
|
|
212
|
+
|
|
213
|
+
Do not invent table ARIA. Copy the matching Canvas Kit guide when the spec includes that behavior:
|
|
214
|
+
|
|
215
|
+
- [Expandable Rows](?path=/docs/guides-accessibility-table-patterns-expandable-rows--docs)
|
|
216
|
+
- [Nested Rows](?path=/docs/guides-accessibility-table-patterns-nested-rows--docs)
|
|
217
|
+
- [Selectable Rows](?path=/docs/guides-accessibility-table-patterns-selectable-rows--docs)
|
|
218
|
+
- [Filterable Column Headers](?path=/docs/guides-accessibility-table-patterns-filterable-column-headers--docs)
|
|
219
|
+
- [Sortable Column Headers](?path=/docs/guides-accessibility-table-patterns-sortable-column-headers--docs)
|
|
220
|
+
- [With Form Fields](?path=/docs/guides-accessibility-table-patterns-with-form-fields--docs)
|
|
221
|
+
|
|
222
|
+
| Requirement | How to satisfy |
|
|
223
|
+
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
224
|
+
| Data table, not layout | Compose **`Table`** (or **`BaseTable`**) with **`Head`**, **`Body`**, **`Row`**, **`Header`**, and **`Cell`**. Do not use a table to position non-tabular UI. |
|
|
225
|
+
| Accessible name | **`Table.Caption`** with a descriptive title, **or** **`aria-labelledby`** on **`Table`** pointing at a visible heading `id` (see **Heading instead of caption**). Canvas Kit does not set **`aria-labelledby`**. |
|
|
226
|
+
| Column headers | Set **`scope="col"`** on every **`Table.Header`** in **`Table.Head`**. **`Table.Header`** renders `<th>` but does not set `scope`. Do not use **`Table.Cell`** (`<td>`) for column headers. |
|
|
227
|
+
| Row headers _(conditional)_ | When the first (or identifying) column names each row, set **`scope="row"`** on **`Table.Header`** in the body, as in [Fixed Column](#fixed-column). Do not rely on `<th>` alone. |
|
|
228
|
+
| Keyboard access to overflow _(conditional)_ | If the table scrolls, set **`tabIndex={0}`** on **`Table`**. Omit it when content does not overflow. |
|
|
229
|
+
| Footer _(conditional)_ | **`Table.Footer`** only when the design includes summary or footer rows. |
|
|
230
|
+
| Form controls in cells _(conditional)_ | Label every control (prefer **`FormField`**). See [With Form Fields](?path=/docs/guides-accessibility-table-patterns-with-form-fields--docs) and [Form Field](?path=/docs/components-inputs-form-field--docs). |
|
|
231
|
+
| Sort, filter, select, expand, or nest _(conditional)_ | Follow the matching [Table Patterns](?path=/docs/guides-accessibility-table-patterns--docs) guide. **Ask the developer** which pattern the spec requires before generating `aria-sort`, `aria-expanded`, `aria-level`, `aria-owns`, live regions, or selection checkboxes. |
|
|
232
|
+
|
|
233
|
+
**Summary for code generation:**
|
|
234
|
+
|
|
235
|
+
- **REQUIRED:** semantic `Table` composition, accessible name (caption or `aria-labelledby`),
|
|
236
|
+
`scope="col"` on column headers
|
|
237
|
+
- **CONDITIONAL:** heading + `aria-labelledby`, `scope="row"`, `tabIndex={0}` for overflow,
|
|
238
|
+
`Table.Footer`, form-field labelling in cells, advanced table patterns from the guides
|
|
239
|
+
|
|
240
|
+
### Anti-Patterns
|
|
241
|
+
|
|
242
|
+
Do **not** generate code that does the following (see **Accessibility Requirements** above for what
|
|
243
|
+
to supply instead):
|
|
244
|
+
|
|
245
|
+
- Manually set `role="table"`, `role="row"`, `role="columnheader"`, `role="rowheader"`, or
|
|
246
|
+
`role="cell"` on **`Table`** subcomponents — they already render native table elements
|
|
247
|
+
- Omit **`scope`** on **`Table.Header`**, or use **`Table.Cell`** for column or row headers
|
|
248
|
+
- Generate `id` / `headers` associations on cells for a simple data table — use **`scope`** instead
|
|
249
|
+
(see **Accessibility Requirements**)
|
|
250
|
+
- Leave the table unnamed (no **`Table.Caption`**, **`aria-labelledby`**, or **`aria-label`**), or
|
|
251
|
+
set **`aria-labelledby`** when **`Table.Caption`** already names the table
|
|
252
|
+
- Use a table for page layout
|
|
253
|
+
- Add **`tabIndex={0}`** on a table that does not scroll, or omit it on a horizontally/vertically
|
|
254
|
+
scrollable table
|
|
255
|
+
- Set **`aria-sort`**, **`aria-expanded`**, **`aria-level`**, **`aria-owns`**, or selection
|
|
256
|
+
checkboxes by default — those belong to specific
|
|
257
|
+
[Table Patterns](?path=/docs/guides-accessibility-table-patterns--docs) and need a matching design
|
|
258
|
+
spec
|
|
259
|
+
- Nest a `<table>` inside a cell to fake hierarchy, or add extra **`Table.Body`** elements to fake a
|
|
260
|
+
tree — see [Nested Rows](?path=/docs/guides-accessibility-table-patterns-nested-rows--docs)
|
|
261
|
+
- Put a "Select All" checkbox in a column header **`Table.Header`** without following
|
|
262
|
+
[Selectable Rows](?path=/docs/guides-accessibility-table-patterns-selectable-rows--docs)
|
|
263
|
+
- Place unlabeled inputs in cells, or rely on the column header alone without checking
|
|
264
|
+
[With Form Fields](?path=/docs/guides-accessibility-table-patterns-with-form-fields--docs)
|
|
265
|
+
- Generate **`role="grid"`** / grid-widget keyboard behavior on **`Table`** — CSS Grid on
|
|
266
|
+
**`Table.Row`** is visual layout, not an ARIA grid
|
|
267
|
+
|
|
93
268
|
## Component API
|
|
94
269
|
|
|
95
270
|
<SymbolDoc name="Table" fileName="/react/" />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workday/canvas-kit-docs",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.15",
|
|
4
4
|
"description": "Documentation components of Canvas Kit components",
|
|
5
5
|
"author": "Workday, Inc. (https://www.workday.com)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
"@stackblitz/sdk": "^1.11.0",
|
|
48
48
|
"@storybook/csf": "0.0.1",
|
|
49
49
|
"@workday/canvas-expressive-icons-web": "1.0.2",
|
|
50
|
-
"@workday/canvas-kit-labs-react": "^16.0.
|
|
51
|
-
"@workday/canvas-kit-preview-react": "^16.0.
|
|
52
|
-
"@workday/canvas-kit-react": "^16.0.
|
|
53
|
-
"@workday/canvas-kit-styling": "^16.0.
|
|
50
|
+
"@workday/canvas-kit-labs-react": "^16.0.15",
|
|
51
|
+
"@workday/canvas-kit-preview-react": "^16.0.15",
|
|
52
|
+
"@workday/canvas-kit-react": "^16.0.15",
|
|
53
|
+
"@workday/canvas-kit-styling": "^16.0.15",
|
|
54
54
|
"@workday/canvas-system-icons-web": "^5.0.3",
|
|
55
55
|
"@workday/canvas-tokens-web": "^4.4.0",
|
|
56
56
|
"markdown-to-jsx": "^7.2.0",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"mkdirp": "^1.0.3",
|
|
64
64
|
"typescript": "5.0"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "f4ae86b34eec3edc8e580c9384d4919da5ae2a76"
|
|
67
67
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import {ExampleCodeBlock} from '@workday/canvas-kit-docs';
|
|
2
|
-
|
|
3
|
-
import InlinePortalPopup from './examples/Popups/InlinePortalPopup';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
## Inline portal with `PopupStack`
|
|
7
|
-
|
|
8
|
-
This example builds on the patterns described in
|
|
9
|
-
[Guides > Accessibility > Inline Popups](?path=/docs/guides-accessibility-inline-popups--docs). It
|
|
10
|
-
does **not** use a focus trap. For modal dialogs with overlay and focus trap, use the
|
|
11
|
-
[**Modal**](?path=/docs/components-popups-modal--docs) component instead.
|
|
12
|
-
|
|
13
|
-
Keep using a portal (default stacking and positioning) but mount the portal **into a sentinel
|
|
14
|
-
element** placed right after the trigger. Call `PopupStack.pushStackContext(sentinelElement)` while
|
|
15
|
-
the popup is open so new stack items append to that sentinel instead of `body`. **Tradeoff:** You
|
|
16
|
-
still get **ancestor overflow** clipping—the portaled content is a descendant of the sentinel, not
|
|
17
|
-
`document.body`. You must also handle **`PopupStack` context** (push/pop on open/close), which is
|
|
18
|
-
more moving parts than `portal={false}` alone. Use **`useInitialFocus`** so opening the popup is
|
|
19
|
-
announced when focus enters the dialog.
|
|
20
|
-
|
|
21
|
-
<ExampleCodeBlock code={InlinePortalPopup} />
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import {ExampleCodeBlock} from '@workday/canvas-kit-docs';
|
|
2
|
-
|
|
3
|
-
import SelectableRows from './examples/Table/WithSelectableRows';
|
|
4
|
-
import ExpandableRows from './examples/Table/WithExpandableRows';
|
|
5
|
-
import SortableColumnHeaders from './examples/Table/WithSortableColumnHeaders';
|
|
6
|
-
import FilterableColumnHeaders from './examples/Table/WithColumnHeaderFilters';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Advanced Table Examples
|
|
10
|
-
|
|
11
|
-
Tables should only be used to organize data that has a clear relationship between rows and columns,
|
|
12
|
-
like a calendar or a schedule. Never use a table just for page layout.
|
|
13
|
-
|
|
14
|
-
When you use the proper HTML table markup, a screen reader can help a user navigate the table. It
|
|
15
|
-
will automatically read the column and row headers as they move through the data, so they always
|
|
16
|
-
know what information they're looking at.
|
|
17
|
-
|
|
18
|
-
- All tables should have a clear header and a descriptive title.
|
|
19
|
-
- Keep your tables simple. If a table is too complex, it might be better to break it up into several
|
|
20
|
-
smaller tables or use a different format.
|
|
21
|
-
|
|
22
|
-
Out of the box, `Table` is a lightweight compound component with a high degree of flexibility, but
|
|
23
|
-
not much functionality outside of providing a basic table layout. This flexibility lets developers
|
|
24
|
-
implement common features, such as selecting rows and sorting columns, on top of `Table` to meet
|
|
25
|
-
their specific application needs.
|
|
26
|
-
|
|
27
|
-
The Workday Accessibility Team has researched and developed the following examples below to
|
|
28
|
-
demonstrate how to build these accessible table patterns. We've listed the specific considerations
|
|
29
|
-
and decisions we've made for each of the examples.
|
|
30
|
-
|
|
31
|
-
### Expandable Rows
|
|
32
|
-
|
|
33
|
-
Expandable Rows combines the likes of an accordion with tabular data tables. Column 1 renders icon
|
|
34
|
-
buttons with 2 states, a collapsed and expanded state. A new row that spans the entire width of the
|
|
35
|
-
table is added to the table just after the expanded row.
|
|
36
|
-
|
|
37
|
-
- The `aria-expanded` property is added to the chevron button to communicate this state to screen
|
|
38
|
-
reader users.
|
|
39
|
-
- A Canvas accessible `Tooltip` component is used to assign names to each icon button based on the
|
|
40
|
-
most useful value in the row. In this example, we combined the car make (in column 1) and model
|
|
41
|
-
(in column 2) together. This allows everyone to view the name of the icon buttons by hovering the
|
|
42
|
-
mouse or focusing with the keyboard.
|
|
43
|
-
- The expanded row uses `colspan` to span the entire width of the table and support screen readers.
|
|
44
|
-
This space provides flexibility to show headings, lists, and other structured content for the
|
|
45
|
-
table row above.
|
|
46
|
-
- There is no explicit relationship between a row of cells and the spanned content below it. The
|
|
47
|
-
spanned content is assumed to belong to the row of cells above it, based on established accordion
|
|
48
|
-
patterns and logical reading order of content rendered to the screen.
|
|
49
|
-
- Outlining hierarchy with additional nested rows in the table is not supported for screen readers
|
|
50
|
-
in this example.
|
|
51
|
-
|
|
52
|
-
<ExampleCodeBlock code={ExpandableRows} />
|
|
53
|
-
|
|
54
|
-
### Selectable Rows
|
|
55
|
-
|
|
56
|
-
Using a `Checkbox` labeled "Select All" inside of a column header can be a confusing experience for
|
|
57
|
-
screen reader users. Screen readers will automatically announce the "Select All" label in the column
|
|
58
|
-
header each time users are reading any of the Check boxes in the first column. For instance, the
|
|
59
|
-
`Checkbox` in row 4 is definitely not going to select all of the rows. Here is what we did about it:
|
|
60
|
-
|
|
61
|
-
- We intentionally rendered row 1, column 1 as a standard `<td>` element so screen readers won't
|
|
62
|
-
automatically announce the "Select All" label while reading cells in column 1.
|
|
63
|
-
- Our research found that VoiceOver (MacOS v12.7, Safari v17.1) persistently announce "Select All"
|
|
64
|
-
despite using the `<td>` element because of the optional `<thead>` element in the table. We
|
|
65
|
-
omitted the optional `<thead>` and `<tbody>` elements from this example for that reason.
|
|
66
|
-
- We used Canvas' accessible `Tooltip` component to assign names to each Checkbox based on the most
|
|
67
|
-
useful value in the row, the topping name. This allows everyone to view the name of the checkboxes
|
|
68
|
-
by hovering the mouse or focusing with the keyboard.
|
|
69
|
-
- We rendered the cells in column 2 as the row headers for the table, enabling screen readers to
|
|
70
|
-
automatically announce the topping name even while reading down the Amounts in column 3. When we
|
|
71
|
-
rendered column 1 as row headers, then reading down column 2 (Topping Name) sounded redundant
|
|
72
|
-
because the `Checkbox` names in column 1 are identical to the Topping Name in column 2.
|
|
73
|
-
|
|
74
|
-
<ExampleCodeBlock code={SelectableRows} />
|
|
75
|
-
|
|
76
|
-
### Filterable Column Headers
|
|
77
|
-
|
|
78
|
-
In this example, we demonstrate using the `Popup` component in each column header allowing users to
|
|
79
|
-
search and filter the data on the table. The `Popup` component relies on React Portals to render the
|
|
80
|
-
popup elements at the bottom of the browser's DOM presenting 2 key challenges for accessibility:
|
|
81
|
-
|
|
82
|
-
1. Keyboard focus order of the elements in the popup,
|
|
83
|
-
2. Screen readers' reading order of the content rendered in the browser.
|
|
84
|
-
|
|
85
|
-
Here's what we did about it:
|
|
86
|
-
|
|
87
|
-
- Canvas Kit includes a `usePopupModel` hook, with quite a few additional hooks developers can add
|
|
88
|
-
to their models. In particular, the `useFocusRedirect` hook manages keyboard focus between the
|
|
89
|
-
`<Popup.Target>` button and the popup content.
|
|
90
|
-
- The `useInitialFocus` hook allows developers to specify which element receives keyboard focus when
|
|
91
|
-
the popup appears. In this example, we auto-focused the search input field.
|
|
92
|
-
- To address the reading order of content, we set the `aria-owns` property onto the parent
|
|
93
|
-
`<Table.Header>` component (`<th>` DOM element) with 2 unique `id` values. The first `id` refers
|
|
94
|
-
to the `<Popup.Target>` button and the second refers to the `<Popup.Card>` container element. This
|
|
95
|
-
manually reassigns the column header's `<Popup.Target>` button and the `Popup` contents as
|
|
96
|
-
siblings in the browser's accessibility tree hierarchy. Screen readers **should** read the column
|
|
97
|
-
header buttons and the popup content in sequential order even though they are not siblings in the
|
|
98
|
-
DOM.
|
|
99
|
-
- The `type='description'` variant of the Canvas `Tooltip` is used to communicate the filtered state
|
|
100
|
-
of the column header, and assigned to the accessible description of the column header
|
|
101
|
-
`<TertiaryButton>` component.
|
|
102
|
-
- The Canvas `AriaLiveRegion` component is used to render the "X of Y items" status inside the table
|
|
103
|
-
caption. This enables screen readers to automatically describe the filter state changes of the
|
|
104
|
-
table content to users in real time. We recommend validating whether this use of a live region is
|
|
105
|
-
well supported for your screen reader and browser combinations first.
|
|
106
|
-
|
|
107
|
-
<ExampleCodeBlock code={FilterableColumnHeaders} />
|
|
108
|
-
|
|
109
|
-
### Sortable Column Headers
|
|
110
|
-
|
|
111
|
-
The challenge in this example is to provide all of the necessary information about the interactive
|
|
112
|
-
column headers, the sort state of the column, and instructions about how the table will be sorted
|
|
113
|
-
without giving too much information to users while reading the data cells below.
|
|
114
|
-
|
|
115
|
-
- The `aria-sort` property has been added to each of the `<Table.Header>` components (`<th>` DOM
|
|
116
|
-
element) and updated to `ascending` or `descending` to reflect the current sort state. We
|
|
117
|
-
recommend validating whether this property is well supported for your screen reader and browser
|
|
118
|
-
combinations first.
|
|
119
|
-
- A `<TertiaryButton>` describing the column name is used inside of the `<Table.Header>` component.
|
|
120
|
-
- The `description` variant of the Canvas `Tooltip` component is applied to the button in the column
|
|
121
|
-
header and applied to the accessible description of the button with the `aria-description`
|
|
122
|
-
property. This is used to describe how the column will be sorted when pressed and screen readers
|
|
123
|
-
will only read this description while focusing on the column headers, not while reading the data
|
|
124
|
-
cells below.
|
|
125
|
-
|
|
126
|
-
<ExampleCodeBlock code={SortableColumnHeaders} />
|