Skip to content

A11y: Underline MDX links for WCAG SC 1.4.1 compliance#33139

Merged
Sidnioulz merged 24 commits into
storybookjs:nextfrom
NikhilChowdhury27:fix-issue-33017-mdx-link-underlines
Mar 10, 2026
Merged

A11y: Underline MDX links for WCAG SC 1.4.1 compliance#33139
Sidnioulz merged 24 commits into
storybookjs:nextfrom
NikhilChowdhury27:fix-issue-33017-mdx-link-underlines

Conversation

@NikhilChowdhury27
Copy link
Copy Markdown
Contributor

@NikhilChowdhury27 NikhilChowdhury27 commented Nov 24, 2025

Closes #33017

What I did

Added textDecoration: 'underline' to links in MDX/docs for WCAG 2.1 Level A accessibility compliance (Success Criterion 1.4.1 - Use of Color).

Files changed:

  • code/addons/docs/src/blocks/components/DocsPage.tsx - Added underline to links in docs pages (.sbdocs-content)
  • code/core/src/components/components/typography/DocumentWrapper.tsx - Added underline to links in MDX wrapper
  • code/core/src/components/components/typography/elements/A.tsx - Added underline to anchor elements

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Tested:

  • ✅ Unit tests passing (7 tests in 2 files: DocsPage.test.tsx, DocumentWrapper.test.tsx)
  • ✅ Visual verification at http://localhost:6006
  • ✅ Links underlined in light and dark themes
  • ✅ Anchor links (.anchor) correctly remain without underlines

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update MIGRATION.MD

Note: No documentation updates needed - visual accessibility enhancement only.


Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels

    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>


Visual Verification

All links now have underlines in both light and dark themes:

![Links with underlines - light theme]
Screenshot 2025-11-24 at 7 31 25 PM

![Links with underlines - dark theme]
Screenshot 2025-11-24 at 7 31 15 PM

References:

Summary by CodeRabbit

  • Style

    • Links in docs and typography now render underlined by default with improved thickness and offset for better visibility and WCAG alignment; decorative anchor markers remain ununderlined. Button-styled links and icon decorations remain non-underlined.
  • Tests

    • Added accessibility tests verifying underline styling in light and dark themes, across multiple-link content, and confirming anchor markers and button-style links are not underlined.

Copy link
Copy Markdown
Member

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

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

Thanks for giving this a go, @NikhilChowdhury27

Please, when using AI, perform your own manual review before requesting a review from maintainers; there are many PRs and issues to review and the maintainers shouldn't need to delay other PRs to catch obvious issues with AI-generated code.

On the screenshots you provided, we can see links that are not underlined. Therefore, some CSS changes are missing. Dosubot suggested that sbdocs classes need to be updated and this is probably what's missing.

@Sidnioulz Sidnioulz self-assigned this Nov 24, 2025
@Sidnioulz Sidnioulz changed the title fix(accessibility): Add underline styling to MDX links for WCAG compliance A11y: Underline MDX links for WCAG SC 1.4.1 compliance Nov 24, 2025
@Sidnioulz
Copy link
Copy Markdown
Member

Also, please make sure your AI does not remove any section from the PR template. We need the maintainer checklist and canary section preserved.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 24, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Default link styling changed from non-underlined to underlined (with thickness and offset) across core typography and docs; .anchor/permalink links remain non-underlined. New tests verify underlines in light/dark themes, multiple-link blocks, and anchor exceptions. Button text-decoration moved into its > svg rule.

Changes

