Skip to content

Commit cd35420

Browse files
committed
Frontend: accessibility and component improvements
Replace div[role=button] with semantic button elements, add missing aria labels, use label elements for toggle controls, fix a11y warnings across components, dialogs, editors, and pickers.
1 parent c8ec522 commit cd35420

23 files changed

Lines changed: 94 additions & 43 deletions

frontend/src/lib/components/AlertBox.svelte

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
import { AlertCircle, CircleCheck, Info, AlertTriangle } from 'lucide-svelte';
33
44
let {
5-
variant = 'error', // 'error', 'warning', 'info', 'success', 'neutral'
5+
variant: variantProp = 'error', // 'error', 'warning', 'info', 'success', 'neutral'
6+
type = undefined, // Alias for variant
67
message = '',
78
showIcon = true,
89
class: className = '',
9-
children
10+
children = undefined
1011
} = $props();
1112
13+
const variant = $derived(type || variantProp);
14+
1215
const styles = $derived({
1316
error: {
1417
borderColor: 'var(--ds-border-danger)',

frontend/src/lib/components/Avatar.svelte

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,29 @@
7474
</script>
7575
7676
{#if src}
77-
<img
78-
{src}
79-
alt={computedAlt}
80-
class="{baseClasses} object-cover"
81-
onclick={onclick}
82-
/>
77+
{#if onclick}
78+
<button type="button" class="p-0 border-0 bg-transparent cursor-pointer" onclick={onclick}>
79+
<img
80+
{src}
81+
alt={computedAlt}
82+
class="{baseClasses} object-cover"
83+
/>
84+
</button>
85+
{:else}
86+
<img
87+
{src}
88+
alt={computedAlt}
89+
class="{baseClasses} object-cover"
90+
/>
91+
{/if}
8392
{:else}
84-
<!-- svelte-ignore a11y_click_events_have_key_events -->
85-
<!-- svelte-ignore a11y_no_static_element_interactions -->
86-
<div class={baseClasses} style={variantStyles[variant]} onclick={onclick}>
87-
{initials}
88-
</div>
93+
{#if onclick}
94+
<button type="button" class="appearance-none bg-transparent border-none p-0 m-0 font-[inherit] text-[inherit] cursor-pointer {baseClasses}" style={variantStyles[variant]} onclick={onclick}>
95+
{initials}
96+
</button>
97+
{:else}
98+
<div class={baseClasses} style={variantStyles[variant]}>
99+
{initials}
100+
</div>
101+
{/if}
89102
{/if}

frontend/src/lib/components/Button.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
onclick = null, // Svelte 5 style click handler
2020
hotkeyConfig = null, // { key: 'a', guard?: () => boolean }
2121
class: className = '',
22-
children
22+
children = undefined
2323
} = $props();
2424
export { className as class };
2525

frontend/src/lib/components/Card.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
class: className = '',
1212
style: userStyle = '',
1313
children,
14-
header, // Snippet for header content
15-
footer // Snippet for footer content
14+
header = undefined, // Snippet for header content
15+
footer = undefined // Snippet for footer content
1616
} = $props();
1717
1818
// Variant styles using design tokens

frontend/src/lib/components/DataTable.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
export let columns = []; // Array of column definitions: { key, label, width?, align?, sortable? }
99
export let data = []; // Array of data objects
1010
export let keyField = 'id'; // Field to use as unique key
11+
export let loading = false; // Loading state
1112
export let emptyMessage = '';
1213
export let emptyDescription = ''; // Optional description for empty state
1314
export let emptyIcon = null; // Lucide icon component

frontend/src/lib/components/Link.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* - Optional onClick for modal/custom behavior while preserving link benefits
1313
*/
1414
15-
export let href;
15+
export let href = '';
1616
export let active = false;
1717
export let disabled = false;
1818
export let onClick = null; // Optional click handler for modals or custom behavior
@@ -25,7 +25,7 @@
2525
export { className as class };
2626
2727
// Expose the anchor element reference for parent components
28-
let anchorElement;
28+
let anchorElement = undefined;
2929
export { anchorElement as element };
3030
3131

frontend/src/lib/components/ModalBackdrop.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
transition = true,
1616
ariaLabelledBy = undefined,
1717
onclose = undefined,
18-
children,
18+
children = undefined,
1919
} = $props();
2020
2121
let backdropRef = $state(null);

frontend/src/lib/components/Pagination.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
export let pageSizeOptions = [10, 25, 50, 100];
1616
export let compact = false; // For smaller spaces
1717
export let hasGradient = false; // Gradient background awareness
18+
export let onpageChange = null; // Callback prop (Svelte 5 callers)
19+
export let onpageSizeChange = null; // Callback prop (Svelte 5 callers)
1820
1921
// Gradient-aware text styles (using inline styles instead of Tailwind classes)
2022
$: textStyle = hasGradient ? 'color: var(--ds-text);' : 'color: var(--ds-text);';
@@ -56,13 +58,15 @@
5658
function goToPage(page) {
5759
if (page >= 1 && page <= totalPages && page !== currentPage) {
5860
dispatch('pageChange', { page, itemsPerPage });
61+
onpageChange?.({ detail: { page, itemsPerPage } });
5962
}
6063
}
61-
64+
6265
function handlePageSizeChange(event) {
6366
const newPageSize = parseInt(event.target.value);
6467
const newPage = Math.min(currentPage, Math.ceil(Math.min(totalItems, maxItems) / newPageSize));
6568
dispatch('pageSizeChange', { page: newPage, itemsPerPage: newPageSize });
69+
onpageSizeChange?.({ detail: { page: newPage, itemsPerPage: newPageSize } });
6670
}
6771
</script>
6872
@@ -80,7 +84,7 @@
8084
8185
{#if showPageSizes && !compact}
8286
<div class="flex items-center gap-2 text-sm" style={textStyle}>
83-
<label>{t('components.pagination.itemsPerPage')}</label>
87+
<span>{t('components.pagination.itemsPerPage')}</span>
8488
<div style="min-width: 100px;">
8589
<BasePicker
8690
value={itemsPerPage}

frontend/src/lib/components/Tabs.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script>
2-
let { tabs = [], activeTab = $bindable(''), onTabChange = null, children } = $props();
2+
let { tabs = [], activeTab = $bindable(''), onTabChange = null, type = 'default', children } = $props();
33
44
// Initialize activeTab to first tab if not set
55
$effect(() => {

frontend/src/lib/components/Toggle.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@
2626
</script>
2727
2828
{#if label}
29-
<div
29+
<label
3030
class="inline-flex items-center gap-3 {labelPosition === 'left' ? 'flex-row-reverse' : ''} {disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
31-
role="group"
3231
>
3332
<button
3433
type="button"
@@ -46,12 +45,13 @@
4645
></span>
4746
</button>
4847
<span class="text-sm text-[var(--ds-text)]">{label}</span>
49-
</div>
48+
</label>
5049
{:else}
5150
<button
5251
type="button"
5352
role="switch"
5453
aria-checked={checked}
54+
aria-label="Toggle"
5555
{disabled}
5656
class="relative inline-flex items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[var(--ds-border-focused)]
5757
disabled:opacity-50 disabled:cursor-not-allowed {currentSize.button} {className}"

0 commit comments

Comments
 (0)