Files
paint-ui/components/editor/theme-toggle.tsx
Sebastian Krüger b2a0b92209 feat(a11y): add comprehensive accessibility improvements
Enhanced accessibility throughout the application:

ARIA Labels & Roles:
- Tool palette: Added role="toolbar", aria-label, aria-pressed states
- Theme toggle: Added aria-label, aria-pressed, aria-hidden on icons
- File menu: Added role="menu", aria-expanded, aria-haspopup, role="menuitem"
- Menu separators: Added role="separator"

Focus Indicators:
- Global :focus-visible styles with ring outline
- Consistent focus:ring-2 styling on interactive elements
- Enhanced focus states on buttons, inputs, selects, textareas
- Offset outlines for better visibility

Keyboard Navigation:
- Proper focus management on menu items
- Focus styles that don't interfere with mouse interactions
- Accessible button states with aria-pressed

Visual Improvements:
- Clear 2px outline on focused elements
- Ring color using theme variables (--ring)
- 2px outline offset for spacing
- Focus visible only for keyboard navigation

These improvements ensure the application is fully navigable via keyboard
and properly announced by screen readers, meeting WCAG 2.1 Level AA standards.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 15:49:20 +01:00

59 lines
1.7 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { Moon, Sun } from 'lucide-react';
export function ThemeToggle() {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const currentTheme = savedTheme === 'dark' || (!savedTheme && prefersDark) ? 'dark' : 'light';
setTheme(currentTheme);
}, []);
const toggleTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
if (newTheme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
};
// Prevent hydration mismatch
if (!mounted) {
return (
<button
className="rounded-md p-2 text-muted-foreground"
disabled
aria-label="Theme toggle loading"
>
<Sun className="h-4 w-4" />
</button>
);
}
return (
<button
onClick={toggleTheme}
className="rounded-md p-2 hover:bg-accent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
aria-pressed={theme === 'dark'}
title={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
>
{theme === 'light' ? (
<Moon className="h-4 w-4" aria-hidden="true" />
) : (
<Sun className="h-4 w-4" aria-hidden="true" />
)}
</button>
);
}