Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/a11y-table-row-status-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astryxdesign/core': patch
---

[fix] Table useTableRowStatus: the status gutter's column header now carries a visually hidden localized name ("Row status", key `@astryx.table.rowStatus.columnHeader`) instead of an empty `<th>` (axe empty-table-header, WCAG 1.3.1 best practice). The gutter stays visually blank.
@AKnassa
10 changes: 0 additions & 10 deletions .github/a11y-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -1008,16 +1008,6 @@
"impact": "moderate",
"helpUrl": "https://dequeuniversity.com/rules/axe/4.12/landmark-unique?application=playwright"
},
{
"key": "TableRowStatus::Default::empty-table-header",
"impact": "minor",
"helpUrl": "https://dequeuniversity.com/rules/axe/4.12/empty-table-header?application=playwright"
},
{
"key": "TableRowStatus::Raw Colors::empty-table-header",
"impact": "minor",
"helpUrl": "https://dequeuniversity.com/rules/axe/4.12/empty-table-header?application=playwright"
},
{
"key": "TableTree::Default::aria-conditional-attr",
"impact": "serious",
Expand Down
4 changes: 4 additions & 0 deletions packages/core/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,10 @@
"defaultMessage": "Apply",
"description": "Primary button label inside a table's filter panel/popover; commits pending filter values. Imperative verb; pairs with `Reset`."
},
"@astryx.table.rowStatus.columnHeader": {
"defaultMessage": "Row status",
"description": "Screen-reader-only column header for the narrow status-indicator gutter a Table gains from useTableRowStatus. Sighted users see a blank gutter; assistive tech announces this as the column name."
},
"@astryx.table.selection.selectAllRows": {
"defaultMessage": "Select all rows",
"description": "Aria label for the \"select all rows\" checkbox in a Table header."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,18 @@ function Harness({
}

describe('useTableRowStatus', () => {
it('prepends a narrow status column with an empty header', () => {
it('names the status column header for assistive technology', () => {
render(<Harness />);
const headers = screen.getAllByRole('columnheader');
// Status column is first; its header is empty.
expect(headers[0]).toHaveAttribute('data-column-key', '__rowStatus');
expect(headers[0].textContent).toBe('');
// The gutter looks blank but its th carries a visually hidden name.
const header = screen.getByRole('columnheader', {name: 'Row status'});
expect(header).toHaveAttribute('data-column-key', '__rowStatus');
// Status column is first.
expect(screen.getAllByRole('columnheader')[0]).toBe(header);
// The name must come from the clipped VisuallyHidden span — bare th text
// would be a visible header on what should stay a blank gutter.
const hiddenText = within(header).getByText('Row status');
expect(hiddenText.tagName).toBe('SPAN');
expect(hiddenText.className).not.toBe('');
});

it('renders a labeled dot for rows with a status', () => {
Expand Down Expand Up @@ -131,8 +137,8 @@ describe('useTableRowStatus', () => {

it('renders the status header with empty data and no indicators', () => {
render(<Harness rows={[]} />);
const headers = screen.getAllByRole('columnheader');
expect(headers[0]).toHaveAttribute('data-column-key', '__rowStatus');
const header = screen.getByRole('columnheader', {name: 'Row status'});
expect(header).toHaveAttribute('data-column-key', '__rowStatus');
expect(screen.queryByRole('img')).not.toBeInTheDocument();
});
});
15 changes: 12 additions & 3 deletions packages/core/src/Table/plugins/rowStatus/useTableRowStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

/**
* @file useTableRowStatus.tsx
* @input React, StyleX, Icon, Table types
* @input React, StyleX, Icon, i18n, VisuallyHidden, Table types
* @output Exports useTableRowStatus hook + config type
* @position Row-status plugin; consumed by Table via plugins prop
*
Expand All @@ -16,6 +16,8 @@ import {useMemo} from 'react';
import * as stylex from '@stylexjs/stylex';
import {Icon, type IconColor, type IconName} from '../../../Icon';
import {Tooltip} from '../../../Tooltip';
import {useTranslator} from '../../../i18n';
import {VisuallyHidden} from '../../../VisuallyHidden';
import type {TableColumn, TablePlugin} from '../../types';

/**
Expand Down Expand Up @@ -142,14 +144,21 @@ const styles = stylex.create({
export function useTableRowStatus<T extends Record<string, unknown>>(
config: UseTableRowStatusConfig<T>,
): TablePlugin<T> {
const t = useTranslator();
const {getStatus} = config;

return useMemo(
(): TablePlugin<T> => ({
transformColumns(columns) {
const statusColumn: TableColumn<T> = {
key: '__rowStatus',
header: '',
// The gutter stays visually blank, but the th needs a discernible
// name for assistive technology (axe: empty-table-header).
header: (
<VisuallyHidden>
{t('@astryx.table.rowStatus.columnHeader')}
</VisuallyHidden>
),
width: STATUS_COLUMN_WIDTH,
resizable: false,
renderCell: (item: T) => {
Expand Down Expand Up @@ -184,6 +193,6 @@ export function useTableRowStatus<T extends Record<string, unknown>>(
return [statusColumn, ...columns];
},
}),
[getStatus],
[getStatus, t],
);
}
2 changes: 1 addition & 1 deletion packages/core/src/Table/useTableRowStatus.doc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const docs = {
subComponentOf: 'Table',
displayName: 'useTableRowStatus',
description:
'Hook that returns a TablePlugin which prepends a narrow column signaling per-row status: a colored status dot by default, or an icon when provided (shape + color is more accessible than color alone). getStatus maps a row to a semantic color (mapped to a theme token) or raw CSS color, an optional icon, and a required accessible label (shown in a tooltip on hover and announced to assistive technology, so status is never color-only); return null for no indicator. Memoize getStatus with useCallback for a stable plugin identity.',
'Hook that returns a TablePlugin which prepends a narrow column signaling per-row status: a colored status dot by default, or an icon when provided (shape + color is more accessible than color alone). getStatus maps a row to a semantic color (mapped to a theme token) or raw CSS color, an optional icon, and a required accessible label (shown in a tooltip on hover and announced to assistive technology, so status is never color-only); return null for no indicator. The column header is visually blank but carries a screen-reader-only localized name ("Row status", i18n key @astryx.table.rowStatus.columnHeader). Memoize getStatus with useCallback for a stable plugin identity.',
props: [
{
name: 'getStatus',
Expand Down
Loading