From 2404b8760c33baec44ef5c6149d15a0ca2fbc026 Mon Sep 17 00:00:00 2001 From: Sascha Date: Thu, 25 Sep 2025 16:01:45 +0200 Subject: [PATCH] web accessibility --- src/app/AppRouter.tsx | 3 ++- src/components/layout/MobileMenu.tsx | 18 ++++++++++--- src/components/layout/Navigation.tsx | 7 ++++-- src/components/sections/ProjectsSection.tsx | 2 ++ src/components/sections/ServicesSection.tsx | 2 +- src/scss/globals.scss | 28 ++++++++++++++++++--- src/scss/layout/topbar.scss | 16 ++++++------ 7 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/app/AppRouter.tsx b/src/app/AppRouter.tsx index 20fb34a..8532425 100644 --- a/src/app/AppRouter.tsx +++ b/src/app/AppRouter.tsx @@ -52,8 +52,9 @@ function AppRouter() { return (
+ Skip to main content -
+
} /> } /> diff --git a/src/components/layout/MobileMenu.tsx b/src/components/layout/MobileMenu.tsx index e7a169d..addcf49 100644 --- a/src/components/layout/MobileMenu.tsx +++ b/src/components/layout/MobileMenu.tsx @@ -1,3 +1,5 @@ +import { useEffect, useRef } from 'react'; + type Props = { menuItems: string[]; isOpen: boolean; @@ -5,19 +7,29 @@ type Props = { }; export default function MobileMenu({ menuItems, isOpen, onItemClick }: Props) { + const firstButtonRef = useRef(null); + + useEffect(() => { + if (isOpen && firstButtonRef.current) { + firstButtonRef.current.focus(); + } + }, [isOpen]); + if (!isOpen) return null; return ( -
- {menuItems.map((item) => ( +
+ ); } diff --git a/src/components/layout/Navigation.tsx b/src/components/layout/Navigation.tsx index 3b57826..29bce7b 100644 --- a/src/components/layout/Navigation.tsx +++ b/src/components/layout/Navigation.tsx @@ -45,13 +45,16 @@ export default function Navbar({ ))} {/* Burger Button für Mobile */} -
setMenuOpen(!menuOpen)} > {[0, 1, 2].map(i => )} -
+ {texts.codeButtonText} @@ -90,6 +91,7 @@ export default function ProjectsSection(props: ProjectsSectionProps = {}) { target="_blank" rel="noopener noreferrer" className="projects-section__action-button projects-section__action-button--primary" + aria-label={`View ${project.title} live demo`} > {texts.liveButtonText} diff --git a/src/components/sections/ServicesSection.tsx b/src/components/sections/ServicesSection.tsx index 2902894..13cff4f 100644 --- a/src/components/sections/ServicesSection.tsx +++ b/src/components/sections/ServicesSection.tsx @@ -18,7 +18,7 @@ export default function ServicesSection(props: ServicesSectionProps = {}) { services = [ { 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.", colorClass: "primary" }, diff --git a/src/scss/globals.scss b/src/scss/globals.scss index 0f2b5da..16aa09a 100644 --- a/src/scss/globals.scss +++ b/src/scss/globals.scss @@ -3,6 +3,27 @@ @forward 'variables'; @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 %button-base { @include button-base; @@ -39,15 +60,15 @@ border-radius: 8px; transition: all 0.3s ease; text-decoration: none; - + &:focus { outline: none; } - + &:active { transform: translateY(0); } - + &:disabled { opacity: 0.5; cursor: not-allowed; @@ -72,4 +93,3 @@ cursor: pointer; font: inherit; } - diff --git a/src/scss/layout/topbar.scss b/src/scss/layout/topbar.scss index 8e79f0f..69f0085 100644 --- a/src/scss/layout/topbar.scss +++ b/src/scss/layout/topbar.scss @@ -32,6 +32,7 @@ &__burger-button { @include globals.flex-center(); + flex-direction: column; justify-content: space-between; width: 20px; height: 17px; @@ -42,31 +43,28 @@ &__item { display: block; - height: 4px; + height: 3px; width: 100%; background-color: var(--color-text); border-radius: 2px; transition: all 0.3s ease; - margin-top: 2px; + margin: 1px 0; } - &:hover .navbar__container__burger-button__item { + &:hover &__item { background-color: var(--color-primary); } &.active { - .navbar__container__burger-button__item:nth-child(1) { + &__item:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); } - .navbar__container__burger-button__item:nth-child(2) { + &__item:nth-child(2) { opacity: 0; } - .navbar__container__burger-button__item:nth-child(3) { + &__item:nth-child(3) { transform: rotate(-45deg) translate(5px, -5px); } - .navbar__container__burger-button__item:nth-child(n + 4) { - opacity: 0; - } } }