Skip to content

Conversation

@Sidnioulz
Copy link
Member

@Sidnioulz Sidnioulz commented Jan 2, 2026

Closes #32271

What I did

Peek.2026-01-09.19-27.webm
  • Aligned site structure with feedback from accessibility experts who audited our recent a11y changes
  • Applied landmarks to
  • Sidebar (header element, banner role, was previously a nav)
  • Search (search role)
  • Explorer (nav role)
  • Preview (main role)
  • Toolbar (region role)
  • Addon panel (aside role)
  • In mobile, applied header/banner to the MobileNavigation bottom bar
  • Added global keyboard shortcuts for landmark nav (F6 / Shift+F6) based on React Aria, so that everyone can keyboard nav like a pro
  • Added focus outline specific to landmark regions so they can be spotted (reviewed with @MichaelArestad)
  • Fixed focus outline visibility on preview region
  • Fix issue with nav being unreachable when Search is focused
  • Fix preview iframe eating up F6 key events (can't find a way)

Still gotta handle a few focus / keyboard handling edge cases.

Checklist for Contributors

Testing

Not tested

Manual testing

With screen reader
  1. Run Storybook with NVDA or VoiceOver on
  2. Use your screen reader's landmark navigation shortcuts (NVDA: D and Shift+D, when in browse mode)
  3. NVDA will show a decorative outline (NVDA baked-in feature), VoiceOver won't
Without screen reader
  1. Run Storybook in your favourite browser
  2. Press F6 and Shift+F6 to quick nav between landmarks, using React Aria's hook
  3. Notice the highlight that helps you spot in which region you are
  4. Notice the quick nav remembers which element of the landmark region you were last on, and returns to that element
  5. This quick nav also does not suffer from limitations between browse mode and focus mode in screen readers

Limitations

  • Can't quick nav from within the preview iframe with React Aria shortcut
  • Can't change the keyboard shortcut, it is hardcoded by React Aria, so we can't show it in the shortcuts UI for it to be customised
  • Quicknav feature is undocumented at the moment

Documentation

@kylegach @jonniebigodes @MichaelArestad I'd like to get your opinions on how to make this feature discoverable to users. Should we add it in the shortcuts page, even though it can't be edited, just for visibility? Is there a best page to document it in the docs website?

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>

Summary by CodeRabbit

  • New Features

    • Added accessibility landmarks for keyboard navigation (F6 key) with visual border highlighting.
    • Enhanced semantic HTML structure across the interface for better accessibility.
    • Added support for custom outline properties in Card component.
  • Bug Fixes

    • Updated skip link labels for improved clarity.
  • Style

    • Added global styling for landmark navigation indicators.

✏️ Tip: You can customize this high-level summary in your review settings.

@nx-cloud
Copy link

nx-cloud bot commented Jan 6, 2026

View your CI Pipeline Execution ↗ for commit 7feac0b

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ⛔ Cancelled 8m 14s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-09 18:44:29 UTC

@Sidnioulz Sidnioulz force-pushed the sidnioulz/issue-32271 branch from 3b6c464 to aaf875d Compare January 9, 2026 13:56
@Sidnioulz Sidnioulz force-pushed the sidnioulz/issue-32271 branch from aaf875d to e37e586 Compare January 9, 2026 17:49
@Sidnioulz Sidnioulz changed the title WIP LANDMARKS UI: Improve landmark navigation Jan 9, 2026
@Sidnioulz Sidnioulz added feature request accessibility ci:normal a11y: keyboard Accessibility issues related to keyboard navigation or shortcuts labels Jan 9, 2026
@Sidnioulz Sidnioulz marked this pull request as ready for review January 9, 2026 18:35
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Adds accessibility landmark navigation via new @react-aria/landmark dependency. Introduces useLandmark and useLandmarkIndicator hooks to manage semantic regions across manager UI components. Integrates landmarks into sidebar, panel, preview, and toolbar. Updates Card with outlineProps prop. Refactors Search render-props API and updates skip-link labels.

Changes

Cohort / File(s) Change Summary
Dependency Management
package.json
Added @react-aria/landmark v^3.0.8 to dependencies and devDependencies
New Landmark Hooks
src/manager/hooks/useLandmark.ts, src/manager/components/layout/useLandmarkIndicator.ts
Created wrapper hook useLandmark() around upstream react-aria landmark; created useLandmarkIndicator() to listen for F6 key, find parent landmarks via data-sb-landmark attribute, and animate border highlight (2px solid) using theme primary color for 1.5s
Sidebar Components - Landmark Integration
src/manager/components/sidebar/Explorer.tsx, src/manager/components/sidebar/Sidebar.tsx, src/manager/components/sidebar/Search.tsx, src/manager/components/sidebar/TestingWidget.tsx
Added useLandmark integration with landmark props and hidden heading labels; changed container elements from div/nav to semantic elements (nav, header); updated render logic to pass navigation visibility state
Sidebar Components - Skip Links & Labels
src/manager/components/sidebar/Heading.tsx, src/manager/components/sidebar/Heading.stories.tsx, src/manager/components/sidebar/Tree.tsx, src/manager/components/sidebar/Tree.stories.tsx
Updated skip-link text from "Skip to canvas" to "Skip to content"; removed IconSymbols from Tree container; added IconSymbols import to story decorators
Sidebar Type Definitions
src/manager/components/sidebar/types.ts
Replaced isBrowsing boolean flag in SearchChildrenFn render-props with isNavVisible, isNavRendered, and isSearchResultRendered properties
Sidebar Stories Cleanup
src/manager/components/sidebar/Sidebar.stories.tsx
Removed IconSymbols component usage from decorators (3 lines)
Panel & MobileNavigation - Landmark Integration
src/manager/components/panel/Panel.tsx, src/manager/components/mobile/navigation/MobileNavigation.tsx
Added useLandmark hook and landmark props integration; changed Panel Section to styled aside element; changed MobileNavigation to semantic section and header elements with landmark props
Preview Components - Landmark Integration
src/manager/components/preview/Preview.tsx, src/manager/components/preview/Toolbar.tsx, src/manager/components/preview/utils/components.ts
Added useLandmark integration to Preview (main) and Toolbar (region); replaced static aria-labelledby with dynamic landmark props; refactored PreviewContainer from styled.main to styled.div and FrameWrap from styled.section to styled.main
Preview - Accessibility
src/manager/components/preview/FramesRenderer.tsx
Removed title attribute from skip-to-sidebar anchor element
Card Component Enhancement
src/components/components/Card/Card.tsx
Added DOMAttributes import from React; extended CardProps with outlineProps?: DOMAttributes<HTMLDivElement> and forwarded to CardOutline element
Layout Manager
src/manager/components/layout/Layout.tsx
Added useLandmarkIndicator() invocation to install landmark navigation listener
Global Styling
src/theming/global.ts
Added CSS rules for [data-sb-landmark] elements: position container, suppress focus outlines, render 2px color border on focus-visible with ::after pseudo-element using theme primary color

Sequence Diagram

sequenceDiagram
    participant User as User
    participant KeyListener as Keyboard Listener<br/>(useLandmarkIndicator)
    participant DOM as DOM Tree
    participant Animation as Web Animations API
    participant Theme as Theme System

    User->>KeyListener: Press F6
    KeyListener->>KeyListener: Capture keydown event
    KeyListener->>DOM: Search up from activeElement<br/>for [data-sb-landmark]
    DOM-->>KeyListener: Return parent landmark element
    alt Landmark Found
        KeyListener->>Animation: Cancel existing animation<br/>on landmark
        KeyListener->>Theme: Fetch theme.color.primary
        Theme-->>KeyListener: Return primary color
        KeyListener->>Animation: Start border highlight animation<br/>(2px solid, 1.5s duration)<br/>on ::after pseudo-element
        Animation->>DOM: Render border highlight<br/>on landmark
        Animation->>Animation: Wait 1.5s
        Animation->>DOM: Remove animation, cleanup state
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • [A11y]: Sidebar is not accessible #31267 — PR directly addresses sidebar accessibility enhancements by implementing ARIA landmark navigation and integrating accessibility hooks across sidebar components.

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link
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: 2

🤖 Fix all issues with AI agents
In @code/core/src/manager/components/sidebar/Explorer.tsx:
- Line 27: Update the ref type for containerRef to match the rendered element:
change useRef<HTMLDivElement>(null) to useRef<HTMLElement | null>(null)
(referencing the containerRef variable in Explorer.tsx) so the ref can point to
the <nav> element and satisfy type safety wherever containerRef.current is used.

In @code/core/src/manager/components/sidebar/TestingWidget.tsx:
- Around line 257-264: The hooks cardRef = useRef<HTMLDivElement>(null) and
const { landmarkProps } = useLandmark(...) are being invoked after an early
return, violating the Rules of Hooks; move those hook calls (cardRef and
useLandmark invocation that provides landmarkProps) to execute unconditionally
before any early return or conditional logic in the TestingWidget component so
they run in the same order on every render, then use landmarkProps and cardRef
where needed after the early-return check.
🧹 Nitpick comments (5)
code/core/src/manager/components/sidebar/Heading.stories.tsx (1)

237-240: Text update correctly reflects the new skip link label.

The change from "Skip to canvas" to "Skip to content" aligns with the PR's accessibility improvements. The play function will correctly focus the updated skip link elements for Chromatic testing.

Optional: Consider renaming the story export for consistency.

The story is named SkipToCanvasLinkFocused (line 229) but now targets "Skip to content" text. For clarity, you might rename it to SkipToContentLinkFocused, though story names primarily serve as identifiers and the current name is still functional.

code/core/src/components/components/Card/Card.tsx (1)

100-107: Consider placing ref after outlineProps spread.

While DOMAttributes doesn't include ref (TypeScript prevents passing it), React convention places ref last to prevent accidental override and provide defense in depth.

♻️ Suggested prop order
-      <CardOutline animation={outlineAnimation} color={outlineColor} ref={ref} {...outlineProps}>
+      <CardOutline animation={outlineAnimation} color={outlineColor} {...outlineProps} ref={ref}>
code/core/src/manager/components/preview/FramesRenderer.tsx (1)

99-103: Removing the anchor title here is fine; consider dropping redundant tabIndex={0}.

Since the <a> has an href, it’s already tabbable; keeping tabIndex={0} is harmless but unnecessary.

code/core/src/manager/components/layout/useLandmarkIndicator.ts (1)

37-43: Consider browser compatibility for the Animation API.

The Web Animations API is well-supported in modern browsers but may fail in older environments. Consider adding a try-catch or feature detection for maximum robustness.

♻️ Optional defensive coding enhancement
-         const animation = landmarkElement.animate(
-           [{ border: `2px solid ${theme.color.primary}` }, { border: `2px solid transparent` }],
-           {
-             duration: 1500,
-             pseudoElement: '::after',
-           }
-         );
+         try {
+           const animation = landmarkElement.animate(
+             [{ border: `2px solid ${theme.color.primary}` }, { border: `2px solid transparent` }],
+             {
+               duration: 1500,
+               pseudoElement: '::after',
+             }
+           );
 
-         currentAnimationRef.current = animation;
+           currentAnimationRef.current = animation;
 
-         animation.onfinish = () => {
-           currentAnimationRef.current = null;
-         };
+           animation.onfinish = () => {
+             currentAnimationRef.current = null;
+           };
+         } catch (e) {
+           // Animation API not supported or failed, silently continue
+         }
code/core/src/manager/components/sidebar/Search.tsx (1)

316-322: Consider adding an explicit label to the search landmark.

The search landmark lacks an aria-label or aria-labelledby attribute. While the input field has an associated label, the landmark region itself would benefit from explicit labeling for better accessibility.

💡 Suggested enhancement
  const searchLandmarkRef = useRef<HTMLDivElement>(null);
  const { landmarkProps } = useLandmark(
    {
+     'aria-label': 'Search components',
      role: 'search',
    },
    searchLandmarkRef
  );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9511a9c and 7feac0b.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (22)
  • code/core/package.json
  • code/core/src/components/components/Card/Card.tsx
  • code/core/src/manager/components/layout/Layout.tsx
  • code/core/src/manager/components/layout/useLandmarkIndicator.ts
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/panel/Panel.tsx
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/preview/utils/components.ts
  • code/core/src/manager/components/sidebar/Explorer.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/manager/components/sidebar/Heading.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/sidebar/Sidebar.stories.tsx
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/sidebar/Tree.tsx
  • code/core/src/manager/components/sidebar/types.ts
  • code/core/src/manager/hooks/useLandmark.ts
  • code/core/src/theming/global.ts
💤 Files with no reviewable changes (1)
  • code/core/src/manager/components/sidebar/Sidebar.stories.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx,json,md,html,css,scss}

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

