web accessibility

This commit is contained in:
Sascha 2025-09-25 16:01:45 +02:00
parent ca488cfce9
commit 2404b8760c
7 changed files with 56 additions and 20 deletions

View File

@ -52,8 +52,9 @@ function AppRouter() {
return ( return (
<Router> <Router>
<div className="app"> <div className="app">
<a href="#main-content" className="skip-link">Skip to main content</a>
<Navbar theme={theme} setTheme={setTheme} /> <Navbar theme={theme} setTheme={setTheme} />
<main className="app__main"> <main id="main-content" className="app__main">
<Routes> <Routes>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route path="/imprint" element={<ImprintPage />} /> <Route path="/imprint" element={<ImprintPage />} />

View File

@ -1,3 +1,5 @@
import { useEffect, useRef } from 'react';
type Props = { type Props = {
menuItems: string[]; menuItems: string[];
isOpen: boolean; isOpen: boolean;
@ -5,19 +7,29 @@ type Props = {
}; };
export default function MobileMenu({ menuItems, isOpen, onItemClick }: Props) { export default function MobileMenu({ menuItems, isOpen, onItemClick }: Props) {
const firstButtonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (isOpen && firstButtonRef.current) {
firstButtonRef.current.focus();
}
}, [isOpen]);
if (!isOpen) return null; if (!isOpen) return null;
return ( return (
<div className="navbar__mobile-menu"> <nav id="mobile-menu" className="navbar__mobile-menu" role="navigation" aria-label="Mobile navigation">
{menuItems.map((item) => ( {menuItems.map((item, index) => (
<button <button
key={item} key={item}
ref={index === 0 ? firstButtonRef : undefined}
onClick={() => onItemClick(item)} onClick={() => onItemClick(item)}
className="navbar__mobile-menu__item" className="navbar__mobile-menu__item"
type="button"
> >
{item} {item}
</button> </button>
))} ))}
</div> </nav>
); );
} }

View File

@ -45,13 +45,16 @@ export default function Navbar({
))} ))}
<ThemeToggle theme={theme} setTheme={setTheme} /> <ThemeToggle theme={theme} setTheme={setTheme} />
{/* Burger Button für Mobile */} {/* Burger Button für Mobile */}
<div <button
type="button"
className={`navbar__burger-button ${menuOpen ? 'active' : ''}`} className={`navbar__burger-button ${menuOpen ? 'active' : ''}`}
aria-label={texts.navigation.mobileMenuAriaLabel} aria-label={texts.navigation.mobileMenuAriaLabel}
aria-expanded={menuOpen}
aria-controls="mobile-menu"
onClick={() => setMenuOpen(!menuOpen)} onClick={() => setMenuOpen(!menuOpen)}
> >
{[0, 1, 2].map(i => <span key={i} className="navbar__burger-button__item"></span>)} {[0, 1, 2].map(i => <span key={i} className="navbar__burger-button__item"></span>)}
</div> </button>
<MobileMenu <MobileMenu
menuItems={menuItems} menuItems={menuItems}

View File

@ -79,6 +79,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="projects-section__action-button projects-section__action-button--secondary" className="projects-section__action-button projects-section__action-button--secondary"
aria-label={`View ${project.title} source code on GitHub`}
> >
<Github className="projects-section__action-icon" /> <Github className="projects-section__action-icon" />
{texts.codeButtonText} {texts.codeButtonText}
@ -90,6 +91,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="projects-section__action-button projects-section__action-button--primary" className="projects-section__action-button projects-section__action-button--primary"
aria-label={`View ${project.title} live demo`}
> >
<ExternalLink className="projects-section__action-icon" /> <ExternalLink className="projects-section__action-icon" />
{texts.liveButtonText} {texts.liveButtonText}

View File

@ -18,7 +18,7 @@ export default function ServicesSection(props: ServicesSectionProps = {}) {
services = [ services = [
{ {
icon: Eye, icon: Eye,
title: "Web Accessiblity", title: "Web Accessibility",
description: "Building inclusive and accessible web applications by implementing Web Content Accessibility Guidelines, semantic HTML, and support for assistive technologies.", description: "Building inclusive and accessible web applications by implementing Web Content Accessibility Guidelines, semantic HTML, and support for assistive technologies.",
colorClass: "primary" colorClass: "primary"
}, },

View File

@ -3,6 +3,27 @@
@forward 'variables'; @forward 'variables';
@forward 'mixins'; @forward 'mixins';
// Skip link for accessibility
.skip-link {
position: absolute;
top: -40px;
left: 6px;
background: var(--color-primary);
color: white;
padding: 8px;
text-decoration: none;
z-index: 9999;
border-radius: 4px;
font-weight: 500;
transition: top 0.3s;
&:focus {
top: 6px;
outline: 2px solid var(--color-secondary);
outline-offset: 2px;
}
}
// Global button placeholder // Global button placeholder
%button-base { %button-base {
@include button-base; @include button-base;
@ -72,4 +93,3 @@
cursor: pointer; cursor: pointer;
font: inherit; font: inherit;
} }

View File

@ -32,6 +32,7 @@
&__burger-button { &__burger-button {
@include globals.flex-center(); @include globals.flex-center();
flex-direction: column;
justify-content: space-between; justify-content: space-between;
width: 20px; width: 20px;
height: 17px; height: 17px;
@ -42,31 +43,28 @@
&__item { &__item {
display: block; display: block;
height: 4px; height: 3px;
width: 100%; width: 100%;
background-color: var(--color-text); background-color: var(--color-text);
border-radius: 2px; border-radius: 2px;
transition: all 0.3s ease; transition: all 0.3s ease;
margin-top: 2px; margin: 1px 0;
} }
&:hover .navbar__container__burger-button__item { &:hover &__item {
background-color: var(--color-primary); background-color: var(--color-primary);
} }
&.active { &.active {
.navbar__container__burger-button__item:nth-child(1) { &__item:nth-child(1) {
transform: rotate(45deg) translate(5px, 5px); transform: rotate(45deg) translate(5px, 5px);
} }
.navbar__container__burger-button__item:nth-child(2) { &__item:nth-child(2) {
opacity: 0; opacity: 0;
} }
.navbar__container__burger-button__item:nth-child(3) { &__item:nth-child(3) {
transform: rotate(-45deg) translate(5px, -5px); transform: rotate(-45deg) translate(5px, -5px);
} }
.navbar__container__burger-button__item:nth-child(n + 4) {
opacity: 0;
}
} }
} }