Cohort / File(s) Summary
Typography — core link styles
code/core/src/components/components/typography/DocumentWrapper.tsx, code/core/src/components/components/typography/elements/A.tsx, code/core/src/components/components/typography/link/link.tsx
Default a styling changed from textDecoration: 'none' to textDecoration: 'underline' with textDecorationThickness: 0.5px and textUnderlineOffset: 0.2em; WCAG comment added. .anchor class explicitly set to textDecoration: 'none'. No API/signature changes.
Docs content styles
code/addons/docs/src/blocks/components/DocsPage.tsx
Global a selector in docs changed to underline with thickness/offset and kept .anchor { textDecoration: 'none' }; hover/focus adjusted to underline anchors.
Tests — typography
code/core/src/components/components/typography/elements/A.test.tsx, code/core/src/components/components/typography/DocumentWrapper.test.tsx
New accessibility-focused tests: asserts anchors render underlined in light/dark themes, a.anchor remains non-underlined, and multiple links in text blocks are underlined.
Tests — docs
code/addons/docs/src/blocks/components/DocsPage.test.tsx
New tests for DocsContent: link underlines in light/dark themes, multiple-link blocks underlined, and a.anchor not underlined.
Button styling tweak
code/core/src/components/components/Button/Button.tsx
textDecoration: 'none' moved from base StyledButton into nested > svg rule (scoped change; no signature changes).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
code/core/src/components/components/typography/DocumentWrapper.test.tsx (1)

19-87: Comprehensive accessibility test coverage!

The test suite thoroughly validates underline styling across:

  • Light and dark themes
  • Single and multiple link scenarios
  • Multiple paragraphs/blocks

The use of getComputedStyle with toContain('underline') is the correct approach, as textDecoration can include additional values like color and style.

Optional enhancement: Consider adding a test case for anchor links (e.g., <a className="anchor">) to verify they maintain the special styling defined at DocumentWrapper.tsx line 93, which removes underlines on hover for heading position markers. This would document the intentional exception to the underline rule.

