Skip to content

Accessibility improvements: skip link, focus styles, and ARIA labels#18

Merged
ooloth merged 13 commits intomainfrom
polish-a11y-for-launch
Dec 21, 2025
Merged

Accessibility improvements: skip link, focus styles, and ARIA labels#18
ooloth merged 13 commits intomainfrom
polish-a11y-for-launch

Conversation

@ooloth
Copy link
Owner

@ooloth ooloth commented Dec 21, 2025

✅ What

  • Adds a skip link for keyboard navigation (jumps to main content)
  • Adds focus-visible styles to interactive elements (cards, error retry button, pagination links)
  • Adds aria-label to pagination nav for screen readers
  • Fixes pagination layout on short post pages (links now stay above footer)
  • Adds comprehensive tests for skip link and pagination accessibility features
  • Unrelated: deletes a number of Claude-related markdown files in favour of my new beads-oriented workflow

🤔 Why

  • Keyboard users can now bypass navigation and jump straight to content
  • Focus indicators are now visible when navigating with keyboard (Tab key)
  • Screen reader users get clearer context about pagination controls

Changed className from "flex_auto" (invalid) to "flex-auto" (correct Tailwind class) in the Post component's main element. This fixes the flex layout so post pages properly expand to push the footer to the bottom.

Closes META-eww
- Added "Skip to main content" link to prose layout
- Link is hidden by default (sr-only) and visible on focus
- Added id="main" to all main elements in prose pages
- Keyboard users can now tab to skip link and bypass navigation

Improves accessibility for keyboard navigation.

Closes META-02m
- Card component: focus-visible border matching hover state
- Likes media links: focus-visible ring for image links
- Error page retry button: focus-visible background and ring

All interactive elements now have clear keyboard focus indicators.

Closes META-sh8, META-pnf, META-86l
@ooloth ooloth marked this pull request as ready for review December 21, 2025 21:01
Copilot AI review requested due to automatic review settings December 21, 2025 21:01
Copy link

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

This PR enhances accessibility across the portfolio site by adding keyboard navigation support, focus indicators, ARIA labels, and comprehensive test coverage. The changes ensure keyboard and screen reader users can effectively navigate and interact with all content. Additionally, pagination positioning is fixed for short blog posts, and several outdated documentation files are removed.

Key changes:

  • Added skip link for keyboard users to bypass navigation and jump to main content
  • Implemented visible focus states for interactive elements (cards, buttons, links)
  • Fixed pagination layout to display above footer on short content pages
  • Added comprehensive tests for accessibility features

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
app/(prose)/layout.tsx Added skip link element for keyboard navigation
app/(prose)/layout.test.tsx New tests validating skip link functionality
app/(prose)/page.tsx Added id="main" to main element as skip link target
app/(prose)/page.test.tsx Updated test to verify main element has id attribute
app/(prose)/blog/page.tsx Added id="main" to main element
app/(prose)/blog/page.test.tsx Updated test to verify main element has id attribute
app/(prose)/not-found.tsx Added id="main" to main element
app/(prose)/[slug]/ui/post.tsx Fixed flex-auto typo and moved pagination outside article
ui/nav/pagination.tsx Added ARIA label and extracted non-breaking space logic
ui/nav/pagination.test.tsx New comprehensive tests for pagination accessibility
ui/card.tsx Added focus-visible styles for keyboard navigation
app/likes/page.tsx Added focus-visible styles to media item links
app/likes/error.tsx Added focus-visible styles to retry button
AGENTS.md Removed (deprecated documentation)
@AGENTS.md Removed (deprecated documentation)
.claude/specs/*.md Removed multiple outdated spec files
.claude/settings.local.json Simplified configuration
.beads/issues.jsonl Updated issue tracking data

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +22 to +24
</article>
</main>
<PaginationLinks prevPost={prevPost} nextPost={nextPost} />
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

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

Moving pagination outside the article element changes the semantic structure - pagination is now a sibling of main rather than a child. Consider whether this affects screen reader navigation and document outline. If pagination is conceptually part of the post content navigation, it might be better to keep it inside the article but outside the main element, using a wrapping fragment around both main and nav.

Suggested change
</article>
</main>
<PaginationLinks prevPost={prevPost} nextPost={nextPost} />
<PaginationLinks prevPost={prevPost} nextPost={nextPost} />
</article>
</main>

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +21
const NON_BREAKING_SPACE = '\u00A0'

/**
* Replaces the last space in a string with a non-breaking space.
* This prevents awkward line wrapping where the last word appears alone on a new line.
*
* @example
* replaceLastSpaceWithNonBreaking("Hello World") // "Hello\u00A0World"
*/
export function replaceLastSpaceWithNonBreaking(text: string): string {
return text.replace(/ (?!.* )/, NON_BREAKING_SPACE)
}
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

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

The JSDoc example shows the Unicode escape sequence in the comment, but actual usage will show as a regular space character. Consider updating the example to clarify what the visual output looks like: // Returns \"Hello World\" (with non-breaking space before \"World\") to make it clearer that there's no visible difference.

Copilot uses AI. Check for mistakes.
@claude
Copy link

claude bot commented Dec 21, 2025

Pull Request Review: Accessibility Improvements

Overview

This PR makes important accessibility improvements to the site, adding keyboard navigation support, focus styles, and ARIA labels. The changes are well-implemented with comprehensive test coverage.

✅ Strengths

1. Excellent Test Coverage

  • Skip link functionality thoroughly tested in app/(prose)/layout.test.tsx
  • Pagination accessibility tested comprehensively in ui/nav/pagination.test.tsx
  • Tests verify both implementation and accessibility requirements (ARIA labels, focus order, etc.)
  • Good use of test descriptions that explain the "why"

2. Proper Accessibility Implementation

  • Skip link correctly positioned as first focusable element (layout.tsx:12-17)
  • Focus styles use focus-visible instead of focus (modern best practice)
  • aria-label="Post navigation" disambiguates pagination from primary nav (pagination.tsx:25)
  • All main elements properly marked with id="main" for skip link target
  • Focus indicators match hover states for consistency

3. Clean, Maintainable Code

  • Bug fix in post.tsx:17 (className="flex_auto" → "flex-auto") is a good catch
  • Focus styles are consistent across components (Card, error button, media links)
  • Exported utility function replaceLastSpaceWithNonBreaking properly tested and documented

4. Good Separation of Concerns

  • Tests appropriately mock dependencies to focus on accessibility features
  • Tests verify both visual structure AND accessible names/labels

🔍 Areas for Improvement

1. Security: Link Target Safety
In app/likes/page.tsx:80-84, external links use target="_blank" with rel="noopener noreferrer" which is correct. However, consider adding visual indication that these are external links for sighted users.

2. Code Quality: Emoji Direction Logic
In ui/nav/pagination.tsx:42, consider whether the emoji directions match user expectations for Previous/Next navigation.

3. Accessibility: Focus Ring Color Contrast
The focus ring uses ring-zinc-300 throughout. On dark backgrounds, verify this meets WCAG contrast requirements with actual testing.

4. Test Coverage: Missing Edge Cases
app/(prose)/layout.test.tsx does not verify that the skip link becomes visible on focus. Consider adding a test for the focus-visible behavior.

5. Performance: CSS Class String Concatenation
In pagination.tsx:47, 48, 50, conditional class concatenation could use clsx or similar utility for cleaner code.

📋 Checklist

  • Code Quality: Clean, readable, follows existing patterns
  • Best Practices: Modern accessibility techniques (focus-visible, ARIA labels)
  • Potential Bugs: One bug fix (flex_auto typo), no new bugs introduced
  • Performance: No performance concerns
  • ⚠️ Security: External links handled correctly; minor enhancement suggested
  • Test Coverage: Excellent - comprehensive tests for new features

🎯 Recommendations

High Priority:

  1. Verify ring-zinc-300 focus indicator contrast meets WCAG AA standards (4.5:1)
  2. Test skip link functionality with actual keyboard navigation (Tab key)
  3. Test with screen reader (NVDA/JAWS/VoiceOver) to verify ARIA labels work as expected

Medium Priority:
4. Review emoji direction logic for Previous/Next navigation
5. Add visual indicator for external links on Likes page
6. Consider adding clsx utility for cleaner conditional class logic

Low Priority:
7. Add test for skip link visibility on focus
8. Verify Emoji component implementation sets proper ARIA attributes

🚀 Overall Assessment

LGTM with minor suggestions. This PR makes meaningful accessibility improvements with strong test coverage. The implementation follows modern best practices and integrates cleanly with the existing codebase. The suggestions above are mostly minor enhancements rather than blockers.

The unrelated cleanup (deletion of Claude markdown files) is noted in the PR description and does not affect code quality.

Score: 8.5/10

  • Excellent accessibility implementation
  • Comprehensive test coverage
  • Clean, maintainable code
  • Minor room for improvement on contrast verification and testing

Great work! 🎉

@ooloth ooloth merged commit 201ed88 into main Dec 21, 2025
5 checks passed
@ooloth ooloth deleted the polish-a11y-for-launch branch December 21, 2025 21:17
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.

2 participants