fix(a11y): WCAG 2.1 AA fixes (#2219)

This commit is contained in:
Philip Okugbe
2026-05-20 16:47:25 +01:00
committed by GitHub
parent 1c166c4736
commit 92c0e36e46
119 changed files with 1064 additions and 194 deletions
@@ -0,0 +1,29 @@
/*
* Focus styling for list-style tables (recent changes, favorites, all
* spaces, groups, verified pages, shares).
*
* Per WAI-ARIA Authoring Practices and Adrian Roselli's guidance on table
* accessibility (https://adrianroselli.com/2020/02/block-links-cards-clickable-regions-etc.html),
* data tables should not be made fully clickable. Only the title cell is the
* link, and that link is what receives Tab focus.
*
* - `.row` adds a subtle background tint when the row contains the focused
* element, so keyboard users can see which row they're inspecting.
* - `.link` adds a visible :focus-visible outline on the title link itself.
*
* No stretched-link pseudo here on purpose: absolutely-positioned pseudos
* inside table cells cause column reflow on focus in Chromium.
*/
.row:focus-within {
background-color: light-dark(
var(--mantine-color-gray-1),
var(--mantine-color-dark-6)
);
}
.link:focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: 2px;
border-radius: var(--mantine-radius-sm);
}
@@ -16,14 +16,18 @@ interface CustomAvatarProps {
mt?: string | number;
}
// `color.shade` pairs whose filled background meets WCAG AA (4.5:1) against
// white text. Avoids lime/yellow/green/orange — even their dark shades have
// weak white-text contrast.
// `color.shade` pairs whose contrast meets WCAG AA (4.5:1) in BOTH variants:
// - filled: white text on the shade as bg
// - light: shade as text on the color's light-bg (10% color.6 over white)
// Avoids lime/yellow/green/orange — even their dark shades have weak
// contrast. grape and indigo were bumped from .7 to darker shades because
// the original picks failed: grape.7 was 4.02/3.61 (both fail) and
// indigo.7 was 4.98/4.39 (light fails by a hair).
const SAFE_INITIALS_COLORS: MantineColor[] = [
"blue.8",
"cyan.9",
"grape.7",
"indigo.7",
"grape.9",
"indigo.8",
"pink.8",
"red.8",
"violet.7",
@@ -41,7 +41,7 @@ export function DestinationPickerModal({
<Modal.Content>
<Modal.Header py={0}>
<Modal.Title fw={500}>{title}</Modal.Title>
<Modal.CloseButton />
<Modal.CloseButton aria-label={t("Close")} />
</Modal.Header>
<Modal.Body>
<DestinationPicker
@@ -14,7 +14,14 @@ export interface SidebarToggleProps extends BoxProps, ElementProps<"button"> {
const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
({ opened, size = "sm", ...others }, ref) => {
return (
<ActionIcon size={size} {...others} variant="subtle" color="gray" ref={ref}>
<ActionIcon
size={size}
aria-expanded={opened}
{...others}
variant="subtle"
color="gray"
ref={ref}
>
{opened ? (
<IconLayoutSidebarRightExpand />
) : (
@@ -0,0 +1,27 @@
.skipLink {
position: absolute;
top: 8px;
left: 8px;
z-index: 9999;
padding: 8px 16px;
background: var(--mantine-color-body);
color: var(--mantine-color-text);
border: 2px solid var(--mantine-color-blue-6);
border-radius: 4px;
text-decoration: none;
font-weight: 500;
font-size: var(--mantine-font-size-sm);
transform: translateY(-200%);
transition: transform 0.15s ease-out;
}
.skipLink:focus {
transform: translateY(0);
outline: none;
}
@media print {
.skipLink {
display: none !important;
}
}
@@ -0,0 +1,13 @@
import { useTranslation } from "react-i18next";
import classes from "./skip-to-main.module.css";
export const MAIN_CONTENT_ID = "main-content";
export function SkipToMain() {
const { t } = useTranslation();
return (
<a href={`#${MAIN_CONTENT_ID}`} className={classes.skipLink}>
{t("Skip to main content")}
</a>
);
}