Skip to content

feat(a11y): allow overriding heading tag via as prop on title-bearing components#372

Open
bntvllnt wants to merge 2 commits into
mainfrom
worktree-issue-371-heading-as-prop
Open

feat(a11y): allow overriding heading tag via as prop on title-bearing components#372
bntvllnt wants to merge 2 commits into
mainfrom
worktree-issue-371-heading-as-prop

Conversation

@bntvllnt
Copy link
Copy Markdown
Collaborator

@bntvllnt bntvllnt commented May 19, 2026

Summary

  • Adds an optional as prop (typed "h1" | "h2" | "h3" | "h4" | "h5" | "h6") to every component that renders a user-supplied title heading. Defaults preserve the current tag → non-breaking.
  • Multi-title components (ContentIntro, TutorialComplete) expose per-slot props (titleAs, tocLabelAs, sectionLabelAs) per the issue's "one prop per slot" guidance.
  • Re-exports HeadingTag from @vllnt/ui so consumers (incl. inlined registry copies) can type their callers.
  • Adds regression tests on a representative single-title component (ProfileSection) and a default-title-bearing component (FAQ).
  • Adds Storybook controls plus HeadingOverride stories for the affected components, and adds a Playwright CT coverage file that verifies rendered accessible heading levels for representative learning/market/status components.
  • Updates packages/ui/CHANGELOG.md under [Unreleased].

Affected components — 24 single-title + 2 multi-title:

ProfileSection, FAQ, Slideshow, WorldClockBar, TableOfContentsPanel, TableOfContents, KeyboardShortcutsHelp, Watchlist, OrderBook, HorizontalScrollRow, MarketTreemap, ActivityHeatmap, Glossary (the heading-bearing component inside key-concept), StatusBoard, CodePlayground, Comparison, Quiz, Exercise, ShareSection, CompletionDialog, Checklist, LearningObjectives, CandlestickChart, StepByStep, ContentIntro, TutorialComplete.

Notes

  • For "multi-heading" components labelled as single-title in the issue (OrderBook, MarketTreemap, StepByStep), only the primary title is exposed via as, matching the issue's "one as prop each" instruction.
  • KeyConcept in the issue scope refers to key-concept.tsx; the actual heading in that file is on the Glossary component (the <dt> in KeyConcept is not a heading), so the prop is added there.

Closes #371

Test plan

  • pnpm -F @vllnt/ui lint — clean
  • pnpm -F @vllnt/ui exec tsc --noEmit --project tsconfig.build.json — clean locally at 396a45727f02061bcc1b72d8970be9e0c6376293
  • pnpm build — both @vllnt/ui and @vllnt/ui-registry build green; registry inlined copies regenerated
  • pnpm test:once — 216 files / 1215 tests pass, incl. new regression tests
  • pnpm -F @vllnt/ui exec tsx scripts/check-story-coverage.tsAll 236 components have stories.
  • pnpm -F @vllnt/ui exec tsx scripts/verify-stories.tsChecked: 236 components; All stories provide required props. No crash risks detected.
  • pnpm -F @vllnt/ui exec playwright test -c playwright-ct.config.ts src/components/heading-overrides.visual.tsx — 2/2 passed locally at 396a45727f02061bcc1b72d8970be9e0c6376293
  • CI workflow checks at 396a45727f02061bcc1b72d8970be9e0c6376293 — Quality Gates, Storybook, Visual Regression, CodeQL, issue-link, registry deploy, and preview deploy all successful

… components

Adds an optional `as` prop (`"h1"`–`"h6"`) to every component that renders a
user-supplied title heading, plus per-slot props (`titleAs`, `tocLabelAs`,
`sectionLabelAs`) on multi-title components. Defaults preserve current tags so
the change is non-breaking.

Consumers can now align rendered headings with the surrounding page hierarchy
to satisfy WCAG 1.3.1 / 2.4.6 and Lighthouse `heading-order`.

Closes #371
Copy link
Copy Markdown
Collaborator Author

@bntvllnt bntvllnt left a comment

Choose a reason for hiding this comment

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

Review — 1 blocking finding

