Skip to content

Commit

Permalink
Improve accessibility of navigation components (#2297)
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-baer authored Nov 14, 2023
1 parent 6c536a2 commit 2f4fbaf
Show file tree
Hide file tree
Showing 21 changed files with 80 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/poor-rice-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sumup/circuit-ui': patch
---

Fixed the HTML semantics in the TopNavigation and SideNavigation components.
5 changes: 5 additions & 0 deletions .changeset/tasty-guests-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sumup/circuit-ui': patch
---

Properly hid presentational icons from the accessibility tree.
5 changes: 5 additions & 0 deletions .changeset/weak-toes-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sumup/circuit-ui': patch
---

Restored the focus styles of the utility links and profile menu in the TopNavigation.
2 changes: 1 addition & 1 deletion packages/circuit-ui/components/Field/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export const FieldValidationHint = ({
<div className={classNames} {...props}>
{Icon && (
<div className={classes['validation-hint-icon']}>
<Icon role="presentation" size="16" />
<Icon aria-hidden="true" size="16" />
</div>
)}
{validationHint}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const Details = (
marginRight: 'var(--cui-spacings-bit)',
color: 'var(--cui-fg-success)',
}}
role="presentation"
aria-hidden="true"
/>
<Body
size="two"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const Details = ({ item }: { item: Item }) => (
marginRight: 'var(--cui-spacings-bit)',
color: 'var(--cui-fg-success)',
}}
role="presentation"
aria-hidden="true"
/>
) : (
<Alert
Expand All @@ -80,7 +80,7 @@ const Details = ({ item }: { item: Item }) => (
marginRight: 'var(--cui-spacings-bit)',
color: 'var(--cui-fg-danger)',
}}
role="presentation"
aria-hidden="true"
/>
)}
<Body
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export const NotificationInline = forwardRef<
>
<div className={clsx(classes.wrapper, classes[variant])}>
<div className={classes.icon}>
<Icon role="presentation" />
<Icon aria-hidden="true" />
</div>
<span className={utilityClasses.hideVisually}>{iconLabel}</span>
<div className={classes.content}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function NotificationToast({
>
<div className={classes.wrapper}>
<div className={classes.icon}>
<Icon role="presentation" />
<Icon aria-hidden="true" />
</div>
<span className={utilityClasses.hideVisually}>{iconLabel}</span>
<div className={classes.content}>
Expand Down
6 changes: 3 additions & 3 deletions packages/circuit-ui/components/Popover/Popover.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,11 @@ describe('Popover', () => {
});
});

