Skip to content

Commit 08d7182

Browse files
committed
improve the accessibility experience of the Anchor component when navigating to external pages.
1 parent 6281a89 commit 08d7182

File tree

3 files changed

+81
-4
lines changed

3 files changed

+81
-4
lines changed

.changeset/gentle-seahorses-press.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sumup-oss/circuit-ui": minor
3+
---
4+
5+
Added two new props `isExternal` and `externalLabel` to the Anchor component props to describe to visually impaired users that the link leads to an external page or opens in a new tab.

packages/circuit-ui/components/Anchor/Anchor.spec.tsx

+30
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,36 @@ describe('Anchor', () => {
107107
expect(onClick).toHaveBeenCalledTimes(1);
108108
});
109109

110+
it('should render as link with an description label when it renders an external link', () => {
111+
render(
112+
<Anchor
113+
href="www.external.link.com"
114+
{...baseProps}
115+
isExternal
116+
externalLabel="Opens in a new tab"
117+
/>,
118+
);
119+
120+
expect(screen.getByRole('link')).toHaveAccessibleDescription(
121+
'Opens in a new tab',
122+
);
123+
});
124+
125+
it('should render as button with an description label when it renders an external link', () => {
126+
render(
127+
<Anchor
128+
onClick={vi.fn()}
129+
{...baseProps}
130+
isExternal
131+
externalLabel="Opens in a new tab"
132+
/>,
133+
);
134+
135+
expect(screen.getByRole('button')).toHaveAccessibleDescription(
136+
'Opens in a new tab',
137+
);
138+
});
139+
110140
it('should meet accessibility guidelines', async () => {
111141
const { container } = render(
112142
<Anchor {...baseProps} href="https://sumup.com" onClick={vi.fn} />,

packages/circuit-ui/components/Anchor/Anchor.tsx

+46-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
type ButtonHTMLAttributes,
2222
type ReactNode,
2323
type Ref,
24+
useId,
2425
} from 'react';
2526

2627
import type { ReturnType } from '../../types/return-type.js';
@@ -43,6 +44,14 @@ export interface BaseProps extends Omit<BodyProps, 'color'> {
4344
* The ref to the HTML DOM element, it can be a button an anchor or a span, typed as any for now because of complex js manipulation with styled components
4445
*/
4546
ref?: Ref<any>;
47+
/**
48+
* Whether the link leads to an external page or opens in a new tab.
49+
*/
50+
isExternal?: boolean;
51+
/**
52+
* Short label to describe that the link leads to an external page or opens in a new tab.
53+
*/
54+
externalLabel?: string;
4655
}
4756
type LinkElProps = Omit<
4857
AnchorHTMLAttributes<HTMLAnchorElement>,
@@ -61,34 +70,67 @@ export type AnchorProps = BaseProps & LinkElProps & ButtonElProps;
6170
*/
6271
export const Anchor = forwardRef(
6372
(
64-
{ className, ...props }: AnchorProps,
73+
{
74+
className,
75+
isExternal,
76+
externalLabel,
77+
'aria-describedby': descriptionId,
78+
children,
79+
...props
80+
}: AnchorProps,
6581
ref?: BaseProps['ref'],
6682
): ReturnType => {
6783
const components = useComponents();
6884
const Link = components.Link as AsPropType;
85+
const externalLabelId = useId();
86+
const descriptionIds = clsx(
87+
externalLabel && externalLabelId,
88+
descriptionId,
89+
);
90+
const isExternalLink = isExternal || props.target === '_blank';
6991

7092
if (!props.href && !props.onClick) {
71-
return <Body as="span" {...props} ref={ref} />;
93+
return (
94+
<Body as="span" {...props} ref={ref}>
95+
{children}
96+
</Body>
97+
);
7298
}
7399

74100
if (props.href) {
75101
return (
76102
<Body
77103
{...props}
104+
aria-describedby={descriptionIds}
78105
className={clsx(classes.base, utilClasses.focusVisible, className)}
79106
as={Link}
80107
ref={ref}
81-
/>
108+
>
109+
{children}
110+
{isExternalLink && externalLabel && (
111+
<span id={externalLabelId} className={utilClasses.hideVisually}>
112+
{externalLabel}
113+
</span>
114+
)}
115+
</Body>
82116
);
83117
}
84118

85119
return (
86120
<Body
87121
as="button"
88122
{...props}
123+
aria-describedby={descriptionIds}
89124
className={clsx(classes.base, utilClasses.focusVisible, className)}
90125
ref={ref}
91-
/>
126+
>
127+
{children}
128+
{isExternalLink && externalLabel && (
129+
<span id={externalLabelId} className={utilClasses.hideVisually}>
130+
{externalLabel}
131+
</span>
132+
)}
133+
</Body>
92134
);
93135
},
94136
);

0 commit comments

Comments
 (0)