BLOCKING

  • T1 — PR evidence does not satisfy the issue's Storybook/visual verification acceptance criteria
    • Evidence: the PR changes 58 files, including 26 component implementations plus registry copies, but no *.stories.tsx, *.visual.tsx, or snapshot files are changed. The linked issue acceptance criteria require the new heading override to be documented in component TSDoc / Storybook story and require an a11y story or visual test confirming the rendered tag updates. Repo docs/agents/COMPONENTS.md also says UI changes should run unit + visual, or explicitly note which visual coverage was skipped and why.
    • Why it matters: this is an accessibility-facing API across many components. The two Vitest regressions prove the prop works for FAQ and ProfileSection, but they do not give Storybook/visual/a11y coverage for the broad component surface that designers and consumers use to verify heading-level behavior.
    • Fix: add Storybook controls/stories and/or Playwright CT/a11y coverage for the heading override (a representative shared pattern is OK if documented), or update the PR body with a concrete skipped-rationale that explains why Storybook/visual coverage is intentionally not part of this issue.

VERIFIED CLEAN

  • The implementation preserves existing default heading tags in the changed source components I inspected.
  • HeadingTag is exported from @vllnt/ui, and registry copies import that public type instead of reaching into package internals.
  • Source and registry changes line up structurally for the affected components.
  • Linked issue is present (Closes #371) and CI checks currently show green in gh pr checks.

VALIDATION

  • Ran: pnpm -F @vllnt/ui exec tsc --noEmit --project tsconfig.build.json — pass.
  • Ran: pnpm -F @vllnt/ui lint — pass.
  • Ran: pnpm -F @vllnt/ui exec vitest run src/components/faq/faq.test.tsx src/components/profile-section/profile-section.test.tsx — 2 files / 16 tests pass.
  • Not run locally: full pnpm build and pnpm test:once; GitHub checks report green for the PR head.

@vllnt-pilot
Copy link
Copy Markdown

vllnt-pilot Bot commented May 20, 2026

Preview ready · Updated 2026-05-20T14:28:28Z

Service Status Preview
ui-registry Ready https://pr-372-ui-registry.preview.vllnt.ai
Inspect
  • Tailnet-only by default (not reachable from public internet)
  • Cert: real Let's Encrypt wildcard
  • Reply with /clean to destroy this preview now

Copy link
Copy Markdown
Collaborator Author

@bntvllnt bntvllnt left a comment

Choose a reason for hiding this comment

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

Review — validation pass for heading as prop

BLOCKING

  • None found in this pass.

WARN

  • None found.

VERIFIED CLEAN

  • PR remains non-empty and targets main: 85 files changed, 958 insertions(+), 96 deletions(-) at head 396a45727f02061bcc1b72d8970be9e0c6376293.
  • Issue link is present: Closes #371.
  • GitHub reports the PR as open, non-draft, and CLEAN against main.
  • The PR body is now synced with the current head and includes story/visual validation evidence.
  • Source/registry/story coverage shape checks passed locally: 26 changed source component files, 26 changed stories, 26 generated registry copies; every changed HeadingTag source file has the matching registry path; every changed story has the heading-control/override marker.
  • Spot-checked story/control and visual coverage:
    • packages/ui/src/components/profile-section/profile-section.stories.tsx exposes an as select and HeadingOverride story.
    • packages/ui/src/components/content-intro/content-intro.stories.tsx exposes per-slot titleAs/tocLabelAs controls and override story.
    • packages/ui/src/components/heading-overrides.visual.tsx verifies rendered accessible heading levels for representative learning/market/status components.

VALIDATION

Local at 396a45727f02061bcc1b72d8970be9e0c6376293:

  • pnpm -F @vllnt/ui exec tsx scripts/check-story-coverage.tsAll 236 components have stories.
  • pnpm -F @vllnt/ui exec tsx scripts/verify-stories.tsChecked: 236 components; All stories provide required props. No crash risks detected.
  • pnpm -F @vllnt/ui exec tsc --noEmit --project tsconfig.build.json → passed locally.
  • pnpm -F @vllnt/ui exec playwright test -c playwright-ct.config.ts src/components/heading-overrides.visual.tsx → 2/2 passed.

GitHub checks at the same head are all successful, including Quality Gates, Verify Stories, Storybook, Visual Regression, CodeQL, issue-link enforcement, registry deploy, and preview deploy.

Approval recommended; leaving final APPROVE for the human reviewer per autonomous-review boundary.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

a11y: allow overriding heading tag (as prop) on title-bearing components

1 participant