Improve accessibility: keyboard-operable mobile nav, skip link, focus styles
- Replace the checkbox-hack mobile menu (display:none checkbox was unreachable by keyboard/screen readers) with a real button using aria-expanded/aria-controls - Add a skip-to-content link - Add a consistent :focus-visible outline across interactive elements - Underline prose links by default instead of only on hover (WCAG 1.4.1 use-of-color), keep nav/button links unstyled since they're already visually distinct - Respect prefers-reduced-motion for smooth scrolling Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
cf8d6a65ea
commit
807022a62a
4 changed files with 97 additions and 16 deletions
|
|
@ -51,6 +51,7 @@ const footer = site.footer;
|
||||||
.footer-nav a {
|
.footer-nav a {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-nav a:hover {
|
.footer-nav a:hover {
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,20 @@ const nav = site.nav;
|
||||||
<img src={nav.logo} alt={nav.logoAlt} width="320" height="60" class="nav-logo-img" />
|
<img src={nav.logo} alt={nav.logoAlt} width="320" height="60" class="nav-logo-img" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<input type="checkbox" id="nav-toggle" class="nav-toggle-checkbox" />
|
<button
|
||||||
<label for="nav-toggle" class="nav-toggle" aria-label="Menü öffnen/schließen">
|
type="button"
|
||||||
|
class="nav-toggle"
|
||||||
|
id="nav-toggle"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="nav-links"
|
||||||
|
aria-label="Menü öffnen"
|
||||||
|
>
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</button>
|
||||||
|
|
||||||
<nav class="nav-links" aria-label="Hauptnavigation">
|
<nav class="nav-links" id="nav-links" aria-label="Hauptnavigation">
|
||||||
<ul>
|
<ul>
|
||||||
{nav.links.map((item) => (
|
{nav.links.map((item) => (
|
||||||
<li>
|
<li>
|
||||||
|
|
@ -50,9 +56,6 @@ const nav = site.nav;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
}
|
|
||||||
|
|
||||||
.nav-logo:hover {
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,11 +81,11 @@ const nav = site.nav;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links ul a:hover {
|
.nav-links ul a:hover {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-cta {
|
.nav-cta {
|
||||||
|
|
@ -92,10 +95,6 @@ const nav = site.nav;
|
||||||
padding: var(--space-3) var(--space-5);
|
padding: var(--space-3) var(--space-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-toggle-checkbox {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-toggle {
|
.nav-toggle {
|
||||||
display: none;
|
display: none;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -103,6 +102,9 @@ const nav = site.nav;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,8 +151,34 @@ const nav = site.nav;
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-toggle-checkbox:checked ~ .nav-links {
|
.nav-links.is-open {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const toggle = document.getElementById('nav-toggle');
|
||||||
|
const links = document.getElementById('nav-links');
|
||||||
|
|
||||||
|
function setOpen(open) {
|
||||||
|
links?.classList.toggle('is-open', open);
|
||||||
|
toggle?.setAttribute('aria-expanded', String(open));
|
||||||
|
toggle?.setAttribute('aria-label', open ? 'Menü schließen' : 'Menü öffnen');
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle?.addEventListener('click', () => {
|
||||||
|
setOpen(!links?.classList.contains('is-open'));
|
||||||
|
});
|
||||||
|
|
||||||
|
links?.addEventListener('click', (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (target instanceof HTMLElement && target.tagName === 'A') {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape') setOpen(false);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,9 @@ const fontsHref = googleFontsHref(theme);
|
||||||
<style set:html={themeCss(theme)} />
|
<style set:html={themeCss(theme)} />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<a href="#main-content" class="skip-link">Zum Inhalt springen</a>
|
||||||
<Nav />
|
<Nav />
|
||||||
<main>
|
<main id="main-content">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
|
||||||
|
|
@ -123,11 +123,23 @@ p {
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
text-decoration: none;
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 0.15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
text-decoration: underline;
|
color: var(--color-primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus-visible,
|
||||||
|
button:focus-visible,
|
||||||
|
input:focus-visible,
|
||||||
|
textarea:focus-visible,
|
||||||
|
summary:focus-visible,
|
||||||
|
[tabindex]:focus-visible {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
|
|
@ -147,6 +159,25 @@ textarea {
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: var(--space-4);
|
||||||
|
transform: translateY(-150%);
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: var(--color-primary-contrast);
|
||||||
|
padding: var(--space-3) var(--space-5);
|
||||||
|
border-radius: 0 0 var(--radius-sm) var(--radius-sm);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: transform var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-link:focus {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
Layout-Hilfsklassen
|
Layout-Hilfsklassen
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
@ -249,3 +280,23 @@ textarea {
|
||||||
border-bottom: 1px dashed var(--color-flag);
|
border-bottom: 1px dashed var(--color-flag);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Bewegungsreduktion
|
||||||
|
Respektiert die Systemeinstellung fuer Nutzer:innen mit vestibulaeren
|
||||||
|
Beeintraechtigungen (WCAG 2.3.3).
|
||||||
|
========================================================================== */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue