diff --git a/frontend/src/lib/components/Pagination.svelte b/frontend/src/lib/components/Pagination.svelte index 0edf0fb7..01659e9e 100644 --- a/frontend/src/lib/components/Pagination.svelte +++ b/frontend/src/lib/components/Pagination.svelte @@ -16,8 +16,10 @@ const { page, totalPages, makeHref, ariaLabel }: Props = $props(); const hasPrev = $derived(page > 0); const hasNext = $derived(page < totalPages - 1); -const linkBase = - 'inline-flex min-h-[44px] min-w-[44px] items-center justify-center gap-1.5 rounded-sm border border-line bg-white px-4 py-2 font-sans text-sm font-bold text-ink transition-colors hover:bg-surface focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:ring-offset-2 focus-visible:outline-none aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-40'; +const controlBase = + 'inline-flex min-h-[44px] min-w-[44px] items-center justify-center gap-1.5 rounded-sm border border-line bg-white px-4 py-2 font-sans text-sm font-bold text-ink'; +const linkBase = `${controlBase} transition-colors hover:bg-surface focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:ring-offset-2 focus-visible:outline-none`; +const disabledBase = `${controlBase} cursor-not-allowed opacity-40`; {#if totalPages > 1} @@ -25,16 +27,30 @@ const linkBase = aria-label={ariaLabel ?? m.pagination_nav_label()} class="mt-6 flex flex-col items-center gap-3 sm:flex-row sm:justify-between" > - - - {m.pagination_prev()} - + + {#if hasPrev} + + + {m.pagination_prev()} + + {:else} + + {/if} - - {m.pagination_next()} - - + {#if hasNext} + + {m.pagination_next()} + + + {:else} + + {/if} {/if} diff --git a/frontend/src/lib/components/Pagination.svelte.spec.ts b/frontend/src/lib/components/Pagination.svelte.spec.ts index 68af1558..90bf8532 100644 --- a/frontend/src/lib/components/Pagination.svelte.spec.ts +++ b/frontend/src/lib/components/Pagination.svelte.spec.ts @@ -33,12 +33,14 @@ describe('Pagination', () => { await expect.element(prev).toHaveAttribute('href', '/documents?page=3'); }); - it('disables prev on page 0 (no href, aria-disabled="true")', async () => { + it('renders disabled prev as an aria-hidden non-link so screen readers skip it', async () => { render(Pagination, { page: 0, totalPages: 3, makeHref }); const prev = page.getByTestId('pagination-prev'); - await expect.element(prev).toHaveAttribute('aria-disabled', 'true'); + // Not a link — no href, no role=link await expect.element(prev).not.toHaveAttribute('href'); + // Hidden from assistive tech — AT shouldn't read "Previous, link, disabled" + await expect.element(prev).toHaveAttribute('aria-hidden', 'true'); }); it('renders next as a link pointing at page + 1 when not on last page', async () => { @@ -48,12 +50,12 @@ describe('Pagination', () => { await expect.element(next).toHaveAttribute('href', '/documents?page=1'); }); - it('disables next on the last page (no href, aria-disabled="true")', async () => { + it('renders disabled next as an aria-hidden non-link on the last page', async () => { render(Pagination, { page: 2, totalPages: 3, makeHref }); const next = page.getByTestId('pagination-next'); - await expect.element(next).toHaveAttribute('aria-disabled', 'true'); await expect.element(next).not.toHaveAttribute('href'); + await expect.element(next).toHaveAttribute('aria-hidden', 'true'); }); it('calls makeHref with p-1 and p+1', async () => {