Skip to content

Commit

Permalink
Table a11y fixes (#4668)
Browse files Browse the repository at this point in the history
* add checkboxLabel and update header focus indicator

* add reverse color for icon headers

* update icon header labelText to pass content to aria-label instead of svg title

* update focus ring style for interactive TableCard

* update stories and API documentation
  • Loading branch information
mcwinter07 authored May 15, 2024
1 parent b32bc36 commit 2772ecd
Show file tree
Hide file tree
Showing 7 changed files with 1,154 additions and 194 deletions.
11 changes: 11 additions & 0 deletions .changeset/large-ducks-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@kaizen/components": minor
---

Accessibility uplift of Table component

- Add default focus ring widths and colors to interactive variants of the TableHeaderRowCell and interactive table cards
- Add a checkboxLabel prop as a mean to resolving the unlinked checkbox label in the checkable variant of the TableHeaderRowCell
- Update the TableHeaderRowCell to pass labelText in the an aria-label for the icon variant
- Update the documentation with some clearer guidance on the APIs and sub components
- Add guidance on usage of tooltip on non-interactive headers
76 changes: 68 additions & 8 deletions packages/components/src/Table/Table.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ $row-height-data-variant: 48px;
&:focus {
text-decoration: none;
}

&.headerRowCellButtonReversed {
color: $color-white;
}
}

// Special Table-only button reset
Expand All @@ -35,6 +31,7 @@ $row-height-data-variant: 48px;
margin: 0;
padding: 0;
transition: none; // override Murmur global styles :(
outline: none;
}

