From f53c559c40f61b3606e8876464103b07b6adbdc3 Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Mon, 14 Sep 2026 17:07:10 +0200 Subject: [PATCH] Keep the state layer off a disabled menu item Plan step 36. The old `state-layer` utility hid its layer on `[aria-disabled='true']`; the menu item's hand-written layer dropped that, so a disabled item, which stays focusable, drew the 10% focus layer. Its 38% ink now reads --md-sys-state-disabled-content-opacity, as button.css does, instead of a literal. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9 --- resources/css/components/menu-item.css | 9 +++++++-- tests/Feature/Components/MenuTest.php | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/resources/css/components/menu-item.css b/resources/css/components/menu-item.css index f4c6daa5..adc948e1 100644 --- a/resources/css/components/menu-item.css +++ b/resources/css/components/menu-item.css @@ -91,6 +91,11 @@ &:active::before { opacity: var(--md-sys-state-pressed-state-layer-opacity); } + + /* A disabled item stays focusable and keeps its focus ring, but has no state layer. */ + &[aria-disabled='true']::before { + display: none; + } } [data-md-menu-item]:first-child { @@ -124,7 +129,7 @@ } [data-md-menu-item][aria-disabled='true'] { - --md-menu-item-ink: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent); + --md-menu-item-ink: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent); pointer-events: none; color: var(--md-menu-item-ink); @@ -135,7 +140,7 @@ } [data-md-menu-item][aria-disabled='true'] [data-md-icon] { - color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent) !important; + color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent) !important; } [data-md-menu-item-text] { diff --git a/tests/Feature/Components/MenuTest.php b/tests/Feature/Components/MenuTest.php index 31f587a8..529e299a 100644 --- a/tests/Feature/Components/MenuTest.php +++ b/tests/Feature/Components/MenuTest.php @@ -130,7 +130,8 @@ it('links an item, and keeps a disabled one out of reach', function () { ->assertSee('aria-disabled="true"', false) ->assertSee('data-md-menu-item', false); - expect(ComponentStylesheet::read('menu-item')->declarations("[data-md-menu-item][aria-disabled='true']"))->toMatchArray(['pointer-events' => 'none']); + expect(ComponentStylesheet::read('menu-item')->declarations("[data-md-menu-item][aria-disabled='true']"))->toMatchArray(['pointer-events' => 'none']) + ->and(ComponentStylesheet::read('menu-item')->declarations("[data-md-menu-item][aria-disabled='true']::before"))->toBe(['display' => 'none']); }); it('separates and labels groups', function () { @@ -178,7 +179,7 @@ it('adds icon-class to the leading icon, over its own colour but not over disabl // icon-class is a caller class, unlayered, so it always outranks the item's own layered // ink — except for a disabled item, whose icon rule is !important. ->and(ComponentStylesheet::read('menu-item')->declarations('[data-md-menu-item] [data-md-icon]'))->toBe(['color' => 'var(--md-menu-item-ink)']) - ->and(ComponentStylesheet::read('menu-item')->declarations("[data-md-menu-item][aria-disabled='true'] [data-md-icon]"))->toBe(['color' => 'color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent) !important']); + ->and(ComponentStylesheet::read('menu-item')->declarations("[data-md-menu-item][aria-disabled='true'] [data-md-icon]"))->toBe(['color' => 'color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent) !important']); }); it('opens a submenu beside the item that holds it', function () {