fix(core): visible focus ring and aria-controls on CodeBlock collapse header#3723
Merged
cixzhang merged 1 commit intoJul 10, 2026
Merged
Conversation
|
@bhamodi is attempting to deploy a commit to the Meta Open Source Team on Vercel. A member of the Team first needs to authorize it. |
851bc83 to
3ead828
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
CodeBlockcollapsible header (packages/core/src/CodeBlock/CodeBlock.tsx) is a<div role="button" tabIndex={0}>that already exposesaria-expanded, but its disclosure pattern was incomplete in two ways:styles.headerresets the control (padding,border: none,background,color,font,textAlign) but never defines a:focus-visibleoutline. UnlikeCollapsible's trigger (fix(core): restore visible keyboard focus ring on Collapsible trigger #3722), it does not useall: unset, and the globalreset.cssdoesn't strip outlines from[role="button"]-- so the header still renders the browser's default UA outline on keyboard focus. That's technically visible, but inconsistent with the rest of the system: every other disclosure control (Collapsible,TabMenu) restores the design system's accent ring explicitly. This adds the same ring so keyboard focus is consistent and theme-aware.aria-controls. The header exposedaria-expandedbut nothing linked it to the region it shows/hides, so assistive tech couldn't move from the button to its controlled content.Changes
CodeBlock/CodeBlock.tsx:outline/outlineOffset:focus-visiblerules tostyles.headerCollapsible(scoped to the collapsible header -- the only state in which it's focusable), using the established convention:outline: 2px solid var(--color-accent),outlineOffset: 2px, both gated on:focus-visible. MirrorsTabMenu(TabList/TabMenu.tsx:93-100) andCollapsible(fix(core): restore visible keyboard focus ring on Collapsible trigger #3722).useId()-basedidon the collapsible code region andaria-controlson the header.CodeBlock/CodeBlock.test.tsx: add two tests (below).aria-controls: unconditional (region stays mounted)The code region collapses via a CSS grid animation (
grid-template-rows: 1fr -> 0fr), not by unmounting -- the region is always in the DOM whenever the header is a button. Soaria-controlsis set unconditionally (likeCollapsible#3707) and never points at a missing element. This differs fromBanner#3719, where the region is conditionally rendered andaria-controlsmust be gated on mount.Test plan
links the collapsible header to its code region via aria-controls-- assertsaria-controlsresolves viadocument.getElementByIdto the region containing the code (role="group"). Written first; confirmed failing before the fix (aria-controlswas null), passing after.keeps aria-controls resolvable when collapsed (region stays mounted)-- collapses the header and asserts the reference still resolves, documenting the unconditional choice.outlinevalue. Consistent with fix(core): restore visible keyboard focus ring on Collapsible trigger #3722, I did not add a brittle class-name-snapshot test. Manual verification: Tabbing to the header in Storybook shows the accent ring (matchingTabMenu); I could not run a browser in this environment, so this step is un-run -- the values are copied verbatim from already-shipping components that render the ring correctly.Commands:
node_modules/.bin/vitest run --root . packages/core/src/CodeBlock-- 26 pass (24 existing + 2 new).node_modules/.bin/tsc --project packages/core/tsconfig.json --noEmit-- clean.node_modules/.bin/eslint packages/core/src/CodeBlock/CodeBlock.tsx packages/core/src/CodeBlock/CodeBlock.test.tsx-- clean.Notes
Found during a broader a11y audit of core components. Scoped to the header/region only -- the copy button and its live region (covered separately by #3709) are untouched. One correction to the audit's framing: for
CodeBlockthe header's reset does not remove the browser outline (noall: unset), so this is a focus-ring consistency fix, not an invisible-focus rescue like #3722.