Format code using Prettier with yarn prettier --write <file>

Files:

  • code/core/src/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Explorer.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/manager/hooks/useLandmark.ts
  • code/core/src/components/components/Card/Card.tsx
  • code/core/src/manager/components/preview/utils/components.ts
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/sidebar/Heading.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/sidebar/Tree.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/sidebar/types.ts
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/src/manager/components/panel/Panel.tsx
  • code/core/package.json
  • code/core/src/manager/components/layout/useLandmarkIndicator.ts
  • code/core/src/manager/components/layout/Layout.tsx
  • code/core/src/theming/global.ts
**/*.{js,jsx,json,html,ts,tsx,mjs}

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

Run ESLint checks using yarn lint:js:cmd <file> or the full command cross-env NODE_ENV=production eslint --cache --cache-location=../.cache/eslint --ext .js,.jsx,.json,.html,.ts,.tsx,.mjs --report-unused-disable-directives to fix linting errors before committing

Files:

  • code/core/src/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Explorer.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/manager/hooks/useLandmark.ts
  • code/core/src/components/components/Card/Card.tsx
  • code/core/src/manager/components/preview/utils/components.ts
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/sidebar/Heading.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/sidebar/Tree.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/sidebar/types.ts
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/src/manager/components/panel/Panel.tsx
  • code/core/package.json
  • code/core/src/manager/components/layout/useLandmarkIndicator.ts
  • code/core/src/manager/components/layout/Layout.tsx
  • code/core/src/theming/global.ts
**/*.{ts,tsx}

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