it('should render items as role=menuitem and dividers as role=presentation', async () => {
renderPopover(baseProps);
it('should render items as role=menuitem and dividers as aria-hidden', async () => {
const { baseElement } = renderPopover(baseProps);

const items = screen.getAllByRole('menuitem');
const dividers = screen.getAllByRole('presentation');
const dividers = baseElement.querySelectorAll('hr[aria-hidden="true"');
expect(items.length).toBe(2);
expect(dividers.length).toBe(1);

Expand Down
2 changes: 1 addition & 1 deletion packages/circuit-ui/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ export const Popover = ({
isDivider(action) ? (
<Hr
className={classes.divider}
role="presentation"
aria-hidden="true"
key={index}
/>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function DesktopNavigation({
className={classes.secondary}
aria-label={secondaryNavigationLabel}
>
<Skeleton className={classes.headline}>
<Skeleton className={classes.headline} as="div">
<Headline as="h2" size="four">
{activePrimaryLink && activePrimaryLink.label}
</Headline>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function PrimaryLink({
const Element = props.href ? (Link as AsPropType) : 'button';

const suffix = Suffix && (
<Suffix className={classes.suffix} role="presentation" />
<Suffix className={classes.suffix} aria-hidden="true" />
);
const isExternalLink = isExternal || props.target === '_blank';

Expand All @@ -65,7 +65,7 @@ export function PrimaryLink({
aria-current={isActive ? 'page' : undefined}
>
<Skeleton className={clsx(classes.icon, badge && classes['icon-badge'])}>
<Icon role="presentation" size="24" />
<Icon aria-hidden="true" size="24" />
</Skeleton>
<Skeleton>
<Body as="span" className={classes.label}>
Expand All @@ -76,7 +76,7 @@ export function PrimaryLink({
{isExternalLink && (
<ArrowRight
size="16"
role="presentation"
aria-hidden="true"
className={clsx(classes.suffix, classes['external-icon'])}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ function SecondaryLink({ label, badge, ...props }: SecondaryLinkProps) {
aria-current={props.isActive ? 'page' : undefined}
>
<Skeleton className={classes.label}>
<Body size="one" variant={props.isActive ? 'highlight' : undefined}>
<Body
as="span"
size="one"
variant={props.isActive ? 'highlight' : undefined}
>
{label}
</Body>
</Skeleton>
Expand All @@ -69,7 +73,7 @@ function SecondaryGroup({
return (
<li>
{label && (
<Skeleton className={classes['group-headline']}>
<Skeleton className={classes['group-headline']} as="div">
<SubHeadline as="h3">{label}</SubHeadline>
</Skeleton>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const hrStyles = (theme: Theme) => css`
*/
export function Separator(): JSX.Element {
return (
<li role="presentation">
<li aria-hidden="true">
<Hr css={hrStyles} />
</li>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ exports[`Separator > should render with default styles 1`] = `
}
<li
role="presentation"
aria-hidden="true"
>
<hr
class="_base_5bcdd0 circuit-0"
Expand Down
15 changes: 10 additions & 5 deletions packages/circuit-ui/components/Skeleton/Skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
HTMLAttributes,
} from 'react';

import type { AsPropType } from '../../types/prop-types.js';
import { clsx } from '../../styles/clsx.js';

import classes from './Skeleton.module.css';
Expand Down Expand Up @@ -75,19 +76,23 @@ export interface SkeletonProps extends HTMLAttributes<HTMLSpanElement> {
* Default: `false`.
*/
circle?: boolean;
/**
* Render the skeleton using any HTML element.
*/
as?: AsPropType;
}

/**
* A placeholder for asynchronously loaded content with a subtle loading
* animation. Only works when wrapped in a SkeletonContainer.
*/
export const Skeleton = forwardRef<HTMLSpanElement, SkeletonProps>(
({ children, className, circle, ...props }, ref) => {
({ children, className, as: Element = 'span', circle, ...props }, ref) => {
const isLoading = useContext(SkeletonContext);

if (isLoading) {
return (
<span
<Element
{...props}
className={clsx(
classes.base,
Expand All @@ -98,14 +103,14 @@ export const Skeleton = forwardRef<HTMLSpanElement, SkeletonProps>(
ref={ref}
>
{children}
</span>
</Element>
);
}

return (
<span {...props} className={clsx(classes.base, className)} ref={ref}>
<Element {...props} className={clsx(classes.base, className)} ref={ref}>
{children}
</span>
</Element>
);
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Base.args = baseArgs;
export const WithSideNavigation = (args: TopNavigationProps) => {
const [isSideNavigationOpen, setSideNavigationOpen] = useState(false);
const hamburger = {
...args.hamburger,
...args.hamburger!,
isActive: isSideNavigationOpen,
onClick: () => setSideNavigationOpen((prev) => !prev),
};
Expand Down Expand Up @@ -116,7 +116,6 @@ export const WithSideNavigation = (args: TopNavigationProps) => {
WithSideNavigation.storyName = 'With SideNavigation';
WithSideNavigation.args = {
...baseArgs,
pageTitle: 'Home',
hamburger: {
activeLabel: 'Close side navigation',
inactiveLabel: 'Open side navigation',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function TopNavigation({
}, []);

return (
<header role="banner" className={clsx(classes.base, className)} {...props}>
<header className={clsx(classes.base, className)} {...props}>
<div className={classes.wrapper}>
{hamburger && (
<SkeletonContainer isLoading={Boolean(isLoading)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
height: var(--cui-icon-sizes-mega);
}

.details {
display: block;
}

@media (min-width: 768px) {
.avatar {
width: var(--cui-icon-sizes-giga);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Body from '../../../Body/index.js';
import Popover, { PopoverProps } from '../../../Popover/index.js';
import { Skeleton } from '../../../Skeleton/index.js';
import type { UserProps } from '../../types.js';
import utilityClasses from '../../../../styles/utility.js';
import sharedClasses from '../../../../styles/shared.js';
import { clsx } from '../../../../styles/clsx.js';

Expand All @@ -38,12 +39,21 @@ interface ProfileProps extends ButtonHTMLAttributes<HTMLButtonElement> {
}

function Profile({ user, label, className, ...props }: ProfileProps) {
const ariaLabel = [user.name, user.id]
.filter((part) => Boolean(part))
.join(', ');

return (
<button
{...props}
className={clsx(classes.profile, sharedClasses.navigationItem, className)}
className={clsx(
classes.profile,
sharedClasses.navigationItem,
utilityClasses.focusVisibleInset,
className,
)}
type="button"
aria-label={label}
aria-label={ariaLabel}
title={label}
>
<Skeleton circle>
Expand All @@ -54,21 +64,23 @@ function Profile({ user, label, className, ...props }: ProfileProps) {
className={classes.avatar}
/>
) : (
<ProfileIcon role="presentation" />
<ProfileIcon aria-hidden="true" />
)}
</Skeleton>
<div className={classes.details}>
<span className={classes.details}>
<Skeleton className={classes.truncate}>
<Body size="two" variant="highlight">
<Body as="span" size="two" variant="highlight">
{user.name}
</Body>
</Skeleton>
{user.id && (
<Skeleton className={classes.truncate}>
<Body size="two">{user.id}</Body>
<Body as="span" size="two">
{user.id}
</Body>
</Skeleton>
)}
</div>
</span>
<ChevronDown size="16" className={classes.chevron} />
</button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Body from '../../../Body/index.js';
import { useComponents } from '../../../ComponentsContext/index.js';
import { Skeleton } from '../../../Skeleton/index.js';
import { clsx } from '../../../../styles/clsx.js';
import utilityClasses from '../../../../styles/utility.js';
import sharedClasses from '../../../../styles/shared.js';

import classes from './UtilityLinks.module.css';
Expand Down Expand Up @@ -64,10 +65,15 @@ function UtilityLink({
<Element
{...props}
aria-current={isActive ? 'page' : undefined}
className={clsx(classes.anchor, sharedClasses.navigationItem, className)}
className={clsx(
classes.anchor,
sharedClasses.navigationItem,
utilityClasses.focusVisibleInset,
className,
)}
>
<Skeleton className={classes.icon}>
<Icon role="presentation" size="24" />
<Icon aria-hidden="true" size="24" />
</Skeleton>
<Skeleton>
<Body as="span" className={classes.label}>
Expand Down

1 comment on commit 2f4fbaf

@vercel
Copy link

@vercel vercel bot commented on 2f4fbaf Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.