diff --git a/code/addons/docs/src/blocks/components/DocsPage.test.tsx b/code/addons/docs/src/blocks/components/DocsPage.test.tsx new file mode 100644 index 000000000000..fcf3fcbbe402 --- /dev/null +++ b/code/addons/docs/src/blocks/components/DocsPage.test.tsx @@ -0,0 +1,101 @@ +// @vitest-environment happy-dom +import { cleanup, render } from '@testing-library/react'; +import { afterEach, describe, expect, it } from 'vitest'; + +import React from 'react'; + +import { ThemeProvider, convert, themes } from 'storybook/theming'; + +import { DocsContent } from './DocsPage'; + +function ThemedDocsContent({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +describe('DocsContent', () => { + afterEach(() => { + cleanup(); + }); + + describe('accessibility', () => { + it('should render links with underline text decoration for accessibility', () => { + const { container } = render( + +

+ This is a paragraph with a link inside. +

+
+ ); + + const link = container.querySelector('a'); + expect(link).toBeTruthy(); + + const styles = window.getComputedStyle(link!); + expect(styles.textDecoration).toContain('underline'); + }); + + it('should render links with underline in dark theme', () => { + const { container } = render( + + +

+ This is a paragraph with a link inside. +

+
+
+ ); + + const link = container.querySelector('a'); + expect(link).toBeTruthy(); + + const styles = window.getComputedStyle(link!); + expect(styles.textDecoration).toContain('underline'); + }); + + it('should render multiple links with underlines in text blocks', () => { + const { container } = render( + +
+

+ Check out this link and also{' '} + this other link. +

+

+ Here is a third link in another paragraph. +

+
+
+ ); + + const links = container.querySelectorAll('a'); + expect(links).toHaveLength(3); + + links.forEach((link) => { + const styles = window.getComputedStyle(link); + expect(styles.textDecoration).toContain('underline'); + }); + }); + + it('should not underline anchor position markers (a.anchor)', () => { + const { container } = render( + +

+ + Heading + +

+
+ ); + + const anchor = container.querySelector('a.anchor'); + expect(anchor).toBeTruthy(); + + const styles = window.getComputedStyle(anchor!); + expect(styles.textDecoration).not.toContain('underline'); + }); + }); +}); diff --git a/code/addons/docs/src/blocks/components/DocsPage.tsx b/code/addons/docs/src/blocks/components/DocsPage.tsx index 11e01c2c5765..d2f1537c2ab4 100644 --- a/code/addons/docs/src/blocks/components/DocsPage.tsx +++ b/code/addons/docs/src/blocks/components/DocsPage.tsx @@ -114,7 +114,10 @@ export const DocsContent = styled.div(({ theme }) => { lineHeight: '24px', color: theme.color.secondary, - textDecoration: 'none', + // Ensure WCAG Level A compliance (SC 1.4.1), see https://www.w3.org/WAI/WCAG22/Techniques/failures/F73 + textDecoration: 'underline', + textDecorationThickness: '0.03125rem', + textUnderlineOffset: '0.11em', '&.absent': { color: '#cc0000', }, @@ -127,6 +130,10 @@ export const DocsContent = styled.div(({ theme }) => { top: 0, left: 0, bottom: 0, + textDecoration: 'none', + }, + '&.anchor:hover, &.anchor:focus': { + textDecoration: 'underline', }, }, [toGlobalSelector('blockquote')]: { diff --git a/code/core/src/components/components/typography/DocumentWrapper.test.tsx b/code/core/src/components/components/typography/DocumentWrapper.test.tsx new file mode 100644 index 000000000000..3c15119bff92 --- /dev/null +++ b/code/core/src/components/components/typography/DocumentWrapper.test.tsx @@ -0,0 +1,83 @@ +// @vitest-environment happy-dom +import { cleanup, render } from '@testing-library/react'; +import { afterEach, describe, expect, it } from 'vitest'; + +import React from 'react'; + +import { ThemeProvider, convert, themes } from 'storybook/theming'; + +import { DocumentWrapper } from './DocumentWrapper'; + +function ThemedDocumentWrapper({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +describe('DocumentWrapper', () => { + afterEach(() => { + cleanup(); + }); + + describe('accessibility', () => { + it('should render links with underline text decoration for accessibility', () => { + const { container } = render( + +

+ This is a paragraph with a link inside. +

+
+ ); + + const link = container.querySelector('a'); + expect(link).toBeTruthy(); + + const styles = window.getComputedStyle(link!); + expect(styles.textDecoration).toContain('underline'); + }); + + it('should render links with underline in dark theme', () => { + const { container } = render( + + +

+ This is a paragraph with a link inside. +

+
+
+ ); + + const link = container.querySelector('a'); + expect(link).toBeTruthy(); + + const styles = window.getComputedStyle(link!); + expect(styles.textDecoration).toContain('underline'); + }); + + it('should render multiple links with underlines in text blocks', () => { + const { container } = render( + +
+

+ Check out this link and also{' '} + this other link. +

+

+ Here is a third link in another paragraph. +

+
+
+ ); + + const links = container.querySelectorAll('a'); + expect(links).toHaveLength(3); + + links.forEach((link) => { + const styles = window.getComputedStyle(link); + expect(styles.textDecoration).toContain('underline'); + }); + }); + }); +}); diff --git a/code/core/src/components/components/typography/DocumentWrapper.tsx b/code/core/src/components/components/typography/DocumentWrapper.tsx index b5b4cca5eccb..25a91f33b3b7 100644 --- a/code/core/src/components/components/typography/DocumentWrapper.tsx +++ b/code/core/src/components/components/typography/DocumentWrapper.tsx @@ -64,7 +64,10 @@ export const DocumentWrapper = styled.div(({ theme }) => ({ }, a: { color: theme.color.secondary, - textDecoration: 'none', + // Ensure WCAG Level A compliance (SC 1.4.1), see https://www.w3.org/WAI/WCAG22/Techniques/failures/F73 + textDecoration: 'underline', + textDecorationThickness: '0.03125rem', + textUnderlineOffset: '0.11em', }, 'a.absent': { color: '#cc0000', @@ -78,6 +81,10 @@ export const DocumentWrapper = styled.div(({ theme }) => ({ top: 0, left: 0, bottom: 0, + textDecoration: 'none', + }, + '&.anchor:hover, &.anchor:focus': { + textDecoration: 'underline', }, 'h1, h2, h3, h4, h5, h6': { margin: '20px 0 10px', diff --git a/code/core/src/components/components/typography/elements/A.test.tsx b/code/core/src/components/components/typography/elements/A.test.tsx new file mode 100644 index 000000000000..cbb638ca8d5c --- /dev/null +++ b/code/core/src/components/components/typography/elements/A.test.tsx @@ -0,0 +1,75 @@ +// @vitest-environment happy-dom +import { cleanup, render } from '@testing-library/react'; +import { afterEach, describe, expect, it } from 'vitest'; + +import React from 'react'; + +import { ThemeProvider, convert, themes } from 'storybook/theming'; + +import { A } from './A'; + +function ThemedA({ children, ...props }: React.ComponentProps) { + return ( + + {children} + + ); +} + +describe('A', () => { + afterEach(() => { + cleanup(); + }); + + describe('accessibility', () => { + it('should render with underline text decoration for accessibility', () => { + const { container } = render(Test Link); + + const link = container.querySelector('a'); + expect(link).toBeTruthy(); + + const styles = window.getComputedStyle(link!); + expect(styles.textDecoration).toContain('underline'); + }); + + it('should render with underline in dark theme', () => { + const { container } = render( + + Test Link + + ); + + const link = container.querySelector('a'); + expect(link).toBeTruthy(); + + const styles = window.getComputedStyle(link!); + expect(styles.textDecoration).toContain('underline'); + }); + + it('should not underline anchor position markers (a.anchor)', () => { + const { container } = render( + + Anchor Link + + ); + + const link = container.querySelector('a.anchor'); + expect(link).toBeTruthy(); + + const styles = window.getComputedStyle(link!); + expect(styles.textDecoration).not.toContain('underline'); + }); + + it('should render with correct color and styling', () => { + const { container } = render(Link Text); + + const link = container.querySelector('a'); + expect(link).toBeTruthy(); + expect(link?.textContent).toBe('Link Text'); + + const styles = window.getComputedStyle(link!); + expect(styles.textDecoration).toContain('underline'); + expect(styles.fontSize).toBe('inherit'); + }); + }); +}); diff --git a/code/core/src/components/components/typography/elements/A.tsx b/code/core/src/components/components/typography/elements/A.tsx index e91833da6cfc..9ab4e9bf03b2 100644 --- a/code/core/src/components/components/typography/elements/A.tsx +++ b/code/core/src/components/components/typography/elements/A.tsx @@ -8,7 +8,10 @@ export const A = styled(Link)(withReset, ({ theme }) => ({ lineHeight: '24px', color: theme.color.secondary, - textDecoration: 'none', + // Ensure WCAG Level A compliance (SC 1.4.1), see https://www.w3.org/WAI/WCAG22/Techniques/failures/F73 + textDecoration: 'underline', + textDecorationThickness: '0.03125rem', + textUnderlineOffset: '0.11em', '&.absent': { color: '#cc0000', }, @@ -21,5 +24,9 @@ export const A = styled(Link)(withReset, ({ theme }) => ({ top: 0, left: 0, bottom: 0, + textDecoration: 'none', + }, + '&.anchor:hover, &.anchor:focus': { + textDecoration: 'underline', }, })); diff --git a/code/frameworks/nextjs-vite/template/cli/js/Configure.mdx b/code/frameworks/nextjs-vite/template/cli/js/Configure.mdx index 70fcc2a9777c..923c1a47dc93 100644 --- a/code/frameworks/nextjs-vite/template/cli/js/Configure.mdx +++ b/code/frameworks/nextjs-vite/template/cli/js/Configure.mdx @@ -52,6 +52,7 @@ export const RightArrow = () => Add styling and CSS

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

Learn more @@ -67,6 +68,7 @@ export const RightArrow = () => Provide context and mocking

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

Learn more @@ -85,6 +87,7 @@ export const RightArrow = () => Learn more @@ -113,6 +116,7 @@ export const RightArrow = () => Auto-generate living, interactive reference documentation from your components and stories.

Learn more @@ -128,6 +132,7 @@ export const RightArrow = () => Publish to Chromatic

Publish your Storybook to review and collaborate with your entire team.

Learn more @@ -144,6 +149,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live implementation in one place.

Learn more @@ -160,6 +166,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how complex.

Learn more @@ -175,6 +182,7 @@ export const RightArrow = () => Accessibility

Automatically test your components for a11y issues as you develop.

Learn more @@ -190,6 +198,7 @@ export const RightArrow = () => Theming

Theme Storybook's UI to personalize it to your project.

Learn more @@ -202,6 +211,7 @@ export const RightArrow = () => Addons

Integrate your tools with Storybook to connect workflows.

Discover all addons @@ -229,6 +239,7 @@ export const RightArrow = () => Star on GitHub @@ -246,6 +257,7 @@ export const RightArrow = () => Join Discord server @@ -264,6 +276,7 @@ export const RightArrow = () => Watch on YouTube @@ -281,6 +294,7 @@ export const RightArrow = () => Follow guided walkthroughs on for key workflows.

Discover tutorials @@ -328,6 +342,16 @@ export const RightArrow = () => Add styling and CSS

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

Learn more @@ -67,6 +68,7 @@ export const RightArrow = () => Provide context and mocking

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

Learn more @@ -85,6 +87,7 @@ export const RightArrow = () => Learn more @@ -113,6 +116,7 @@ export const RightArrow = () => Auto-generate living, interactive reference documentation from your components and stories.

Learn more @@ -128,6 +132,7 @@ export const RightArrow = () => Publish to Chromatic

Publish your Storybook to review and collaborate with your entire team.

Learn more @@ -144,6 +149,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live implementation in one place.

Learn more @@ -160,6 +166,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how complex.

Learn more @@ -175,6 +182,7 @@ export const RightArrow = () => Accessibility

Automatically test your components for a11y issues as you develop.

Learn more @@ -190,6 +198,7 @@ export const RightArrow = () => Theming

Theme Storybook's UI to personalize it to your project.

Learn more @@ -202,6 +211,7 @@ export const RightArrow = () => Addons

Integrate your tools with Storybook to connect workflows.

Discover all addons @@ -229,6 +239,7 @@ export const RightArrow = () => Star on GitHub @@ -246,6 +257,7 @@ export const RightArrow = () => Join Discord server @@ -264,6 +276,7 @@ export const RightArrow = () => Watch on YouTube @@ -281,6 +294,7 @@ export const RightArrow = () => Follow guided walkthroughs on for key workflows.

Discover tutorials @@ -328,6 +342,16 @@ export const RightArrow = () => Add styling and CSS

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

Learn more @@ -67,6 +68,7 @@ export const RightArrow = () => Provide context and mocking

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

Learn more @@ -85,6 +87,7 @@ export const RightArrow = () => Learn more @@ -113,6 +116,7 @@ export const RightArrow = () => Auto-generate living, interactive reference documentation from your components and stories.

Learn more @@ -128,6 +132,7 @@ export const RightArrow = () => Publish to Chromatic

Publish your Storybook to review and collaborate with your entire team.

Learn more @@ -144,6 +149,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live implementation in one place.

Learn more @@ -160,6 +166,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how complex.

Learn more @@ -175,6 +182,7 @@ export const RightArrow = () => Accessibility

Automatically test your components for a11y issues as you develop.

Learn more @@ -190,6 +198,7 @@ export const RightArrow = () => Theming

Theme Storybook's UI to personalize it to your project.

Learn more @@ -202,6 +211,7 @@ export const RightArrow = () => Addons

Integrate your tools with Storybook to connect workflows.

Discover all addons @@ -229,6 +239,7 @@ export const RightArrow = () => Star on GitHub @@ -246,6 +257,7 @@ export const RightArrow = () => Join Discord server @@ -264,6 +276,7 @@ export const RightArrow = () => Watch on YouTube @@ -281,6 +294,7 @@ export const RightArrow = () => Follow guided walkthroughs on for key workflows.

Discover tutorials @@ -328,6 +342,16 @@ export const RightArrow = () => Add styling and CSS

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

Learn more @@ -60,6 +61,7 @@ export const RightArrow = () => Provide context and mocking

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

Learn more @@ -72,6 +74,7 @@ export const RightArrow = () => Learn more @@ -94,6 +97,7 @@ export const RightArrow = () => Auto-generate living, interactive reference documentation from your components and stories.

Learn more @@ -103,6 +107,7 @@ export const RightArrow = () => Publish to Chromatic

Publish your Storybook to review and collaborate with your entire team.

Learn more @@ -113,6 +118,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live implementation in one place.

Learn more @@ -123,6 +129,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how complex.

Learn more @@ -132,6 +139,7 @@ export const RightArrow = () => Accessibility

Automatically test your components for a11y issues as you develop.

Learn more @@ -141,6 +149,7 @@ export const RightArrow = () => Theming

Theme Storybook's UI to personalize it to your project.

Learn more @@ -153,6 +162,7 @@ export const RightArrow = () => Addons

Integrate your tools with Storybook to connect workflows.

Discover all addons @@ -168,6 +178,7 @@ export const RightArrow = () => Star on GitHub @@ -178,6 +189,7 @@ export const RightArrow = () => Join Discord server @@ -189,6 +201,7 @@ export const RightArrow = () => Watch on YouTube @@ -199,6 +212,7 @@ export const RightArrow = () => Follow guided walkthroughs on for key workflows.

Discover tutorials @@ -246,6 +260,16 @@ export const RightArrow = () => Add styling and CSS

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

Learn more @@ -60,6 +61,7 @@ export const RightArrow = () => Provide context and mocking

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

Learn more @@ -72,6 +74,7 @@ export const RightArrow = () => Learn more @@ -94,6 +97,7 @@ export const RightArrow = () => Auto-generate living, interactive reference documentation from your components and stories.

Learn more @@ -103,6 +107,7 @@ export const RightArrow = () => Publish to Chromatic

Publish your Storybook to review and collaborate with your entire team.

Learn more @@ -113,6 +118,7 @@ export const RightArrow = () => Embed your stories into Figma to cross-reference the design and live implementation in one place.

Learn more @@ -123,6 +129,7 @@ export const RightArrow = () => Use stories to test a component in all its variations, no matter how complex.

Learn more @@ -132,6 +139,7 @@ export const RightArrow = () => Accessibility

Automatically test your components for a11y issues as you develop.

Learn more @@ -141,6 +149,7 @@ export const RightArrow = () => Theming

Theme Storybook's UI to personalize it to your project.

Learn more @@ -153,6 +162,7 @@ export const RightArrow = () => Addons

Integrate your tools with Storybook to connect workflows.

Discover all addons @@ -168,6 +178,7 @@ export const RightArrow = () => Star on GitHub @@ -178,6 +189,7 @@ export const RightArrow = () => Join Discord server @@ -189,6 +201,7 @@ export const RightArrow = () => Watch on YouTube @@ -199,6 +212,7 @@ export const RightArrow = () => Follow guided walkthroughs on for key workflows.

Discover tutorials @@ -246,6 +260,16 @@ export const RightArrow = () =>