Enable TypeScript strict mode across all packages

Files:

  • code/core/src/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Explorer.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/manager/hooks/useLandmark.ts
  • code/core/src/components/components/Card/Card.tsx
  • code/core/src/manager/components/preview/utils/components.ts
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/sidebar/Heading.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/sidebar/Tree.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/sidebar/types.ts
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/src/manager/components/panel/Panel.tsx
  • code/core/src/manager/components/layout/useLandmarkIndicator.ts
  • code/core/src/manager/components/layout/Layout.tsx
  • code/core/src/theming/global.ts
**/*.{ts,tsx,js,jsx}

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

**/*.{ts,tsx,js,jsx}: Export functions from modules if they need to be tested
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/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Explorer.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/manager/hooks/useLandmark.ts
  • code/core/src/components/components/Card/Card.tsx
  • code/core/src/manager/components/preview/utils/components.ts
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/sidebar/Heading.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/sidebar/Tree.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/sidebar/types.ts
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/src/manager/components/panel/Panel.tsx
  • code/core/src/manager/components/layout/useLandmarkIndicator.ts
  • code/core/src/manager/components/layout/Layout.tsx
  • code/core/src/theming/global.ts
code/{core,lib,addons,builders,frameworks,presets}/**/*.{ts,tsx,js,jsx}

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

