Skip to content

Fix disabled pagination controls accessibility - #2867

Merged
ganyicz merged 1 commit into
mainfrom
filip/fix-pagination-disabled-aria
Oct 27, 2026
Merged

ganyicz merged 1 commit into
mainfrom
filip/fix-pagination-disabled-aria

Conversation

@ganyicz

@ganyicz ganyicz commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

The scenario

When a user renders <flux:pagination :paginator="$paginator" /> on the first or last page, axe reports a serious aria-prohibited-attr violation on the unavailable Previous/Next control.

@php
    $paginator = new \Illuminate\Pagination\LengthAwarePaginator(
        items: range(1, 15),
        total: 17,
        perPage: 15,
        currentPage: 1,
    );
@endphp

<flux:pagination :paginator="$paginator" />

The problem

When there's no previous/next page, the control is rendered as a plain div with an aria-label. A div has no role, and ARIA doesn't allow naming elements without a role, so the label is ignored by screen readers and flagged by axe:

<div aria-disabled="true" aria-label="&laquo; Previous" class="flex justify-center items-center size-6 ...">
    <!-- chevron icon -->
</div>
aria-prohibited-attr (serious)
aria-label attribute cannot be used on a div with no valid role attribute.

Because of this, the disabled control is not exposed at all, while the enabled one is a button. Screen reader users on the first page only hear "Next" and have no way of telling that "Previous" exists but is unavailable.

The disabled Next control was also missing aria-disabled, so even with a role it wouldn't be announced as disabled.

The simple layout (used for Paginator and CursorPaginator) has the same problem in a different shape: its disabled controls have no label at all, and its enabled buttons have no accessible name either, which axe reports as button-name (critical).

The solution

Render the unavailable controls as native disabled buttons, keeping the same classes and icons:

<button type="button" disabled aria-label="{{ __('pagination.previous') }}" class="flex justify-center items-center size-6 rounded-[6px] text-zinc-300 dark:text-zinc-500">
    <flux:icon.chevron-left variant="micro" class="rtl:hidden" />
    <flux:icon.chevron-right variant="micro" class="hidden rtl:inline" />
</button>

This matches the enabled controls, so screen readers now announce both as buttons, with the unavailable one marked as disabled. A native disabled button is also skipped in the tab order and can't be clicked, just like the div before, and Tailwind's preflight resets the default button styles, so it looks exactly the same.

In the simple layout, the disabled controls get the same treatment and the enabled buttons get the same aria-labels as in the default layout.

Accessibility tree on the first page, before:

- text: Showing 1 to 15 of 17 results 1
- button "2"
- button "Next &raquo;"

After:

- text: Showing 1 to 15 of 17 results
- button "&laquo; Previous" [disabled]
- text: "1"
- button "2"
- button "Next &raquo;"

Fixes #2771

@ganyicz

ganyicz commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

No actionable findings in PR #2867 (d9c1fe0).

Reviewed stubs/resources/views/flux/pagination.blade.php across simple, cursor, mobile, and desktop pagination. Native disabled buttons provide correct accessibility semantics; labels cover all Previous/Next controls. No styling or Livewire directive-lifecycle issues identified.

Source review only; no runtime or accessibility tests run.

@ganyicz
ganyicz merged commit 5be57f7 into main Oct 27, 2026
1 check passed
@ganyicz
ganyicz deleted the filip/fix-pagination-disabled-aria branch October 27, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pagination applies aria-label to a generic div for disabled controls

1 participant