Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
## 2026-02-15 - [Semantic Form Labels]
**Learning:** Even with clear visual labels, missing `for` attributes on `<label>` elements prevents proper association with inputs for assistive technologies.
**Action:** Explicitly link labels and inputs using `for` and `id` attributes in all form components.

## 2025-02-25 - [Table Sorting Accessibility]
**Learning:** Enhancing JavaScript-generated table headers with `role="button"`, `tabindex="0"`, and `onkeydown` handlers significantly improves keyboard accessibility. Using `aria-sort` on the parent `th` provides essential feedback to screen reader users about the current sort state.
**Action:** Always include keyboard support and ARIA sort attributes when implementing client-side table sorting.
2 changes: 1 addition & 1 deletion app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@
<!-- Scanner Animation -->
<div class="absolute w-full h-0.5 bg-primary/50 -top-1 left-0 animate-scanner after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-primary/30"></div>
</div>
</button>
</div>

<!-- Trennlinie -->
<div class="sidebar-divider"></div>
Expand Down
22 changes: 20 additions & 2 deletions app/templates/shared/list_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ <h3 class="card-title text-lg">
document.querySelectorAll('th').forEach(th => {
if (!th.classList.contains('no-sort')) {
const wrapper = document.createElement('div');
wrapper.className = 'flex items-center gap-2 cursor-pointer select-none';
wrapper.className = 'flex items-center gap-2 cursor-pointer select-none focus:outline-none focus:ring-2 focus:ring-primary/50 rounded-lg p-1 -m-1';
wrapper.setAttribute('role', 'button');
wrapper.setAttribute('tabindex', '0');
const colName = th.textContent.trim();
wrapper.setAttribute('aria-label', `Sortieren nach ${colName}`);

wrapper.innerHTML = `
${th.innerHTML}
<span class="sort-icons opacity-50">
Expand All @@ -105,9 +110,17 @@ <h3 class="card-title text-lg">
th.innerHTML = '';
th.appendChild(wrapper);

wrapper.addEventListener('click', () => {
const sortHandler = () => {
const column = Array.from(th.parentElement.children).indexOf(th);
sortTable(column, th);
};

wrapper.addEventListener('click', sortHandler);
wrapper.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
sortHandler();
}
});
}
});
Expand All @@ -117,13 +130,18 @@ <h3 class="card-title text-lg">
const tbody = table.querySelector('tbody');
const rows = Array.from(tbody.querySelectorAll('tr'));

// Reset all th aria-sort
table.querySelectorAll("th").forEach(header => {
header.removeAttribute("aria-sort");
});
// Update Sort-Direction
if (currentSort.column === column) {
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
} else {
currentSort.column = column;
currentSort.direction = 'asc';
}
th.setAttribute("aria-sort", currentSort.direction === "asc" ? "ascending" : "descending");

// Update Sort-Icons
table.querySelectorAll('.sort-icons').forEach(icon => {
Expand Down