Use logger from storybook/internal/node-logger for server-side logging in Node.js code

Files:

  • code/core/src/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Explorer.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/manager/hooks/useLandmark.ts
  • code/core/src/components/components/Card/Card.tsx
  • code/core/src/manager/components/preview/utils/components.ts
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/sidebar/Heading.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/sidebar/Tree.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/sidebar/types.ts
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/src/manager/components/panel/Panel.tsx
  • code/core/src/manager/components/layout/useLandmarkIndicator.ts
  • code/core/src/manager/components/layout/Layout.tsx
  • code/core/src/theming/global.ts
🧠 Learnings (13)
📓 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.
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.
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32594
File: code/core/src/components/components/Popover/WithPopover.tsx:7-9
Timestamp: 2025-10-01T15:24:01.060Z
Learning: In the Storybook repository, "react-aria-components/patched-dist/*" (e.g., "react-aria-components/patched-dist/Dialog", "react-aria-components/patched-dist/Popover", "react-aria-components/patched-dist/Tooltip") are valid import paths created by a patch applied to the react-aria-components package. These imports should not be flagged as broken or invalid until a maintainer explicitly states they are no longer acceptable.
📚 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/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Explorer.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/manager/hooks/useLandmark.ts
  • code/core/src/components/components/Card/Card.tsx
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/sidebar/Heading.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/sidebar/Tree.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/package.json
  • code/core/src/theming/global.ts
📚 Learning: 2025-11-05T09:37:25.920Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/tooltip/WithTooltip.tsx:54-96
Timestamp: 2025-11-05T09:37:25.920Z
Learning: Repo: storybookjs/storybook — In code/core/src/components/components/tooltip/WithTooltip.tsx, the legacy WithTooltip implementation is intentionally reintroduced for backward compatibility and is deprecated; maintainers (per Sidnioulz) do not want maintenance or improvements on it. Prefer WithTooltipNew/Popover; avoid suggesting changes to WithTooltip.* going forward.

Applied to files:

  • code/core/src/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/components/components/Card/Card.tsx
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/preview/Preview.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/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Explorer.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/manager/components/preview/utils/components.ts
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/sidebar/Heading.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/sidebar/Tree.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/src/manager/components/panel/Panel.tsx
  • code/core/src/theming/global.ts
📚 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/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/sidebar/Explorer.tsx
  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/components/components/Card/Card.tsx
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/src/manager/components/panel/Panel.tsx
  • code/core/src/manager/components/layout/useLandmarkIndicator.ts
