diff --git a/src/App.tsx b/src/App.tsx
index 5c4e796..f57501b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -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,17 +15,20 @@ export function App() {
return (
+
- {/* Suspense handles the loading state for lazy-loaded routes */}
- }>
-
- } />
- } />
- } />
-
-
+
+ {/* Suspense handles the loading state for lazy-loaded routes */}
+ }>
+
+ } />
+ } />
+ } />
+
+
+
diff --git a/src/components/ui/SkipLink.module.css b/src/components/ui/SkipLink.module.css
new file mode 100644
index 0000000..15373d1
--- /dev/null
+++ b/src/components/ui/SkipLink.module.css
@@ -0,0 +1,20 @@
+.skipLink {
+ position: absolute;
+ top: 1rem;
+ left: 1rem;
+ z-index: 100;
+ padding: 0.75rem 1.25rem;
+ background-color: var(--md-sys-color-primary);
+ color: var(--md-sys-color-on-primary);
+ font-weight: 600;
+ border-radius: var(--radius-md);
+ text-decoration: none;
+ transform: translateY(-200%);
+ transition: transform var(--transition-normal);
+ outline: none;
+ box-shadow: var(--md-sys-color-shadow) 0px 4px 8px 3px;
+}
+
+.skipLink:focus {
+ transform: translateY(0);
+}
diff --git a/src/components/ui/SkipLink.tsx b/src/components/ui/SkipLink.tsx
new file mode 100644
index 0000000..f317ad1
--- /dev/null
+++ b/src/components/ui/SkipLink.tsx
@@ -0,0 +1,12 @@
+import { useTranslation } from '../../i18n';
+import styles from './SkipLink.module.css';
+
+export function SkipLink() {
+ const { t } = useTranslation();
+
+ return (
+
+ {t.nav.skipLink}
+
+ );
+}
diff --git a/src/components/ui/__tests__/SkipLink.test.tsx b/src/components/ui/__tests__/SkipLink.test.tsx
new file mode 100644
index 0000000..6459088
--- /dev/null
+++ b/src/components/ui/__tests__/SkipLink.test.tsx
@@ -0,0 +1,33 @@
+// @vitest-environment jsdom
+import { render, screen, cleanup } from '@testing-library/react';
+import { describe, it, expect, afterEach } from 'vitest';
+import { SkipLink } from '../SkipLink';
+import { LanguageProvider } from '../../../i18n';
+
+describe('SkipLink', () => {
+ afterEach(() => {
+ cleanup();
+ });
+
+ const renderWithProviders = (component: React.ReactNode) => {
+ return render(
+
+ {component}
+
+ );
+ };
+
+ it('renders correctly with correct href', () => {
+ renderWithProviders();
+ const link = screen.getByRole('link');
+ expect(link).toBeTruthy();
+ expect(link.getAttribute('href')).toBe('#main-content');
+ });
+
+ it('renders translated text', () => {
+ renderWithProviders();
+ const link = screen.getByRole('link');
+ const text = link.textContent;
+ expect(text === 'Zum Inhalt springen' || text === 'Skip to content').toBe(true);
+ });
+});
diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts
index 78cc7f9..f857f96 100644
--- a/src/components/ui/index.ts
+++ b/src/components/ui/index.ts
@@ -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';
diff --git a/src/i18n/de.ts b/src/i18n/de.ts
index 0b42d51..2866316 100644
--- a/src/i18n/de.ts
+++ b/src/i18n/de.ts
@@ -1,6 +1,7 @@
export const de = {
// Navigation
nav: {
+ skipLink: 'Zum Inhalt springen',
home: 'Startseite',
about: 'Über uns',
contact: 'Kontakt',
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 20c8a34..4a87e87 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -3,6 +3,7 @@ import type { Translations } from './de';
export const en: Translations = {
// Navigation
nav: {
+ skipLink: 'Skip to content',
home: 'Home',
about: 'About',
contact: 'Contact',