Skip to content

Commit 0293ad9

Browse files
fix(a11y): add correct aria-label on ThemeToggle button (#8045)
* fix aria-label a11y * fix test * add a11y test for aria-label * fix i18n aria-label text * fix hydration * refactor test * fix test * Exclude ThemeToggle btn from SSR
1 parent 2e4a6d2 commit 0293ad9

File tree

4 files changed

+47
-11
lines changed

4 files changed

+47
-11
lines changed

apps/site/components/withNavBar.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import LanguageDropdown from '@node-core/ui-components/Common/LanguageDropDown';
44
import Skeleton from '@node-core/ui-components/Common/Skeleton';
5-
import ThemeToggle from '@node-core/ui-components/Common/ThemeToggle';
65
import NavBar from '@node-core/ui-components/Containers/NavBar';
76
// TODO(@AvivKeller): I don't like that we are importing styles from another module
87
import styles from '@node-core/ui-components/Containers/NavBar/index.module.css';
@@ -27,6 +26,16 @@ const SearchButton = dynamic(() => import('#site/components/Common/Search'), {
2726
),
2827
});
2928

29+
const ThemeToggle = dynamic(
30+
() => import('@node-core/ui-components/Common/ThemeToggle'),
31+
{
32+
ssr: false,
33+
loading: () => (
34+
<Skeleton className={styles.themeToggleSkeleton} loading={true} />
35+
),
36+
}
37+
);
38+
3039
const WithNavBar: FC = () => {
3140
const { navigationItems } = useSiteNavigation();
3241
const { resolvedTheme, setTheme } = useTheme();
@@ -39,6 +48,11 @@ const WithNavBar: FC = () => {
3948
const toggleCurrentTheme = () =>
4049
setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
4150

51+
const themeToggleAriaLabel =
52+
resolvedTheme === 'dark'
53+
? t('components.common.themeToggle.light')
54+
: t('components.common.themeToggle.dark');
55+
4256
const changeLanguage = (locale: SimpleLocaleConfig) =>
4357
replace(pathname!, { locale: locale.code });
4458

@@ -63,7 +77,7 @@ const WithNavBar: FC = () => {
6377

6478
<ThemeToggle
6579
onClick={toggleCurrentTheme}
66-
aria-label={t('components.common.themeToggle.label')}
80+
aria-label={themeToggleAriaLabel}
6781
/>
6882

6983
<LanguageDropdown

apps/site/tests/e2e/general-behavior.spec.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,25 @@ const locators = {
1313
navLinksLocator: `[aria-label="${englishLocale.components.containers.navBar.controls.toggle}"] + div`,
1414
// Global UI controls
1515
languageDropdownName: englishLocale.components.common.languageDropdown.label,
16-
themeToggleName: englishLocale.components.common.themeToggle.label,
17-
16+
themeToggleAriaLabels: {
17+
light: englishLocale.components.common.themeToggle.light,
18+
dark: englishLocale.components.common.themeToggle.dark,
19+
},
1820
// Search components (from Orama library)
1921
searchButtonTag: 'orama-button',
2022
searchInputTag: 'orama-input',
2123
searchResultsTag: 'orama-search-results',
2224
};
2325

2426
const getTheme = (page: Page) =>
25-
page.evaluate(() => document.documentElement.dataset.theme);
27+
page.evaluate(
28+
() => document.documentElement.dataset.theme as 'light' | 'dark'
29+
);
30+
31+
const getCurrentAriaLabel = (theme: string) =>
32+
theme === 'dark'
33+
? locators.themeToggleAriaLabels.light
34+
: locators.themeToggleAriaLabels.dark;
2635

2736
const openLanguageMenu = async (page: Page) => {
2837
const button = page.getByRole('button', {
@@ -65,21 +74,27 @@ test.describe('Node.js Website', () => {
6574
test.describe('Theme', () => {
6675
test('should toggle between light/dark themes', async ({ page }) => {
6776
const themeToggle = page.getByRole('button', {
68-
name: locators.themeToggleName,
77+
name: /Switch to (Light|Dark) Mode/i,
6978
});
70-
await expect(themeToggle).toBeVisible();
7179

7280
const initialTheme = await getTheme(page);
81+
const initialAriaLabel = getCurrentAriaLabel(initialTheme);
82+
let currentAriaLabel = await themeToggle.getAttribute('aria-label');
83+
expect(currentAriaLabel).toBe(initialAriaLabel);
84+
7385
await themeToggle.click();
7486

7587
const newTheme = await getTheme(page);
76-
expect(newTheme).not.toEqual(initialTheme);
77-
expect(['light', 'dark']).toContain(newTheme);
88+
const newAriaLabel = getCurrentAriaLabel(newTheme);
89+
currentAriaLabel = await themeToggle.getAttribute('aria-label');
90+
91+
expect(newTheme).not.toBe(initialTheme);
92+
expect(currentAriaLabel).toBe(newAriaLabel);
7893
});
7994

8095
test('should persist theme across page navigation', async ({ page }) => {
8196
const themeToggle = page.getByRole('button', {
82-
name: locators.themeToggleName,
97+
name: /Switch to (Light|Dark) Mode/i,
8398
});
8499
await themeToggle.click();
85100
const selectedTheme = await getTheme(page);

packages/i18n/src/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@
229229
"label": "Choose Language"
230230
},
231231
"themeToggle": {
232-
"label": "Toggle Dark Mode"
232+
"light": "Switch to Light Mode",
233+
"dark": "Switch to Dark Mode"
233234
}
234235
},
235236
"metabar": {

packages/ui-components/src/Containers/NavBar/index.module.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ span.searchButtonSkeleton {
108108
}
109109
}
110110

111+
span.themeToggleSkeleton {
112+
@apply size-9
113+
rounded-md
114+
py-4;
115+
}
116+
111117
.ghIconWrapper {
112118
@apply size-9
113119
rounded-md

0 commit comments

Comments
 (0)