📚 Learning: 2025-09-18T20:51:06.618Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/viewport/components/Tool.tsx:38-39
Timestamp: 2025-09-18T20:51:06.618Z
Learning: In viewport tool code, when using the `useGlobals` hook from storybook/manager-api, the third returned value `storyGlobals` is guaranteed by TypeScript to be defined (not undefined/null), making the `in` operator safe to use without additional null checks.

Applied to files:

  • code/core/src/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/preview/Preview.tsx
  • code/core/src/manager/components/panel/Panel.tsx
📚 Learning: 2025-09-18T20:51:06.618Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/viewport/components/Tool.tsx:38-39
Timestamp: 2025-09-18T20:51:06.618Z
Learning: The useGlobals hook from storybook/manager-api returns a tuple where the third element (storyGlobals) is typed as Globals, not Globals | undefined. This means TypeScript guarantees it's always defined, making the `in` operator safe to use without additional null checks.

Applied to files:

  • code/core/src/manager/components/sidebar/TestingWidget.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
  • code/core/src/manager/components/panel/Panel.tsx
📚 Learning: 2025-09-24T09:39:39.233Z
Learnt from: ndelangen
Repo: storybookjs/storybook PR: 32507
File: code/core/src/manager/globals/globals-module-info.ts:25-33
Timestamp: 2025-09-24T09:39:39.233Z
Learning: In Storybook, storybook/actions/decorator is a preview-only entrypoint and should not be included in manager globals configuration. The duplicatedKeys array in code/core/src/manager/globals/globals-module-info.ts is specifically for manager-side externalization, not preview entrypoints.

Applied to files:

  • code/core/src/manager/components/sidebar/Heading.stories.tsx
  • code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx
  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/src/manager/components/preview/Toolbar.tsx
  • code/core/src/manager/components/preview/FramesRenderer.tsx
  • code/core/src/manager/components/sidebar/Search.tsx
📚 Learning: 2025-10-01T15:24:01.060Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32594
File: code/core/src/components/components/Popover/WithPopover.tsx:7-9
Timestamp: 2025-10-01T15:24:01.060Z
Learning: In the Storybook repository, "react-aria-components/patched-dist/*" (e.g., "react-aria-components/patched-dist/Dialog", "react-aria-components/patched-dist/Popover", "react-aria-components/patched-dist/Tooltip") are valid import paths created by a patch applied to the react-aria-components package. These imports should not be flagged as broken or invalid until a maintainer explicitly states they are no longer acceptable.

Applied to files:

  • code/core/src/manager/components/sidebar/Tree.stories.tsx
  • code/core/package.json
📚 Learning: 2025-12-22T22:03:40.123Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T22:03:40.123Z
Learning: Applies to code/{renderers}/**/*.{ts,tsx,js,jsx} : Use `logger` from `storybook/internal/client-logger` for client-side logging in browser code

Applied to files:

  • code/core/src/manager/components/sidebar/Tree.stories.tsx
📚 Learning: 2025-12-22T22:03:40.123Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T22:03:40.123Z
Learning: Applies to code/{core,lib,addons,builders,frameworks,presets}/**/*.{ts,tsx,js,jsx} : Use `logger` from `storybook/internal/node-logger` for server-side logging in Node.js code

Applied to files:

  • code/core/src/manager/components/sidebar/Tree.stories.tsx
📚 Learning: 2025-11-24T17:49:59.279Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-11-24T17:49:59.279Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock all required dependencies that the test subject uses

Applied to files:

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

Applied to files:

  • code/core/src/manager/components/sidebar/Tree.stories.tsx
🧬 Code graph analysis (13)
code/core/src/manager/components/sidebar/TestingWidget.tsx (2)
code/core/src/preview-api/index.ts (1)
  • useRef (13-13)
code/core/src/manager/hooks/useLandmark.ts (1)
  • useLandmark (10-22)
code/core/src/manager/components/sidebar/Explorer.tsx (1)
code/core/src/manager/hooks/useLandmark.ts (1)
  • useLandmark (10-22)
code/core/src/components/components/Card/Card.tsx (1)
code/core/src/components/index.ts (1)
  • Card (46-46)
code/core/src/manager/components/sidebar/Sidebar.tsx (4)
code/core/src/preview-api/index.ts (1)
  • useRef (13-13)
code/core/src/manager/hooks/useLandmark.ts (1)
  • useLandmark (10-22)
code/core/src/manager/components/sidebar/Explorer.tsx (1)
  • Explorer (19-72)
code/core/src/manager/components/sidebar/SearchResults.tsx (1)
  • SearchResults (224-345)
code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx (2)
code/core/src/preview-api/index.ts (1)
  • useRef (13-13)
code/core/src/manager/hooks/useLandmark.ts (1)
  • useLandmark (10-22)
