fix(website): resolve accessibility violations in ported detail pages

Fixes the three axe violations surfaced by the a11y audit after the
Primer Brand redesign:

- aria-prohibited-attr: `aria-label` was set on roleless `div`s in
  InstructionDetail and PluginDetail. The "Applies to" list now uses
  list/listitem roles; plugin provenance uses a group role.
- scrollable-region-focusable: the install command `code` element
  overflows horizontally but was not keyboard reachable. Added
  tabIndex to all three render sites.
- color-contrast: the Playbook "New" label used the brand's
  success-fg on success-subtle, reaching only 4.09:1 in light mode.
  Stepped one down the same green ramp for 6.14:1. Scoped to light
  mode; dark mode already passed and its green-7 is near-black.

Also corrects the stale route list in the audit script: /hooks/,
/workflows/ and /tools/ have never existed as pages.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 80686fef-efe3-4cdd-8cd6-bfa61a5d0af6
This commit is contained in:
Aaron Powell
2026-08-14 17:46:36 +10:00
parent f29b88f27f
commit 49cdb7e010
6 changed files with 25 additions and 12 deletions
+1 -6
View File
@@ -28,21 +28,16 @@ const routes = [
'/agents/',
'/instructions/',
'/skills/',
'/hooks/',
'/workflows/',
'/extensions/',
'/plugins/',
'/tools/',
'/contributors/',
'/learning-hub/',
'/learning-hub/cookbook/',
'/learning-hub/github-copilot-app/',
// Representative dedicated detail pages (one per resource type) so the audit
// covers the shared detail layout, sidebar, install buttons, and file browser.
'/agent/accessibility/',
'/instruction/a11y/',
'/skill/acquire-codebase-knowledge/',
'/hook/dependency-license-checker/',
'/workflow/daily-issues-report/',
'/plugin/accessibility-kanban/',
'/extension/accessibility-kanban/',
];
@@ -74,7 +74,9 @@ function InstallCommand({ command }: { command: string }) {
return (
<div className={appStyles.installBar}>
<code className={appStyles.installCommand}>{command}</code>
<code className={appStyles.installCommand} tabIndex={0}>
{command}
</code>
<button
type="button"
className={appStyles.installCopy}
@@ -70,7 +70,9 @@ export function InstallCommandBar() {
))}
</ActionMenu.Overlay>
</ActionMenu>
<code className={styles.installCommand}>{selected.command}</code>
<code className={styles.installCommand} tabIndex={0}>
{selected.command}
</code>
<button
type="button"
className={styles.installCopy}
@@ -125,9 +125,11 @@ export function InstructionDetail({
// are promoted into the hero rather than left in the sidebar.
const heroExtras =
applyTo.length > 0 ? (
<div className={styles.metaValues} aria-label="Applies to">
<div className={styles.metaValues} role="list" aria-label="Applies to">
{applyTo.map((pattern) => (
<Token key={pattern} text={pattern} />
<span key={pattern} role="listitem">
<Token text={pattern} />
</span>
))}
</div>
) : null;
@@ -141,7 +141,9 @@ export function PluginDetail({
const install = (
<>
<div className={installStyles.installBar}>
<code className={installStyles.installCommand}>{installCommand}</code>
<code className={installStyles.installCommand} tabIndex={0}>
{installCommand}
</code>
<button
type="button"
className={installStyles.installCopy}
@@ -167,7 +169,7 @@ export function PluginDetail({
// Provenance sits in the hero: whether the plugin ships from this repository
// or from a third-party repo is the first thing a reader needs to know.
const heroExtras = (
<div className={styles.metaValues} aria-label="Plugin provenance">
<div className={styles.metaValues} role="group" aria-label="Plugin provenance">
<Label color={item.external ? "purple" : "green"} size="medium">
{item.external ? "External plugin" : "Built-in"}
</Label>
@@ -1049,6 +1049,16 @@
background-color: var(--brand-color-success-subtle);
}
/*
* In light mode the brand's success-fg on success-subtle only reaches 4.09:1 at
* this text size. Step one down the same green ramp to clear WCAG AA (6.14:1)
* without leaving the brand palette. Dark mode already passes, and its green-7
* is near-black, so this is deliberately light-mode only.
*/
.page[data-mode="light"] .recCardLabelAccent {
color: var(--base-color-scale-green-7);
}
/*
* Secondary label variant: stroke only, no fill. Pairs with the neutral filled
* .recCardLabel so the two tags on a card read as distinct (per brand cards).