From 7f678569167bd54cf7cf45a4a0e72dec94c9fda5 Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Mon, 14 Sep 2026 05:54:25 +0200 Subject: [PATCH] Stop a disabled list item answering the keyboard Plan step 12, containment.md C-02. `pointer-events-none` blocked the pointer but not Tab and Enter: the item's `` stayed in the tab order and navigated, and nothing announced it disabled. A disabled item now renders its title as text, is no longer a `data-list-row`, and carries `aria-disabled="true"`. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9 --- resources/views/components/list-item.blade.php | 7 +++++-- tests/Feature/Components/ListTest.php | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/resources/views/components/list-item.blade.php b/resources/views/components/list-item.blade.php index f9791dbe..c87a38ad 100644 --- a/resources/views/components/list-item.blade.php +++ b/resources/views/components/list-item.blade.php @@ -13,7 +13,9 @@ `link` makes the whole item the link. Otherwise, to make it open something while its trailing controls keep their own presses, give it `data-list-row` and put `data-list-open` on the one control that opens — see resources/js/list-rows.js. `selected` (true) is M3's selected item, - in secondary-container; `disabled` greys it. --}} + in secondary-container; `disabled` greys it to 38%, drops its link and announces it + `aria-disabled` — M3's states model treats disabled as not interactive, so a disabled item + answers neither the pointer nor the keyboard. --}} @props([ 'title' => null, @@ -32,7 +34,7 @@ ]) @php - $isLink = filled($link); + $isLink = filled($link) && ! $disabled; $lines = (filled($overline) ? 1 : 0) + (filled($description) ? 1 : 0); $initials = filled($avatar) && ! str_contains((string) $avatar, '/') && ! str_contains((string) $avatar, '.'); @endphp @@ -42,6 +44,7 @@ data-list-item @if ($isLink) data-list-row @endif @if ($selected) data-selected @endif + @if ($disabled) aria-disabled="true" @endif {{ $attributes->class([ 'relative flex items-center gap-3 px-4 text-on-surface', 'min-h-14 py-2' => $lines === 0, diff --git a/tests/Feature/Components/ListTest.php b/tests/Feature/Components/ListTest.php index 7da5a031..c01436ef 100644 --- a/tests/Feature/Components/ListTest.php +++ b/tests/Feature/Components/ListTest.php @@ -38,6 +38,15 @@ it('makes a linked item a row that opens from anywhere', function () { ->toContain('wire:navigate'); }); +it('leaves a disabled item out of the keyboard and announces it disabled', function () { + expect((string) $this->blade('')) + ->toContain('aria-disabled="true"') + ->toContain('pointer-events-none text-on-surface/38') + ->not->toContain('href="/settings"') + ->not->toContain('data-list-open') + ->not->toContain('data-list-row'); +}); + it('marks a selected item and takes controls in its slots', function () { expect((string) $this->blade('')) ->toContain('data-selected')