code/core/src/manager/components/sidebar/Tree.stories.tsx (1)
code/core/src/manager-api/root.tsx (1)
  • ManagerContext (79-79)
code/core/src/manager/components/preview/Toolbar.tsx (2)
code/core/src/preview-api/index.ts (1)
  • useRef (13-13)
code/core/src/manager/hooks/useLandmark.ts (1)
  • useLandmark (10-22)
code/core/src/manager/components/sidebar/Search.tsx (1)
code/core/src/manager/hooks/useLandmark.ts (1)
  • useLandmark (10-22)
code/core/src/manager/components/preview/Preview.tsx (1)
code/core/src/preview-api/index.ts (1)
  • useRef (13-13)
code/core/src/manager/components/panel/Panel.tsx (2)
code/core/src/preview-api/index.ts (1)
  • useRef (13-13)
code/core/src/manager/hooks/useLandmark.ts (1)
  • useLandmark (10-22)
code/core/src/manager/components/layout/useLandmarkIndicator.ts (2)
code/core/src/theming/index.ts (1)
  • useTheme (18-18)
code/core/src/preview-api/index.ts (2)
  • useRef (13-13)
  • useEffect (8-8)
code/core/src/manager/components/layout/Layout.tsx (1)
code/core/src/manager/components/layout/useLandmarkIndicator.ts (1)
  • useLandmarkIndicator (9-59)
code/core/src/theming/global.ts (1)
code/core/src/theming/base.ts (1)
  • color (1-42)
🪛 Biome (2.1.2)
code/core/src/manager/components/sidebar/TestingWidget.tsx

[error] 257-257: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 258-258: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ 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). (4)
  • GitHub Check: normal
  • GitHub Check: nx
  • GitHub Check: nx
  • GitHub Check: nx
🔇 Additional comments (28)
code/core/src/components/components/Card/Card.tsx (2)

1-1: LGTM: Import addition is appropriate.

The DOMAttributes import is necessary for typing the new outlineProps and is correctly imported from React.


95-95: LGTM: Type definition is appropriate.

The outlineProps type correctly uses DOMAttributes<HTMLDivElement> to accept standard DOM attributes, ARIA attributes, and event handlers for the outline element.

code/core/src/manager/components/sidebar/Heading.tsx (1)

90-96: Skip link text change looks good and consistent with the new landmark framing.

ariaLabel={false} is the right choice here since the anchor text provides the accessible name (as per learnings).

code/core/src/manager/components/sidebar/Tree.stories.tsx (2)

15-17: Story decorator update to include IconSymbols matches the component-level change.

This keeps the Tree stories self-contained now that Tree no longer injects the SVG symbols.

Also applies to: 79-85


277-285: Updated play assertion for “Skip to content” matches the new label.

code/core/src/manager/components/sidebar/Tree.tsx (2)

304-306: “Skip to content” label updates are consistent across node types.

Also: using ariaLabel={false} is correct since the link text is the accessible name (as per learnings).

Also applies to: 445-447, 503-505


753-755: No action needed — IconSymbols is properly mounted in the parent Sidebar component.

The verification confirms that <IconSymbols /> is rendered at line 156 of Sidebar.tsx, which is the parent component of Tree. This global mount ensures that symbol-based icons (via <UseSymbol />) remain functional across all child components, including those that depend on it like TreeNode, ContextMenu, and SearchResults. The refactoring correctly moved the mount to the appropriate parent level.

code/core/package.json (1)

228-235: No action needed. @react-aria/landmark is correctly placed in devDependencies because it is bundled into the manager output, following Storybook's established pattern for bundled UI component libraries (similar to open and fast-printf). All @react-aria/* packages are grouped in devDependencies and are not listed in the esbuild global externals configuration, confirming they are bundled rather than externalized.

code/core/src/theming/global.ts (1)

137-155: LGTM! Landmark focus styling implementation is sound.

The landmark focus indication using ::after pseudo-element is well-executed:

  • Avoids layout shift by using an overlay approach
  • Properly isolates pointer events
  • Uses theme primary color for brand consistency

Per the PR objectives, ensure this styling has been tested in high-contrast mode to verify the focus outline remains visible.

code/core/src/manager/components/sidebar/types.ts (1)

54-66: LGTM! Improved state granularity for accessibility.

The replacement of isBrowsing with three distinct flags provides clearer semantics and enables better accessibility control. The inline documentation effectively explains each flag's purpose and the distinction between visibility and DOM presence is crucial for maintaining keyboard navigation (per PR objectives).

code/core/src/manager/components/layout/Layout.tsx (1)

162-163: LGTM! Landmark indicator integration is correctly placed.

