Skip to content

fix(aria-controls): adding aria-controls to expandable elements #3069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ea3238b
added aria-expanded and aria-controls to Disclosure
LinKCoding Mar 21, 2025
c1991ca
update infotip aria attributes
LinKCoding Mar 21, 2025
b488938
added controls to listrow and expandcontrol
LinKCoding Mar 24, 2025
e7ba3ba
formatted
LinKCoding Mar 24, 2025
eb4bfc4
Merge branch 'main' into kl-gm-1006
LinKCoding Mar 27, 2025
375c843
fix list story
LinKCoding Mar 27, 2025
c5c7894
remove minichevron icon from list stores
LinKCoding Mar 27, 2025
af644bd
updated drawer and flyout
LinKCoding Mar 27, 2025
e3a6542
formatted
LinKCoding Mar 31, 2025
09dafab
updated drawer and flyout stories
LinKCoding Apr 1, 2025
7b8a1b0
updated guidance on listrow
LinKCoding Apr 1, 2025
b8d36af
Merge branch 'main' into kl-gm-1006
LinKCoding Apr 1, 2025
d4851a7
formatted
LinKCoding Apr 1, 2025
b9d903e
omit id from DisclosureProps
LinKCoding Apr 1, 2025
93c7be2
Merge branch 'main' into kl-gm-1006
LinKCoding Apr 2, 2025
970247f
updated disclosure types
LinKCoding Apr 2, 2025
416098d
formatted
LinKCoding Apr 2, 2025
f6dc0bc
updated disclosure to correct props and types
LinKCoding Apr 3, 2025
2fe31e1
fix typo
LinKCoding Apr 3, 2025
750cb72
Merge branch 'main' into kl-gm-1006
LinKCoding Apr 3, 2025
bba3aaf
clean up of useId
LinKCoding Apr 3, 2025
c1799a7
formatted
LinKCoding Apr 3, 2025
dd23d8f
updated disclosure body id
LinKCoding Apr 3, 2025
978394f
fixed typo in id
LinKCoding Apr 3, 2025
bdaef81
Merge branch 'main' into kl-gm-1006
LinKCoding Apr 3, 2025
f37c7d5
pushing for build
LinKCoding Apr 3, 2025
b64c25f
updated disclosure to conditionally set aria-controls
LinKCoding Apr 4, 2025
ab014d2
Merge branch 'main' into kl-gm-1006
LinKCoding Apr 4, 2025
699c116
re-implement useId()
LinKCoding Apr 4, 2025
9bafa40
update drawer/flyout examples
LinKCoding Apr 4, 2025
0499920
removed old comment and updated another
LinKCoding Apr 4, 2025
89b1cd3
Merge branch 'main' into kl-gm-1006
LinKCoding Apr 7, 2025
870787e
removed aria-controls from disclosure
LinKCoding Apr 7, 2025
269659e
leftover code cleanup
LinKCoding Apr 7, 2025
46bd453
Merge branch 'main' into kl-gm-1006
LinKCoding Apr 17, 2025
1c0c050
Merge branch 'main' into kl-gm-1006
LinKCoding Apr 22, 2025
b719c11
left placeholders to revisit aria-controls
LinKCoding Apr 22, 2025
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
2 changes: 2 additions & 0 deletions packages/gamut/src/DataList/Controls/ExpandControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const ExpandControl: React.FC<ExpandColProps> = ({
}}
aria-label={`Expand ${id} Row`}
aria-expanded={expanded}
// REVISIT THIS
aria-controls={id ?? undefined}
>
<Rotation rotated={expanded}>
<MiniChevronDownIcon color="text-disabled" />
Expand Down
1 change: 1 addition & 0 deletions packages/gamut/src/DataList/Tables/Rows/TableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const TableRow: DataRow = ({
return (
<ListRow
as="tr"
id={String(id)}
numOfColumns={numberOfColumns}
selectable={selectable}
{...listRowProps}
Expand Down
23 changes: 12 additions & 11 deletions packages/gamut/src/Disclosure/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,36 +29,37 @@ export const Disclosure: React.FC<DisclosureProps> = ({
variant,
}) => {
const [isExpanded, setIsExpanded] = useState(initiallyExpanded);

return (
<DisclosureWrapper
as={isListItem ? 'li' : undefined}
column
variant={variant}
hasBorder={hasBorder}
onClick={() => onClick?.()}
as={isListItem ? 'li' : undefined}
variant={variant}
>
<DisclosureButton
spacing={spacing}
disabled={disabled}
heading={heading}
headingLevel={headingLevel}
overline={overline}
subheading={subheading}
isExpanded={isExpanded}
overline={overline}
setIsExpanded={setIsExpanded}
disabled={disabled}
spacing={spacing}
subheading={subheading}
/>
<AnimatePresence>
{isExpanded && (
<ExpandInCollapseOut>
<DisclosureBody
body={body}
hasPanelBg={hasPanelBg}
ctaText={ctaText}
spacing={spacing}
ctaCallback={ctaCallback}
buttonPlacement={buttonPlacement}
href={href}
buttonType={button}
ctaCallback={ctaCallback}
ctaText={ctaText}
hasPanelBg={hasPanelBg}
href={href}
spacing={spacing}
/>
</ExpandInCollapseOut>
)}
Expand Down
11 changes: 9 additions & 2 deletions packages/gamut/src/Drawer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ export interface DrawerProps extends Omit<BoxProps, 'width'> {
* Which edge of the drawer content container is aligned to during the animation.
*/
alignContentContainer?: 'left' | 'right';
// REVISIT THIS
/**
* This `id` is used to link the element that expands the Drawer to the Drawer itself.
* It is needed for the `aria-controls` attribute to work properly for accessibility.
*/
id: string;
}

export const Drawer: React.FC<DrawerProps> = ({
alignContentContainer = 'right',
children,
expanded,
alignContentContainer = 'right',
id,
...props
}) => {
const drawerRef = useRef<HTMLDivElement>(null);
Expand All @@ -41,9 +48,9 @@ export const Drawer: React.FC<DrawerProps> = ({
{expanded ? (
<DrawerBase
animate={{ width: fullWidth }}
aria-expanded={expanded}
bg="background-current"
exit={{ width: 0 }}
id={id}
initial={{ width: 0 }}
overflow="clip"
position="relative"
Expand Down
12 changes: 10 additions & 2 deletions packages/gamut/src/Flyout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,23 @@ export interface FlyoutProps extends WithChildrenProp {
*/
title: React.ReactNode;
bg?: Colors;
// REVISIT THIS
/**
* This `id` is used to link the element that expands the Drawer to the Drawer itself.
* It is needed for the `aria-controls` attribute to work properly for accessibility.
*/
id: string;
}

export const Flyout: React.FC<FlyoutProps> = ({
bg = 'background',
children,
closeLabel = 'Close',
id,
expanded,
openFrom = 'left',
onClose,
title,
bg = 'background',
}) => {
return (
<Overlay
Expand All @@ -56,13 +63,14 @@ export const Flyout: React.FC<FlyoutProps> = ({
>
<Background bg={bg}>
<Drawer
alignContentContainer={openFrom === 'left' ? 'right' : 'left'}
bottom={0}
display="flex"
expanded={expanded}
flexDirection={openFrom === 'left' ? 'row' : 'row-reverse'}
id={id}
position="fixed"
top={0}
alignContentContainer={openFrom === 'left' ? 'right' : 'left'}
{...{ [openFrom]: 0 }}
>
<FlexBox
Expand Down
16 changes: 13 additions & 3 deletions packages/gamut/src/List/ListRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { getGridTemplateColumns } from './utils';
export interface RowProps
extends Partial<PublicListProps<ComponentProps<typeof RowEl>>> {
header?: boolean;
// REVISIT THIS
/** Used to link expandable content with the component that does the expanding, i.e. it's used to set the value for aria-controls */
id?: string;
/** This is an internal prop that is largely only used for the DataTable component */
numOfColumns?: number;
/** This is an internal prop that is largely only used for the DataTable component */
Expand Down Expand Up @@ -45,12 +48,13 @@ const DivExpand = styled(motion.div)(expandStyles);
const TDExpand = styled(motion.td)(expandStyles);

const ExpandInCollapseOut: React.FC<
WithChildrenProp & { as: 'td' | 'div' }
> = ({ as, children }) => {
WithChildrenProp & { as: 'td' | 'div'; id: string | undefined }
> = ({ as, children, id }) => {
const ResponsiveExpand = as === 'td' ? TDExpand : DivExpand;

return (
<ResponsiveExpand
id={id}
initial="collapsed"
exit="collapsed"
animate="expanded"
Expand All @@ -68,6 +72,7 @@ const ExpandInCollapseOut: React.FC<
export const ListRow = forwardRef<HTMLLIElement, ListRowProps>(
(
{
id,
children,
expanded,
expandedRowAriaLabel,
Expand Down Expand Up @@ -101,6 +106,8 @@ export const ListRow = forwardRef<HTMLLIElement, ListRowProps>(
<RowEl
as="div"
{...rowConfig}
// REVISIT THIS
aria-controls={onClick ? id : undefined}
aria-expanded={renderExpanded && onClick ? expanded : undefined}
clickable={Boolean(onClick)}
isOl={isOl}
Expand Down Expand Up @@ -141,7 +148,10 @@ export const ListRow = forwardRef<HTMLLIElement, ListRowProps>(
{content}
<AnimatePresence>
{expanded && (
<ExpandInCollapseOut as={isTable ? 'td' : 'div'}>
<ExpandInCollapseOut
as={isTable ? 'td' : 'div'}
id={id ?? undefined}
>
<Box role="region" aria-label={expandedRowAriaLabel}>
{renderExpanded?.()}
</Box>
Expand Down
8 changes: 7 additions & 1 deletion packages/gamut/src/Tip/InfoTip/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react';
import { useEffect, useId, useRef, useState } from 'react';

import { FloatingTip } from '../shared/FloatingTip';
import { InlineTip } from '../shared/InlineTip';
Expand Down Expand Up @@ -88,6 +88,8 @@ export const InfoTip: React.FC<InfoTipProps> = ({

const Tip = loaded && isFloating ? FloatingTip : InlineTip;

const textId = useId();

const tipProps = {
alignment,
escapeKeyPressHandler,
Expand All @@ -101,14 +103,18 @@ export const InfoTip: React.FC<InfoTipProps> = ({
<ScreenreaderNavigableText
aria-hidden={isAriaHidden}
aria-live="assertive"
id={textId}
screenreader
>
{!isTipHidden ? info : `\xa0`}
</ScreenreaderNavigableText>
);

// REVISIT THIS
const tip = (
<InfoTipButton
aria-controls={textId}
aria-expanded={!isTipHidden}
active={!isTipHidden}
emphasis={emphasis}
onClick={() => clickHandler()}
Expand Down
26 changes: 26 additions & 0 deletions packages/styleguide/src/lib/Atoms/Drawer/Drawer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@ An example of a molecule that uses Drawer is the <LinkTo id="Molecules/Flyout">F

Our Drawers are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded`.

{/* REVISIT THIS */}
The component that toggles the `Drawer` should include both `aria-expanded` and `aria-controls` attributes. The `aria-controls` attribute must reference the `id` of the `Drawer`. The `aria-expanded` attribute should reflect the value of the `expanded` prop passed to the `Drawer`.

```tsx
const ExampleDrawer: React.FC = () => {
const [expanded, setExpanded] = useState(false);
const drawerId = useId();

return (
<FlexBox>
<Drawer expanded={expanded} id={drawerId}>
Example Drawer
</Drawer>
<StrokeButton
// REVISIT THIS
aria-controls={expanded ? drawerId : undefined}
aria-expanded={expanded}
onClick={() => setExpanded((previousExpanded) => !previousExpanded)}
>
Set the proper aria-labels!
</StrokeButton>
</FlexBox>
);
};
```

## Playground

<Canvas sourceState="shown" of={DrawerStories.Default} />
Expand Down
11 changes: 9 additions & 2 deletions packages/styleguide/src/lib/Atoms/Drawer/Drawer.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Drawer, FlexBox, StrokeButton } from '@codecademy/gamut';
import type { Meta } from '@storybook/react';
import { useState } from 'react';
import { useId, useState } from 'react';

const meta: Meta<typeof Drawer> = {
component: Drawer,
Expand All @@ -11,10 +11,17 @@ export default meta;

export const Default: React.FC = () => {
const [expanded, setExpanded] = useState(false);
const drawerId = useId();
return (
<FlexBox bg="paleYellow" height="20rem">
<Drawer expanded={expanded}>Drawer content in here!</Drawer>
{/* // REVISIT THIS */}
<Drawer expanded={expanded} id={drawerId}>
Drawer content in here!
</Drawer>
<StrokeButton
// REVISIT THIS
aria-controls={expanded ? drawerId : undefined}
aria-expanded={expanded}
onClick={() => setExpanded((previousExpanded) => !previousExpanded)}
>
Toggle Drawer
Expand Down
28 changes: 27 additions & 1 deletion packages/styleguide/src/lib/Molecules/Flyout/Flyout.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,33 @@ On button click, a container animates in from the left or right side of the wind

Internally, Flyout is a combination of <LinkTo id="Molecules/Modals/Overlay">Overlay</LinkTo> and <LinkTo id="Atoms/Drawer">Drawer</LinkTo>.

Our Flyouts are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded` and `onChange`:
Our Flyouts are [controlled components](https://reactjs.org/docs/forms.html#controlled-components), so their checked value must be controlled by an external state passed in as `expanded` and `onChange`.

{/* // REVISIT THIS */}
The component that toggles the `Flyout` should include both `aria-expanded` and `aria-controls` attributes. The `aria-controls` attribute must reference the `id` of the `Flyout`. The `aria-expanded` attribute should reflect the value of the `expanded` prop passed to the `Flyout`.

```tsx
const ExampleFlyout: React.FC = () => {
const [expanded, setExpanded] = useState(false);
const flyoutId = useId();

return (
<FlexBox>
<Flyout expanded={expanded} id={flyoutId}>
Example Flyout
</Flyout>
<StrokeButton
aria-expanded={expanded}
// REVISIT THIS
aria-controls={expanded ? drawerId : undefined}
onClick={() => setExpanded((previousExpanded) => !previousExpanded)}
>
Set the proper aria-labels!
</StrokeButton>
</FlexBox>
);
};
```

## Playground

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { useState } from 'react';

const meta: Meta<typeof Flyout> = {
component: Flyout,
args: {},
args: {
id: 'flyout-example',
},
};

export default meta;
Expand Down Expand Up @@ -35,7 +37,12 @@ export const FlyoutExample: Story = {
hurricane...
</Box>
</Flyout>
<StrokeButton onClick={() => setExpanded(true)}>
<StrokeButton
// REVISIT THIS
aria-controls={expanded ? args.id : undefined}
aria-expanded={expanded}
onClick={() => setExpanded(true)}
>
Tell me more?!
</StrokeButton>
<FlexBox>
Expand Down
Loading
Loading