.container {
Expand Down Expand Up @@ -80,19 +77,55 @@ $row-height-data-variant: 48px;
.headerRowCell .headerRowCellTooltip {
display: flex;
align-items: stretch;
max-width: 100%;
}

// overflow has to be set at this level as well as on the heading for some reason ¯\_(ツ)_/¯
.headerRowCell.headerRowCellNoWrap .headerRowCellTooltip {
overflow: hidden;
.headerRowCell.headerRowCellNoWrap .headerRowCellContent {
max-width: 100%;
}

.headerRowCellButton {
@include button-reset;
@include anchor-reset;

display: flex;
align-items: stretch;
width: 100%;
// Ensures that the 100% doesn't go outside of the `headerRowCell` width
box-sizing: border-box;

&:focus-visible {
outline: none;
position: relative;

&::after {
// This offset provide enough gap on reverse for contrast ratios
$focus-ring-offset: calc((#{$border-focus-ring-border-width} * 2) + 1px);

top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100% + #{$focus-ring-offset});
height: calc(100% + #{$focus-ring-offset});
content: "";
position: absolute;
background: transparent;
border-color: $color-blue-500;
border-width: $border-focus-ring-border-width;
border-style: $border-focus-ring-border-style;
border-radius: $border-focus-ring-border-radius;
}
}
}

.headerRowCellButtonReversed {
color: $color-white;

&:focus-visible::after {
border-color: $color-blue-100;
}
}

.headerRowCellButton,
.headerRowCellNoButton {
display: flex;
align-items: stretch;
Expand Down Expand Up @@ -120,6 +153,10 @@ $row-height-data-variant: 48px;
.headerRowCellActive & {
color: $color-purple-800;
}

.headerRowCellButtonReversed & {
color: $color-white;
}
}

.card {
Expand Down Expand Up @@ -154,6 +191,29 @@ $row-height-data-variant: 48px;
will-change: box-shadow, border-color, margin, padding, width;
}

&:focus-visible {
outline: none;
position: relative;

&::after {
// This offset provide enough gap on on reverse for contrast ratios
$focus-ring-offset: calc(#{$border-focus-ring-border-width} + 2px);

top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100% + #{$focus-ring-offset});
height: calc(100% + #{$focus-ring-offset});
content: "";
position: absolute;
background: transparent;
border-color: $color-blue-500;
border-width: $border-focus-ring-border-width;
border-style: $border-focus-ring-border-style;
border-radius: inherit;
}
}

&.well {
margin-top: $spacing-sm;
}
Expand Down
38 changes: 27 additions & 11 deletions packages/components/src/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import styles from "./Table.module.scss"

export type TableContainerProps = {
children?: React.ReactNode
/** @default "compact" */
variant?: "compact" | "default" | "data"
}
/**
Expand Down Expand Up @@ -56,6 +57,14 @@ export type TableHeaderRowProps = {
const ratioToPercent = (width?: number): string | number | undefined =>
width != null ? `${width * 100}%` : width

export type TableHeaderRowCellCheckboxProps = {
checkable?: boolean
checkedStatus?: CheckedStatus
/** This will be passed into the aria-label for the checkbox to provide context to the user */
checkboxLabel?: string
onCheck?: (event: React.ChangeEvent<HTMLInputElement>) => any
}

/**
* @param width value between 1 and 0, to be calculated as a percentage
* @param flex CSS flex shorthand as a string. Be sure to specify the flex grow,
Expand All @@ -71,9 +80,6 @@ export type TableHeaderRowCellProps = {
flex?: string
href?: string
icon?: ReactElement
checkable?: boolean
checkedStatus?: CheckedStatus
onCheck?: (event: React.ChangeEvent<HTMLInputElement>) => any
reversed?: boolean
/**
* Shows an up or down arrow, to show that the column is sorted.
Expand All @@ -82,13 +88,19 @@ export type TableHeaderRowCellProps = {
wrapping?: "nowrap" | "wrap"
align?: "start" | "center" | "end"
tooltipInfo?: string
/** If set, this will hide the tooltip exclamation icon. Useful in situations where
the table header does not have enough space. This should be done with caution as tooltips
should have a visual indicator to users */
isTooltipIconHidden?: boolean
/**
* Specify where the tooltip should be rendered.
*/
tooltipPortalSelector?: string | undefined
/** If set, this will show the arrow in the direction provided
when the header cell is hovered over. */
sortingArrowsOnHover?: "ascending" | "descending" | undefined
} & OverrideClassName<HTMLAttributes<HTMLElement>>
} & TableHeaderRowCellCheckboxProps &
OverrideClassName<HTMLAttributes<HTMLElement>>

export const TableHeaderRowCell = ({
labelText,
Expand All @@ -99,6 +111,7 @@ export const TableHeaderRowCell = ({
icon,
checkable,
checkedStatus,
checkboxLabel,
onCheck,
reversed,
sorting: sortingRaw,
Expand All @@ -111,13 +124,8 @@ export const TableHeaderRowCell = ({
wrapping = "nowrap",
align = "start",
tooltipInfo,
// If set, this will hide the tooltip exclamation icon. Useful in situations where
// the table header does not have enough space. However, we should always show a
// tooltip icon as the default based on design system tooltip guidelines.
isTooltipIconHidden = false,
tooltipPortalSelector,
// If set, this will show the arrow in the direction provided
// when the header cell is hovered over.
sortingArrowsOnHover,
classNameOverride,
// There aren't any other props in the type definition, so I'm unsure why we
Expand All @@ -143,12 +151,20 @@ export const TableHeaderRowCell = ({
<div className={styles.headerRowCellLabelAndIcons}>
{icon && (
<span className={styles.headerRowCellIcon}>
{cloneElement(icon, { title: labelText, role: "img" })}
{cloneElement(icon, {
title: labelText,
["aria-label"]: labelText, // title is unreliable so this is a sensible fallback for tables with icons as headers without aria-labels
role: "img",
})}
</span>
)}
{checkable && (
<div className={styles.headerRowCellCheckbox}>
<Checkbox checkedStatus={checkedStatus} onCheck={onCheck} />
<Checkbox
checkedStatus={checkedStatus}
onCheck={onCheck}
aria-label={checkboxLabel}
/>
</div>
)}
{tooltipInfo != null && !isTooltipIconHidden ? (
Expand Down
56 changes: 43 additions & 13 deletions packages/components/src/Table/_docs/Table.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,61 @@ A table displays rows of data, including all data in a set, making it efficient

<Canvas of={TableStories.Playground} />

## API
## TableContainer API

### Data
### Variant

Controls the spacing in each cell within the table. Options available are `compact`, `default` and `data`.

#### Compact
<Canvas of={TableStories.Data} />

### Compact
<Canvas of={TableStories.Compact} />
#### Default
<Canvas of={TableStories.Default} />

### LinkVariant
<Canvas of={TableStories.LinkVariant} />
#### Data
<Canvas of={TableStories.Data} />

## TableHeaderRowCell API

### Sorting
<Canvas of={TableStories.Sorting} />

### Checkbox

To ensure there appropriate context for users of assistive technologies, when using a `checkable` `TableHeaderRowCell`, we advise in the `checkboxLabel`. This will be passed to the a checkbox aria-label and be announce to screen reader users when focused.

### CheckboxVariant
<Canvas of={TableStories.CheckboxVariant} />

### IconVariant
<Canvas of={TableStories.IconVariant} />
### Icon

### Expandable
<Canvas of={TableStories.Expandable} />
When using providing `icon` to `TableHeaderRowCell` the `labelText` will be passed to the `aria-label` of the SVG.

<Canvas of={TableStories.IconVariant} />

### HeaderAlignmentAndWrapping
### Align and wrapping
<Canvas of={TableStories.HeaderAlignmentAndWrapping} />

### Tooltip
### Tooltips

While Tooltip content can be passed to any table header via the `tooltipInfo` prop, it is strong advised to avoid this if the header is not an interactive element as the tooltip content will be unreadable to keyboard users or those that use assistive technologies.

<Canvas of={TableStories.Tooltip} />

You can read more about the Tooltip component and accessibility limitation [here](https://cultureamp.design/?path=/docs/components-tooltip--docs#screen-reader-accessibility).

### Reversed
<Canvas of={TableStories.Reversed} />

## TableCard API

### Link
<Canvas of={TableStories.LinkVariant} />

### Expandable

The `expandable` prop introduces known accessibility issues with nesting interactive elements as children of a `button` or `anchor` tag. We recommend avoiding this pattern if possible, or creating a tier 3 component that adheres to correct WCAG hierarchy.

<Canvas of={TableStories.Expandable} />


Loading

0 comments on commit 2772ecd

Please sign in to comment.