The useLandmarkIndicator() hook is appropriately called in the root Layout component, which serves as the parent container for all landmarks. The hook's implementation (per code snippets) properly manages the F6 keydown listener lifecycle with cleanup, and the animation mechanism prevents concurrent highlights.

The PR acknowledges the known limitation where the preview iframe can intercept F6 events, preventing navigation from within rendered stories—an acceptable trade-off given iframe constraints.

code/core/src/manager/components/preview/utils/components.ts (1)

5-5: LGTM! Landmark element restructuring is semantically correct.

Moving the main landmark from PreviewContainer to FrameWrap properly aligns semantic roles with actual content:

  • PreviewContainer as div: appropriate for layout wrapper
  • FrameWrap as main: correct for the primary preview content region

This change aligns with the PR objective of marking Preview as the main landmark. The useLandmark hook properly integrates the landmark with React Aria, registering the region with appropriate ARIA attributes (aria-labelledby and role='main').

Verification confirms only one <main> landmark exists in the manager components (the FrameWrap in Preview), with no conflicting landmarks across the codebase.

code/core/src/manager/hooks/useLandmark.ts (1)

1-22: LGTM! Clean wrapper implementation.

The hook properly wraps React Aria's useLandmark and augments it with the Storybook-specific data-sb-landmark attribute that enables the focus styling defined in global.ts. The implementation is minimal, type-safe, and actively used across eight manager components (Preview, Toolbar, Panel, Sidebar, Search, Explorer, TestingWidget, and MobileNavigation) with consistent patterns.

code/core/src/manager/components/preview/Preview.tsx (2)

115-119: LGTM! Landmark integration correctly implemented.

The landmark hook is properly configured with role='main' and references the visually-hidden heading, aligning with the PR objectives to mark the Preview as the main landmark region.


138-138: LGTM! Landmark props correctly applied.

The ref and landmarkProps are properly spread onto the FrameWrap element, enabling both landmark navigation and visual highlighting.

code/core/src/manager/components/preview/Toolbar.tsx (2)

87-94: LGTM! Landmark integration correctly implemented.

The landmark hook properly configures the toolbar as a region with appropriate labeling via the visually-hidden heading.


101-102: LGTM! Landmark props correctly applied.

The ref and landmarkProps are properly spread onto the StyledSection, enabling landmark navigation and visual highlighting.

code/core/src/manager/components/panel/Panel.tsx (2)

156-160: Verify ARIA role matches semantic intent.

The <aside> element has an implicit ARIA role of complementary, but the code explicitly sets role='region', overriding this. The PR objectives specify marking the "Addon panel as aside", suggesting the complementary role should be used.

Consider using role='complementary' to align with the semantic <aside> element, or omit the role attribute entirely to use the implicit role:

♻️ Proposed fix
  const { landmarkProps } = useLandmark(
-   { 'aria-labelledby': 'storybook-panel-heading', role: 'region' },
+   { 'aria-labelledby': 'storybook-panel-heading', role: 'complementary' },
    asideRef
  );

Alternatively, if the React Aria landmark hook properly handles implicit roles:

  const { landmarkProps } = useLandmark(
-   { 'aria-labelledby': 'storybook-panel-heading', role: 'region' },
+   { 'aria-labelledby': 'storybook-panel-heading' },
    asideRef
  );

163-163: LGTM! Landmark props correctly applied.

The ref and landmarkProps are properly spread onto the Aside element, assuming the role mismatch is addressed.

code/core/src/manager/components/layout/useLandmarkIndicator.ts (1)

9-58: LGTM! Landmark indicator implementation is sound.

The F6 keydown handler correctly traverses the DOM to locate landmark regions and applies a visual border animation. The cleanup and dependency on theme.color.primary are appropriate.

Note: The PR objectives mention a known limitation where the preview iframe intercepts F6 events, preventing landmark navigation from within the iframe. This is an expected constraint.

code/core/src/manager/components/sidebar/TestingWidget.tsx (1)

274-279: LGTM! Landmark props correctly applied.

The ref and outlineProps with landmarkProps are properly applied to the HoverCard, and the visually-hidden heading provides appropriate labeling. This will work correctly once the hook placement issue is resolved.

code/core/src/manager/components/mobile/navigation/MobileNavigation.tsx (2)

81-88: LGTM: Landmark integration correctly implemented.

The banner landmark is properly configured with aria-labelledby referencing the "Navigation controls" heading. The ref type matches the header element.


145-174: LGTM: Semantic element updates align with landmark strategy.

Changing Container to styled.section and MobileBottomBar to styled.header provides the appropriate semantic structure for the banner landmark.