it('should not underline anchor position markers on hover', () => {
  const { container } = render(
    <ThemedDocumentWrapper>
      <h2>
        <a className="anchor" href="#heading">
          Heading
        </a>
      </h2>
    </ThemedDocumentWrapper>
  );

  const anchor = container.querySelector('a.anchor');
  expect(anchor).toBeTruthy();
  
  // Anchor links should still have underline by default
  const styles = window.getComputedStyle(anchor!);
  expect(styles.textDecoration).toContain('underline');
  
  // Note: Testing :hover pseudo-class would require additional setup
  // This test documents the default state
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf8beb1 and fe0534c.

📒 Files selected for processing (3)
  • code/core/src/components/components/typography/DocumentWrapper.test.tsx (1 hunks)
  • code/core/src/components/components/typography/DocumentWrapper.tsx (1 hunks)
  • code/core/src/components/components/typography/elements/A.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.
📚 Learning: 2025-11-05T09:36:55.944Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.

Applied to files:

  • code/core/src/components/components/typography/elements/A.tsx
  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
🧬 Code graph analysis (1)
code/core/src/components/components/typography/DocumentWrapper.test.tsx (1)
code/core/src/components/components/typography/DocumentWrapper.tsx (1)
  • DocumentWrapper (3-310)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal
🔇 Additional comments (3)
code/core/src/components/components/typography/DocumentWrapper.tsx (1)

67-67: Excellent accessibility improvement!

This change ensures links are distinguishable by more than color alone, meeting WCAG 2.1 Level A (SC 1.4.1). The implementation correctly preserves the intentional textDecoration: 'none' for a.anchor elements on hover (line 93), which are heading position markers rather than content links.

code/core/src/components/components/typography/elements/A.tsx (1)

11-11: LGTM! Consistent accessibility enhancement.

This change aligns perfectly with the DocumentWrapper.tsx update, ensuring that the A component also provides underlined links for improved accessibility.

code/core/src/components/components/typography/DocumentWrapper.test.tsx (1)

1-17: Well-structured test setup!

The test file setup is clean with appropriate environment configuration and a reusable ThemedDocumentWrapper helper component. The use of happy-dom is suitable for testing DOM styles.

@NikhilChowdhury27 NikhilChowdhury27 force-pushed the fix-issue-33017-mdx-link-underlines branch 2 times, most recently from 78e7ddf to 9085e51 Compare November 24, 2025 14:25
@NikhilChowdhury27
Copy link
Copy Markdown
Contributor Author

NikhilChowdhury27 commented Nov 24, 2025

@Sidnioulz
Hey! Really sorry about the mess in my initial submission - you were absolutely right about the missing CSS for .sbdocs classes and the PR template issue.

This is my first time contributing to Storybook and I definitely made some rookie mistakes with the screenshots. I've cleaned everything up now with all 3 CSS changes, proper tests. I really appreciate your patience and detailed feedback - once this gets merged I'll have way better context on how things work here and I promise I won't make the same mistakes again.

Thanks for taking the time to guide me through this! 🙏

@NikhilChowdhury27 NikhilChowdhury27 force-pushed the fix-issue-33017-mdx-link-underlines branch from b0610f2 to f5f627b Compare November 24, 2025 14:48
@MichaelArestad
Copy link
Copy Markdown
Contributor

Howdy! I appreciate the PR. We are still defining link styles and trying to find out exactly what is needed. I do expect to add underlines to link styles in MDX, but those underlines probably shouldn't be applied in instances where there an icon accompanying the link text. Conversely, we could remove the chevron from the Configure page.

As far as the link styles, I am leaning toward something like this:

text-decoration-thickness: 0.5px; /* may also want relative */
  text-underline-offset: 0.11em;

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
code/addons/docs/src/blocks/components/DocsPage.tsx (1)

107-132: Docs page link reset correctly underlines links while excluding .anchor

The [toGlobalSelector('a')] block now underlines links by default, and the nested &.anchor rule removes the underline for anchor icons, matching the intended a11y behavior and keeping heading anchors visually clean.

There is some inevitable duplication of link style (font size, line height, color, underline) with A.tsx and DocumentWrapper.tsx, so if you later tweak underline thickness/offset you’ll need to touch all three. If link styling continues to evolve, consider extracting a shared “docs link” style helper to keep these in sync.

code/core/src/components/components/typography/DocumentWrapper.test.tsx (1)

1-83: Accessibility underline tests for DocumentWrapper are solid

The tests cover light theme, dark theme, and multiple links, and asserting getComputedStyle(...).textDecoration contains 'underline' is a pragmatic way to verify the styling without overfitting to exact CSS serialization.

If you want a tiny cleanup, you can simplify the cleanup hook:

-  afterEach(() => {
-    cleanup();
-  });
+  afterEach(cleanup);

Otherwise this file looks good as‑is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe0534c and 0950240.

📒 Files selected for processing (6)
  • code/addons/docs/src/blocks/components/DocsPage.test.tsx (1 hunks)
  • code/addons/docs/src/blocks/components/DocsPage.tsx (2 hunks)
  • code/core/src/components/components/typography/DocumentWrapper.test.tsx (1 hunks)
  • code/core/src/components/components/typography/DocumentWrapper.tsx (2 hunks)
  • code/core/src/components/components/typography/elements/A.test.tsx (1 hunks)
  • code/core/src/components/components/typography/elements/A.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • code/addons/docs/src/blocks/components/DocsPage.test.tsx
  • code/core/src/components/components/typography/elements/A.test.tsx
  • code/core/src/components/components/typography/DocumentWrapper.tsx
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use camelCase for variable and function names

Files:

  • code/core/src/components/components/typography/elements/A.tsx
  • code/addons/docs/src/blocks/components/DocsPage.tsx
  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Enable TypeScript strict mode
Export functions from modules for testing purposes

Files:

  • code/core/src/components/components/typography/elements/A.tsx
  • code/addons/docs/src/blocks/components/DocsPage.tsx
  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
**/*.{ts,tsx,js,jsx,json,html,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx,json,html,mjs}: Use ESLint and Prettier for code style enforcement
Run 'yarn prettier --write ' to format code after making changes
Run 'yarn lint:js:cmd ' to check for ESLint issues after making changes

Files:

  • code/core/src/components/components/typography/elements/A.tsx
  • code/addons/docs/src/blocks/components/DocsPage.tsx
  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
code/**/!(*.test).{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/!(*.test).{ts,tsx,js,mjs}: Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error
Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/components/components/typography/elements/A.tsx
  • code/addons/docs/src/blocks/components/DocsPage.tsx
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{test,spec}.{ts,tsx}: Test files should follow the naming pattern *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx
Follow the spy mocking rules defined in .cursor/rules/spy-mocking.mdc for consistent mocking patterns with Vitest

Files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.test.{ts,tsx}: Write meaningful unit tests that import and call functions being tested, not just verify syntax patterns
Use 'yarn vitest run --coverage ' to run tests with coverage reports and aim for 75%+ coverage of statements/lines
Focus test coverage on all branches, conditions, edge cases, error paths, and different input variations
Use 'vi.mock()' to mock external dependencies like file system and loggers in unit tests

Files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)

**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access the mocked functions in Vitest tests
Implement mock behaviors in beforeEach blocks in Vitest tests
Mock all required dependencies that the test subject uses
Each mock implementation should return a Promise for async functions in Vitest
Mock implementations should match the expected return type of the original function
Mock all required properties and methods that the test subject uses in Vitest tests
Avoid direct function mocking without vi.mocked() in Vitest tests
Avoid mock implementations outside of beforeEach blocks in Vitest tests
Avoid mocking without the spy: true option in Vitest tests
Avoid inline mock implementations within test cases in Vitest tests
Avoid mocking only a subset of required dependencies in Vitest tests
Mock at the highest level of abstraction needed in Vitest tests
Keep mock implementations simple and focused in Vitest tests
Use type-safe mocking with vi.mocked() in Vitest tests
Document complex mock behaviors in Vitest tests
Group related mocks together in Vitest tests

Files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
🧠 Learnings (9)
📓 Common learnings
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.
📚 Learning: 2025-11-05T09:36:55.944Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.

Applied to files:

  • code/core/src/components/components/typography/elements/A.tsx
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid inline mock implementations within test cases in Vitest tests

Applied to files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Document complex mock behaviors in Vitest tests

Applied to files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Implement mock behaviors in `beforeEach` blocks in Vitest tests

Applied to files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Group related mocks together in Vitest tests

Applied to files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mock implementations outside of `beforeEach` blocks in Vitest tests

Applied to files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock all required properties and methods that the test subject uses in Vitest tests

Applied to files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
📚 Learning: 2025-11-24T17:49:59.268Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.268Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock at the highest level of abstraction needed in Vitest tests

Applied to files:

  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal
🔇 Additional comments (1)
code/core/src/components/components/typography/elements/A.tsx (1)

6-26: Default underline for links with explicit .anchor opt‑out looks good

Setting textDecoration: 'underline' on the base A component while explicitly resetting .anchor to 'none' cleanly implements “underline by default, but not on heading anchor icons.” This stays scoped to typography links and aligns with the docs/MDX behavior introduced elsewhere in the PR.

No functional issues from this change on its own.

@NikhilChowdhury27
Copy link
Copy Markdown
Contributor Author

Hey Michael!
Thanks for the feedback! Before implementing the refined styling (text-decoration-thickness: 0.5px; text-underline-offset: 0.11em), I have a concern:
If we exclude underlines for every link with an icon, we'll need ongoing manual exclusions across the codebase as new components are added.

Alternative approach:
Target contextual text links specifically instead:

.sbdocs-content p a,
.sbdocs-content li a,
.sbdocs-content td a {
  text-decoration: underline;
  text-decoration-thickness: 0.5px;
  text-underline-offset: 0.11em;
}

@MichaelArestad
Copy link
Copy Markdown
Contributor

MichaelArestad commented Nov 25, 2025

Target contextual text links specifically instead:

@NikhilChowdhury27 This is a great call and will also reduce conflicts with buttons as it is in unlikely they are in those contexts. Let's try this!

@NikhilChowdhury27
Copy link
Copy Markdown
Contributor Author

NikhilChowdhury27 commented Nov 25, 2025

Hey @MichaelArestad ,

Thanks for the feedback! I've updated the implementation based on your suggestions.

What changed:

  • Applied text-decoration: underline (0.5px thickness, 0.11em offset) to all text links across docs content and UI
  • Button-styled links (like GitHub, Documentation buttons) correctly don't have underlines since they're visually buttons, not text links
  • Footer text links (Chromatic, Storybook Community) have underlines as expected

Technical approach:

  • Updated DocsPage.tsx, DocumentWrapper.tsx, A.tsx, and link/link.tsx to apply underlines globally
  • Added specific exclusion for .anchor position markers
  • Button component keeps textDecoration: 'none' since mixing button styling with underlines would be confusing UX

Note: The "What's new?" page loads content from an external iframe (storybook.js.org), so we can't control link styling there. Those links would need to be updated on the blog side.

I'll add two screenshots showing the fixed About page for reference. All tests pass ✅

Let me know if you'd like any adjustments!
Screenshot 2025-11-25 at 8 49 32 PM
Screenshot 2025-11-25 at 8 49 48 PM

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
code/addons/docs/src/blocks/components/DocsPage.tsx (1)

117-120: DocsContent link underline behavior matches intent; confirm selector scope

The underline + thickness/offset on [toGlobalSelector('a')] combined with &.anchor { textDecoration: 'none' } mirrors DocumentWrapper and should get MDX text links over the SC 1.4.1 bar while keeping structural anchors unstyled.

Because toGlobalSelector('a') targets all non-.sb-unstyled anchors, this will also catch any remaining anchors in docs that aren’t inside unstyled blocks. If there are button-like anchors still living outside .sb-unstyled, they’ll now be underlined. It’s probably fine, but worth a quick scan of key docs pages to ensure there are no unexpected underlines on UI-ish anchors; if you spot any, you could either wrap them in sb-unstyled or narrow the selector further (e.g., to paragraph/list/table contexts).

Also applies to: 133-134

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0950240 and 2e12435.

📒 Files selected for processing (8)
  • code/addons/docs/src/blocks/components/DocsPage.test.tsx (1 hunks)
  • code/addons/docs/src/blocks/components/DocsPage.tsx (2 hunks)
  • code/core/src/components/components/Button/Button.tsx (1 hunks)
  • code/core/src/components/components/typography/DocumentWrapper.test.tsx (1 hunks)
  • code/core/src/components/components/typography/DocumentWrapper.tsx (2 hunks)
  • code/core/src/components/components/typography/elements/A.test.tsx (1 hunks)
  • code/core/src/components/components/typography/elements/A.tsx (2 hunks)
  • code/core/src/components/components/typography/link/link.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • code/addons/docs/src/blocks/components/DocsPage.test.tsx
  • code/core/src/components/components/typography/DocumentWrapper.test.tsx
  • code/core/src/components/components/typography/elements/A.tsx
  • code/core/src/components/components/typography/elements/A.test.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use camelCase for variable and function names

Files:

  • code/core/src/components/components/typography/link/link.tsx
  • code/addons/docs/src/blocks/components/DocsPage.tsx
  • code/core/src/components/components/Button/Button.tsx
  • code/core/src/components/components/typography/DocumentWrapper.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Enable TypeScript strict mode
Export functions from modules for testing purposes

Files:

  • code/core/src/components/components/typography/link/link.tsx
  • code/addons/docs/src/blocks/components/DocsPage.tsx
  • code/core/src/components/components/Button/Button.tsx
  • code/core/src/components/components/typography/DocumentWrapper.tsx
**/*.{ts,tsx,js,jsx,json,html,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx,json,html,mjs}: Use ESLint and Prettier for code style enforcement
Run 'yarn prettier --write ' to format code after making changes
Run 'yarn lint:js:cmd ' to check for ESLint issues after making changes

Files:

  • code/core/src/components/components/typography/link/link.tsx
  • code/addons/docs/src/blocks/components/DocsPage.tsx
  • code/core/src/components/components/Button/Button.tsx
  • code/core/src/components/components/typography/DocumentWrapper.tsx
code/**/!(*.test).{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/!(*.test).{ts,tsx,js,mjs}: Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error
Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/components/components/typography/link/link.tsx
  • code/addons/docs/src/blocks/components/DocsPage.tsx
  • code/core/src/components/components/Button/Button.tsx
  • code/core/src/components/components/typography/DocumentWrapper.tsx
🧠 Learnings (3)
📓 Common learnings
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.
📚 Learning: 2025-11-05T09:36:55.944Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.

Applied to files:

  • code/core/src/components/components/typography/link/link.tsx
  • code/core/src/components/components/Button/Button.tsx
  • code/core/src/components/components/typography/DocumentWrapper.tsx
📚 Learning: 2025-10-03T07:55:42.639Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/manager/components/preview/Toolbar.tsx:102-105
Timestamp: 2025-10-03T07:55:42.639Z
Learning: In code/core/src/manager/components/preview/Toolbar.tsx, we intentionally do not add aria-label/aria-labelledby to StyledToolbar (AbstractToolbar) because the enclosing section is already labeled via an sr-only heading and the toolbar is the only content. Revisit only if real user testing indicates a need.

Applied to files:

  • code/core/src/components/components/typography/DocumentWrapper.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal
🔇 Additional comments (2)
code/core/src/components/components/Button/Button.tsx (1)

319-320: Good safeguard to keep button-styled anchors non-underlined

Explicitly forcing textDecoration: 'none' on StyledButton is a nice defensive addition now that docs/global styles underline a elements. This will prevent <Button as="a"> and similar button-styled controls from accidentally looking like text links.

code/core/src/components/components/typography/DocumentWrapper.tsx (1)

67-70: Docs link underlines and anchor exception look consistent

Underlining a with controlled thickness/offset while explicitly removing decoration on a.anchor fits the SC 1.4.1 goal and preserves anchors as structural markers only. The combination with the existing &:hover a.anchor rule keeps anchors clean in all states. No issues from my side.

Also applies to: 84-85

Comment thread code/core/src/components/components/typography/link/link.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
code/core/src/components/components/typography/link/link.tsx (2)

66-73: Default underline + thickness/offset look good for SC 1.4.1

The new base underline (with textDecorationThickness: '0.5px' and textUnderlineOffset: '0.11em') is consistent with the stated a11y goal and keeps the styling localized to links. Now that every link is underlined by default, the textDecoration: 'underline' inside the nochrome hover/active block (Line 144) has become redundant; you can optionally drop that property for clarity in a follow-up.


171-181: Ensure button-styled links never get underlines, even when combined with other variants

textDecoration: 'none' on the base selector correctly removes the default underline for isButton links. However, if a caller ever combines isButton with another variant that sets textDecoration on :hover/:active (e.g., nochrome), those pseudo-classes can still reintroduce underlines.

To make button-like links robustly non-underlined in all states, you could also override the pseudo-classes in the isButton branch:

   ({ isButton }) =>
     isButton
       ? {
         border: 0,
         borderRadius: 0,
         background: 'none',
         padding: 0,
         fontSize: 'inherit',
-        // Button-styled links should not have underlines
-        textDecoration: 'none',
+        // Button-styled links should not have underlines in any state
+        textDecoration: 'none',
+        '&:hover, &:focus, &:active': {
+          textDecoration: 'none',
+        },
       }
       : {}

This keeps button variants visually consistent even if future style functions add underline rules on hover/active.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c72040 and 7f44ec0.

📒 Files selected for processing (1)
  • code/core/src/components/components/typography/link/link.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use camelCase for variable and function names

Files:

  • code/core/src/components/components/typography/link/link.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Enable TypeScript strict mode
Export functions from modules for testing purposes

Files:

  • code/core/src/components/components/typography/link/link.tsx
**/*.{ts,tsx,js,jsx,json,html,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx,json,html,mjs}: Use ESLint and Prettier for code style enforcement
Run 'yarn prettier --write ' to format code after making changes
Run 'yarn lint:js:cmd ' to check for ESLint issues after making changes

Files:

  • code/core/src/components/components/typography/link/link.tsx
code/**/!(*.test).{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/!(*.test).{ts,tsx,js,mjs}: Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error
Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/components/components/typography/link/link.tsx
🧠 Learnings (4)
📓 Common learnings
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.
📚 Learning: 2025-11-05T09:36:55.944Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.

Applied to files:

  • code/core/src/components/components/typography/link/link.tsx
📚 Learning: 2025-10-03T07:55:42.639Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/manager/components/preview/Toolbar.tsx:102-105
Timestamp: 2025-10-03T07:55:42.639Z
Learning: In code/core/src/manager/components/preview/Toolbar.tsx, we intentionally do not add aria-label/aria-labelledby to StyledToolbar (AbstractToolbar) because the enclosing section is already labeled via an sr-only heading and the toolbar is the only content. Revisit only if real user testing indicates a need.

Applied to files:

  • code/core/src/components/components/typography/link/link.tsx
📚 Learning: 2025-11-05T09:38:47.712Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Select/Select.tsx:200-204
Timestamp: 2025-11-05T09:38:47.712Z
Learning: Repo: storybookjs/storybook — Guidance: Until Storybook 11 is released, do not suggest using React.useId anywhere (e.g., in code/core/src/components/components/Select/Select.tsx) to maintain compatibility with React 17 runtimes. Prefer advising: accept a caller-provided props.id and, if needed, generate a client-only fallback id to minimize SSR hydration issues — but avoid useId. Resume prompting for useId after Storybook 11.

Applied to files:

  • code/core/src/components/components/typography/link/link.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Dec 1, 2025

View your CI Pipeline Execution ↗ for commit 7699d6f

Command Status Duration Result
nx run-many -t check --parallel=7 ✅ Succeeded 4m 53s View ↗
nx run-many -t compile --parallel=3 ✅ Succeeded 54s View ↗

☁️ Nx Cloud last updated this comment at 2025-12-04 07:27:19 UTC

Copy link
Copy Markdown
Member

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

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

Code LGTM. Let's clean up comments before merging though.

Comment thread code/addons/docs/src/blocks/components/DocsPage.tsx Outdated
Comment thread code/addons/docs/src/blocks/components/DocsPage.tsx Outdated
Comment thread code/core/src/components/components/Button/Button.tsx Outdated
Comment thread code/addons/docs/src/blocks/components/DocsPage.test.tsx Outdated
Comment thread code/core/src/components/components/typography/elements/A.tsx Outdated
Comment thread code/core/src/components/components/typography/elements/A.tsx Outdated
Comment thread code/core/src/components/components/typography/link/link.tsx Outdated
Comment thread code/core/src/components/components/typography/link/link.tsx Outdated
Comment thread code/core/src/components/components/typography/DocumentWrapper.tsx Outdated
Comment thread code/core/src/components/components/typography/DocumentWrapper.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
code/core/src/components/components/typography/elements/A.tsx (1)

11-14: WCAG-linked underline styles look solid; optional: centralize shared underline tokens

The inline WCAG F73 reference plus textDecoration / textDecorationThickness / textUnderlineOffset combo, along with the .anchor exception, clearly express the intended a11y behavior and match the PR goal. If these exact underline values are reused in Link/DocumentWrapper, consider extracting them into a shared constant or helper to keep future tweaks in sync across typography components.

Also applies to: 27-28

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f44ec0 and 24391c5.

📒 Files selected for processing (2)
  • code/addons/docs/src/blocks/components/DocsPage.tsx (2 hunks)
  • code/core/src/components/components/typography/elements/A.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/addons/docs/src/blocks/components/DocsPage.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ESLint and Prettier configurations that are enforced in the codebase

Files:

  • code/core/src/components/components/typography/elements/A.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Enable TypeScript strict mode

Files:

  • code/core/src/components/components/typography/elements/A.tsx
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{ts,tsx,js,jsx,mjs}: Use server-side logger from 'storybook/internal/node-logger' for Node.js code
Use client-side logger from 'storybook/internal/client-logger' for browser code
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/components/components/typography/elements/A.tsx
code/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions that need to be tested from their modules

Files:

  • code/core/src/components/components/typography/elements/A.tsx
code/**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier with --write flag to format code before committing
Run ESLint with yarn lint:js:cmd to check for linting issues and fix errors before committing

Files:

  • code/core/src/components/components/typography/elements/A.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.
📚 Learning: 2025-11-05T09:36:55.944Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.

Applied to files:

  • code/core/src/components/components/typography/elements/A.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal

@NikhilChowdhury27
Copy link
Copy Markdown
Contributor Author

@Sidnioulz Thanks for your feedbacks, I have resolved, please see if we can merge this pr.

Also going forward I will keep in mind all the comments you have given

Comment thread code/core/src/components/components/typography/DocumentWrapper.tsx
Comment thread code/core/src/components/components/typography/elements/A.tsx
Comment thread code/core/src/components/components/typography/link/link.tsx Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Links in MDX should have an underline styling by default

6 participants