fix(a11y): skip-link target, priority labels, greeting tokens

- Rename #page-content to #main-content so skip-to-content link
  targets the semantic <main> landmark
- Add sr-only priority labels to dashboard task items for screen
  readers (WCAG 1.4.1 color-not-only)
- Replace hardcoded hex in greeting gradient with accent tokens
  so dark mode themes the banner correctly
- Replace hardcoded gap: 2px with --space-0h token
- Bump version to 0.7.2
This commit is contained in:
Ulas
2026-04-04 06:31:21 +02:00
parent 6bc4c46f03
commit 70c1291ae7
5 changed files with 29 additions and 9 deletions
+10
View File
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.7.2] - 2026-04-04
### Accessibility
- Rename `#page-content` to `#main-content` so the existing skip-to-content link targets the semantic `<main>` landmark correctly
- Add `sr-only` priority labels to dashboard task items - screen readers now announce priority level instead of relying on color alone (WCAG 1.4.1)
### Fixed
- Replace hardcoded hex values in greeting widget gradient with `--color-accent-active` / `--color-accent` tokens - dark mode now correctly themes the greeting banner
- Replace hardcoded `gap: 2px` with `--space-0h` token in greeting widget
## [0.7.1] - 2026-04-04
### Security
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "oikos",
"version": "0.7.1",
"version": "0.7.2",
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
"main": "server/index.js",
"type": "module",
+9 -1
View File
@@ -56,6 +56,13 @@ function formatDueDate(dateStr) {
};
}
const PRIORITY_LABELS = () => ({
urgent: t('tasks.priorityUrgent'),
high: t('tasks.priorityHigh'),
medium: t('tasks.priorityMedium'),
low: t('tasks.priorityLow'),
});
const MEAL_LABELS = () => ({
breakfast: t('meals.typeBreakfast'),
lunch: t('meals.typeLunch'),
@@ -160,7 +167,8 @@ function renderUrgentTasks(tasks) {
const due = formatDueDate(t.due_date);
return `
<div class="task-item" data-route="/tasks" role="button" tabindex="0">
<div class="task-item__priority task-item__priority--${t.priority}"></div>
<div class="task-item__priority task-item__priority--${t.priority}" aria-hidden="true"></div>
<span class="sr-only">${PRIORITY_LABELS()[t.priority] ?? t.priority}</span>
<div class="task-item__content">
<div class="task-item__title">${esc(t.title)}</div>
${due ? `<div class="task-item__meta ${due.overdue ? 'task-item__meta--overdue' : ''}">${due.text}</div>` : ''}
+5 -5
View File
@@ -177,13 +177,13 @@ async function renderPage(route, previousPath = null) {
}
// App-Shell einmalig aufbauen BEVOR render() aufgerufen wird -
// page-content muss im DOM existieren damit document.getElementById()
// main-content muss im DOM existieren damit document.getElementById()
// in Seiten-Modulen funktioniert.
if (!document.querySelector('.nav-bottom') && currentUser) {
renderAppShell(app);
}
const content = document.getElementById('page-content') || app;
const content = document.getElementById('main-content') || app;
// Richtung bestimmen (previousPath ist der alte Pfad vor der Navigation)
const direction = getDirection(previousPath, route.path);
@@ -216,7 +216,7 @@ async function renderPage(route, previousPath = null) {
*/
function renderAppShell(container) {
container.innerHTML = `
<a href="#page-content" class="sr-only">${t('common.skipToContent')}</a>
<a href="#main-content" class="sr-only">${t('common.skipToContent')}</a>
<nav class="nav-sidebar" aria-label="${t('nav.main')}">
<div class="nav-sidebar__logo"><span>Oikos</span></div>
<div class="nav-sidebar__items" role="list">
@@ -224,7 +224,7 @@ function renderAppShell(container) {
</div>
</nav>
<main class="app-content" id="page-content" aria-live="polite">
<main class="app-content" id="main-content" aria-live="polite">
</main>
<nav class="nav-bottom" aria-label="${t('nav.navigation')}">
@@ -416,7 +416,7 @@ window.addEventListener('auth:expired', () => {
window.addEventListener('locale-changed', () => {
const navSidebarItems = document.querySelector('.nav-sidebar__items');
const navBottomPages = document.querySelectorAll('.nav-bottom__page');
const skipLink = document.querySelector('.sr-only[href="#page-content"]');
const skipLink = document.querySelector('.sr-only[href="#main-content"]');
const navSidebar = document.querySelector('.nav-sidebar');
const navBottom = document.querySelector('.nav-bottom');
+4 -2
View File
@@ -89,7 +89,9 @@
* Begrüßungs-Widget
* -------------------------------------------------------- */
.widget-greeting {
background: linear-gradient(135deg, #1D4ED8, #2563EB);
--greeting-from: var(--color-accent-active);
--greeting-to: var(--color-accent);
background: linear-gradient(135deg, var(--greeting-from), var(--greeting-to));
border-radius: var(--radius-md);
padding: var(--space-4) var(--space-5);
color: #ffffff;
@@ -99,7 +101,7 @@
.widget-greeting__content {
display: flex;
flex-direction: column;
gap: 2px;
gap: var(--space-0h);
}
.widget-greeting__title {