code/core/src/manager/components/sidebar/Explorer.tsx (1)

37-56: LGTM: Navigation landmark properly configured.

The landmark is correctly labeled via the "Stories" heading, and the semantic nav element with role="navigation" provides appropriate structure for assistive technologies.

code/core/src/manager/components/sidebar/Search.tsx (1)

445-447: LGTM: Render props API updated to support landmark-aware navigation.

The new isNavVisible, isNavRendered, and isSearchResultRendered flags provide clearer semantics for controlling visibility and rendering of navigation elements, aligning with the broader landmark navigation strategy.

code/core/src/manager/components/sidebar/Sidebar.tsx (3)

142-156: LGTM: Banner landmark and document structure correctly implemented.

The addition of a visually-hidden h1 for "Storybook" and the banner landmark on the header element provides proper document structure and navigation semantics. The IconSymbols component placement is appropriate.


213-222: Verify screen reader behavior with conditionally hidden Explorer.

When isNavVisible is false, the Explorer remains in the DOM with sb-sr-only class applied. Confirm that this approach doesn't cause screen readers to announce hidden navigation content while users are interacting with search results.

This pattern is likely intentional to preserve keyboard navigation state, but validation with actual screen reader testing would be beneficial to ensure it doesn't create confusion for AT users.


31-47: LGTM: Semantic container updated to header for banner landmark.

The change from styled.div to styled.header provides the appropriate semantic foundation for the banner landmark while preserving all styling.

selected,
...restProps
}) {
const containerRef = useRef<HTMLDivElement>(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix type mismatch for containerRef.

The ref is typed as HTMLDivElement but Line 46 shows the element is now a nav. Update to HTMLElement for type safety.

🔧 Proposed fix
-  const containerRef = useRef<HTMLDivElement>(null);
+  const containerRef = useRef<HTMLElement>(null);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const containerRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLElement>(null);
🤖 Prompt for AI Agents
In @code/core/src/manager/components/sidebar/Explorer.tsx at line 27, Update the
ref type for containerRef to match the rendered element: change
useRef<HTMLDivElement>(null) to useRef<HTMLElement | null>(null) (referencing
the containerRef variable in Explorer.tsx) so the ref can point to the <nav>
element and satisfy type safety wherever containerRef.current is used.

Comment on lines +257 to +264
const cardRef = useRef<HTMLDivElement>(null);
const { landmarkProps } = useLandmark(
{
'aria-labelledby': 'storybook-testing-widget-heading',
role: 'region',
},
cardRef
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Hooks called conditionally after early return.

Lines 257-264 call useRef and useLandmark hooks after an early return on line 253. This violates the Rules of Hooks, which require hooks to be called unconditionally and in the same order on every render. This can cause React state corruption and runtime errors.

🐛 Proposed fix: Move hooks before early return
+  const cardRef = useRef<HTMLDivElement>(null);
+  const { landmarkProps } = useLandmark(
+    {
+      'aria-labelledby': 'storybook-testing-widget-heading',
+      role: 'region',
+    },
+    cardRef
+  );
+
   if (!hasTestProviders && !errorCount && !warningCount) {
     return null;
   }
 
-  const cardRef = useRef<HTMLDivElement>(null);
-  const { landmarkProps } = useLandmark(
-    {
-      'aria-labelledby': 'storybook-testing-widget-heading',
-      role: 'region',
-    },
-    cardRef
-  );
-
   return (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const cardRef = useRef<HTMLDivElement>(null);
const { landmarkProps } = useLandmark(
{
'aria-labelledby': 'storybook-testing-widget-heading',
role: 'region',
},
cardRef
);
const cardRef = useRef<HTMLDivElement>(null);
const { landmarkProps } = useLandmark(
{
'aria-labelledby': 'storybook-testing-widget-heading',
role: 'region',
},
cardRef
);
if (!hasTestProviders && !errorCount && !warningCount) {
return null;
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 257-257: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 258-258: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🤖 Prompt for AI Agents
In @code/core/src/manager/components/sidebar/TestingWidget.tsx around lines 257
- 264, The hooks cardRef = useRef<HTMLDivElement>(null) and const {
landmarkProps } = useLandmark(...) are being invoked after an early return,
violating the Rules of Hooks; move those hook calls (cardRef and useLandmark
invocation that provides landmarkProps) to execute unconditionally before any
early return or conditional logic in the TestingWidget component so they run in
the same order on every render, then use landmarkProps and cardRef where needed
after the early-return check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a11y: keyboard Accessibility issues related to keyboard navigation or shortcuts accessibility ci:normal feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[A11y]: Harmonise landmarks and heading levels

4 participants