Skip to content

Card: address accessibility review feedback#7852

Merged
liuliu-dev merged 14 commits into
mainfrom
liuliu/card-improvements
May 21, 2026
Merged

Card: address accessibility review feedback#7852
liuliu-dev merged 14 commits into
mainfrom
liuliu/card-improvements

Conversation

@liuliu-dev
Copy link
Copy Markdown
Contributor

@liuliu-dev liuliu-dev commented May 18, 2026

Addresses accessibility-review feedback from Tetralogical (github/accessibility#10648) for the experimental Card component. Part of https://github.com/github/primer/issues/6672

Changelog

Default example - metadata content

Default story no longer uses "Updated 2 hours ago" (which would have pulled in RelativeTime's outstanding accessibility issues). Replaced with <PeopleIcon /> 3 contributors. The metadata slot is also now documented in Card.docs.json and JSDoc to confirm it accepts any content, including other components.

Data-component attributes

Added data-component attributes per ADR-023 to Card, Card.Icon, Card.Image, Card.Heading, Card.Description, Card.Metadata, and Card.Menu.

Grouping content for accessibility — new as prop

Cards can now opt into being a <section> landmark via a new as prop. The prop is typed so as="section" requires aria-label or aria-labelledby.

// default: renders as <div>, no accessible name required
<Card></Card>

// section landmark: enforces an accessible name
<Card as="section" aria-labelledby="my-card-heading">
  <Card.Heading id="my-card-heading">Repo name</Card.Heading>
  
</Card>

Interactive content

New WithMenu feature story showing an action menu inside a Card with an accessible name on the trigger (aria-label="More options for primer/react").

Custom content variant

CustomContent story now uses an <h3> heading instead of <strong>, resolving the WCAG 2.2 SC 1.3.1 violation.

Props section

Card.docs.json and JSDoc now document Card.Description, Card.Menu, and Card.Metadata.

Empty props

children is now required on Card. Empty cards log a dev warning and return null. Tests cover both paths.

Inline style attributes on wrapping divs

Added Card.stories.module.css with a WidthConstraintContainer class instead ofstyle={{…}} props in the stories.

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

Merge checklist

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 18, 2026

🦋 Changeset detected

Latest commit: 45e4ed7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

@github-actions github-actions Bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label May 18, 2026
@liuliu-dev liuliu-dev added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label May 18, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-7852 May 18, 2026 23:04 Inactive
@github-actions github-actions Bot removed the update snapshots 🤖 Command that updates VRT snapshots on the pull request label May 18, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-7852 May 18, 2026 23:22 Inactive
@siddharthkp siddharthkp added the Canary Release Apply this label when you want CI to create a canary release of the current PR label May 19, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-7852 May 19, 2026 20:31 Inactive
@liuliu-dev liuliu-dev marked this pull request as ready for review May 19, 2026 21:17
@liuliu-dev liuliu-dev requested a review from a team as a code owner May 19, 2026 21:17
@liuliu-dev liuliu-dev requested review from Copilot and joshblack May 19, 2026 21:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the experimental Card component to address external accessibility review feedback by tightening runtime/dev-time validation, improving a11y-focused examples and docs, and aligning with the repo’s data-component instrumentation approach.

Changes:

  • Added data-component attributes across Card and subcomponents; added an as prop to support rendering as a labelled <section> landmark (with runtime dev warnings for missing accessible name).
  • Made children required and added a dev warning + null render for empty cards, with new unit test coverage.
  • Improved Storybook examples and docs (new feature stories, removed inline styles in stories, expanded docs.json props coverage).
Show a summary per file
File Description
packages/react/src/Card/Card.tsx Adds children requirement, as="section" support + a11y warnings, and data-component attributes for Card + subcomponents.
packages/react/src/Card/Card.test.tsx Adds tests for data-component, empty-children behavior, and as="section" landmark behavior/warnings.
packages/react/src/Card/Card.stories.tsx Updates default/playground stories; uses CSS module decorator instead of inline max-width styles; updates metadata example content.
packages/react/src/Card/Card.stories.module.css Introduces shared Storybook layout classes (width constraint, list layout, custom content layout).
packages/react/src/Card/Card.features.stories.tsx Adds feature stories demonstrating menu usage, landmark section usage, list usage, and interactive content a11y labeling patterns.
packages/react/src/Card/Card.docs.json Documents children requirement, as prop guidance, and props for Card.Description, Card.Menu, and Card.Metadata; adds new stories to docs.
.changeset/lucky-terms-sing.md Declares a minor release for the new props/requirements and instrumentation updates.

Copilot's findings

  • Files reviewed: 7/16 changed files
  • Comments generated: 2

Comment thread packages/react/src/Card/Card.tsx Outdated
Comment thread packages/react/src/Card/Card.tsx Outdated
Copy link
Copy Markdown
Member

@joshblack joshblack left a comment

Choose a reason for hiding this comment

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

Looking great! Just left a couple of comments/questions, hope they make sense. Let me know if you want to talk about anything 👀

Comment thread packages/react/src/Card/Card.tsx Outdated
Comment thread packages/react/src/Card/Card.tsx Outdated
Comment thread packages/react/src/Card/Card.tsx
Comment thread packages/react/src/Card/Card.tsx Outdated
Comment on lines +151 to +163
warning(
isEmpty,
'The <Card> component was rendered with no children and will not render. Provide either Card subcomponents (Card.Heading, Card.Description, etc.) or custom content.',
)

warning(
as === 'section' && !('aria-label' in props) && !('aria-labelledby' in props),
'The <Card> component used with `as="section"` requires either `aria-label` or `aria-labelledby` so screen-reader users can identify the labelled region. Typically `aria-labelledby` should reference the id of the `Card.Heading`.',
)

if (isEmpty) {
return null
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think both of these are either already enforced at the type level or could be enforced at the type level, is that right? If so, I don't know if we need to do runtime checks for them. Let me know what you think 👀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the section aria-label warning.

Kept the empty children warning, seems that types can't prevent <Card>{null}</Card> 👀

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@liuliu-dev is this something that we need to warn about? (having no children).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nope, removed it

)
}

CardImage.displayName = 'Card.Image'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Super nitpicky, it'd be nice if the components were function declarations so that we didn't have to do this, e.g.

function CardImage(/* ... */) {
  // ...
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Converted to function declarations.

Should we keep displayName for the dotted names? Otherwise it will show as CardImage not Card.Image in devtools

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm neutral about it, if I had to pick I'm happy with just the component name itself versus adding the dots in personally but I get if that's confusing

Comment thread packages/react/src/Card/Card.tsx Outdated
Comment thread packages/react/src/Card/Card.features.stories.tsx Outdated
Comment thread packages/react/src/Card/Card.features.stories.tsx
<Card.Description>
{"GitHub's design system implemented as React components for building consistent user interfaces."}
</Card.Description>
<Card.Menu>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One naming suggestion, would it make sense to have Card.Menu be Card.Actions? Or is it important for it to always be a Menu?

Suggested change
<Card.Menu>
<Card.Actions>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think "Actions" might imply buttons rather than a single dropdown trigger. "Menu" is accurate for the current use case but too specific. What do you think about Card.Control?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think if the idea is that this will always be a Menu then that makes sense 👍 If it might grow in the future it might make sense to make it more generic. For cards, do you think we're in the more specific case? 👀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That makes sense, renamed it to Card.Action.

Comment thread packages/react/src/Card/Card.tsx Outdated
@liuliu-dev
Copy link
Copy Markdown
Contributor Author

@joshblack thanks for the thorough review, really appreciate it!! I addressed most of them, and some I left questions or marked as features to add later, what do you think?

@liuliu-dev liuliu-dev requested a review from joshblack May 21, 2026 16:41
Copy link
Copy Markdown
Member

@joshblack joshblack left a comment

Choose a reason for hiding this comment

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

Approving in advance ✅ Left a couple of questions, only big thing is that I don't think we need to warn on empty children but that's definitely a personal preference.

@github-actions github-actions Bot requested a deployment to storybook-preview-7852 May 21, 2026 22:33 Abandoned
@primer
Copy link
Copy Markdown
Contributor

primer Bot commented May 21, 2026

🤖 Lint issues have been automatically fixed and committed to this PR.

@github-actions github-actions Bot requested a deployment to storybook-preview-7852 May 21, 2026 22:39 Abandoned
@github-actions github-actions Bot temporarily deployed to storybook-preview-7852 May 21, 2026 22:48 Inactive
@primer-integration
Copy link
Copy Markdown

👋 Hi from github/github-ui! Your integration PR is ready: https://github.com/github/github-ui/pull/21334

@primer-integration
Copy link
Copy Markdown

Integration test results from github/github-ui:

Passed  CI   Passed
Passed  VRT   Passed
Passed  Projects   Passed

All checks passed!

@liuliu-dev liuliu-dev added this pull request to the merge queue May 21, 2026
Merged via the queue into main with commit 5504680 May 21, 2026
53 checks passed
@liuliu-dev liuliu-dev deleted the liuliu/card-improvements branch May 21, 2026 23:26
@primer primer Bot mentioned this pull request May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Canary Release Apply this label when you want CI to create a canary release of the current PR integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants