🎨 Palette: Add Skip to Main Content link #45

Open
ragusa-it wants to merge 1 commits from palette-skiplink-5651691488283915033 into main
9 changed files with 46 additions and 4 deletions

View File

@@ -3,7 +3,7 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { LanguageProvider } from './i18n';
import { Navbar, Footer, FancyCursor, ScrollToTop } from './components/layout';
import { Home } from './pages/Home';
import { PageLoader } from './components/ui';
import { PageLoader, SkipLink } from './components/ui';
import './styles/global.css';
// Lazy load pages to reduce initial bundle size.
@@ -15,6 +15,7 @@ export function App() {
return (
<LanguageProvider>
<BrowserRouter>
<SkipLink />
<ScrollToTop />
<FancyCursor />
<Navbar />

View File

@@ -0,0 +1,26 @@
.skipLink {
position: fixed;
top: 0;
left: 50%;
z-index: 1000;
transform: translateY(-100%) translateX(-50%);
padding: var(--space-sm) var(--space-md);
background-color: var(--md-sys-color-primary-container);
color: var(--md-sys-color-on-primary-container);
font-family: var(--md-sys-typescale-body-font);
font-weight: 600;
text-decoration: none;
border-radius: 0 0 var(--radius-md) var(--radius-md);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
transition: transform var(--transition-fast);
opacity: 0;
pointer-events: none;
}
.skipLink:focus {
transform: translateY(0) translateX(-50%);
opacity: 1;
pointer-events: auto;
outline: 2px solid var(--md-sys-color-primary);
outline-offset: 2px;
}

View File

@@ -0,0 +1,12 @@
import { useTranslation } from '../../i18n';
import styles from './SkipLink.module.css';
export function SkipLink() {
const { t } = useTranslation();
return (
<a href="#main-content" className={styles.skipLink}>
copilot-pull-request-reviewer[bot] commented 2026-02-01 02:03:10 +00:00 (Migrated from github.com)
Review

Clicking an in-page anchor typically scrolls but does not reliably move keyboard focus to the target element (focus often remains on the link). Since the goal is to let keyboard/screen reader users skip navigation, consider handling activation by programmatically focusing #main-content (and optionally updating the hash) so focus transfer is consistent across browsers/AT.

import React from 'react';
import { useTranslation } from '../../i18n';
import styles from './SkipLink.module.css';

export function SkipLink() {
  const { t } = useTranslation();

  const handleSkipToContent = (
    event: React.MouseEvent<HTMLAnchorElement>
  ) => {
    event.preventDefault();

    const mainContent = document.getElementById('main-content');

    if (mainContent) {
      if (!mainContent.hasAttribute('tabindex')) {
        mainContent.setAttribute('tabindex', '-1');
      }

      (mainContent as HTMLElement).focus();
    }

    window.location.hash = 'main-content';
  };

  return (
    <a
      href="#main-content"
      className={styles.skipLink}
      onClick={handleSkipToContent}
    >
Clicking an in-page anchor typically scrolls but does not reliably move keyboard focus to the target element (focus often remains on the link). Since the goal is to let keyboard/screen reader users skip navigation, consider handling activation by programmatically focusing `#main-content` (and optionally updating the hash) so focus transfer is consistent across browsers/AT. ```suggestion import React from 'react'; import { useTranslation } from '../../i18n'; import styles from './SkipLink.module.css'; export function SkipLink() { const { t } = useTranslation(); const handleSkipToContent = ( event: React.MouseEvent<HTMLAnchorElement> ) => { event.preventDefault(); const mainContent = document.getElementById('main-content'); if (mainContent) { if (!mainContent.hasAttribute('tabindex')) { mainContent.setAttribute('tabindex', '-1'); } (mainContent as HTMLElement).focus(); } window.location.hash = 'main-content'; }; return ( <a href="#main-content" className={styles.skipLink} onClick={handleSkipToContent} > ```
{t.nav.skipToContent}
</a>
);
}
copilot-pull-request-reviewer[bot] commented 2026-02-01 02:03:10 +00:00 (Migrated from github.com)
Review

There are unit tests for other UI components under src/components/ui/__tests__, but the new SkipLink component has no coverage. Adding a test that renders it within LanguageProvider and verifies the link text/href (and the focus-to-main behavior, if implemented) would help prevent regressions.

There are unit tests for other UI components under `src/components/ui/__tests__`, but the new `SkipLink` component has no coverage. Adding a test that renders it within `LanguageProvider` and verifies the link text/href (and the focus-to-main behavior, if implemented) would help prevent regressions.

View File

@@ -2,3 +2,4 @@ export { Button } from './Button';
export { Card } from './Card';
export { Input, Textarea } from './Input';
export { PageLoader } from './PageLoader';
export { SkipLink } from './SkipLink';

View File

@@ -1,6 +1,7 @@
export const de = {
// Navigation
nav: {
skipToContent: 'Zum Hauptinhalt springen',
home: 'Startseite',
about: 'Über uns',
contact: 'Kontakt',

View File

@@ -3,6 +3,7 @@ import type { Translations } from './de';
export const en: Translations = {
// Navigation
nav: {
skipToContent: 'Skip to main content',
home: 'Home',
about: 'About',
contact: 'Contact',

View File

@@ -46,7 +46,7 @@ export function About() {
const location = useLocation();
return (
<main className={styles.about} key={location.key}>
<main id="main-content" tabIndex={-1} className={styles.about} key={location.key}>
{/* Hero Section */}
<section className={styles.hero}>
<div className="container">

View File

@@ -133,7 +133,7 @@ export function Contact() {
};
return (
<main className={styles.contact}>
<main id="main-content" tabIndex={-1} className={styles.contact}>
{/* Hero */}
<section className={styles.hero}>
<div className="container">

View File

@@ -2,7 +2,7 @@ import { Hero, Services } from '../components/sections';
export function Home() {
return (
<main>
<main id="main-content" tabIndex={-1}>
<Hero />
<Services />
</main>