From 5800ab1fe119820e8da88074b654c9e1ef2f9eba Mon Sep 17 00:00:00 2001 From: Andreas Reinhold / reini Date: Tue, 15 Sep 2026 22:23:16 +0200 Subject: [PATCH] Leave a toolbar's filled button its own colours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M3's colour list for a standard toolbar ends "Standard button (Primary)" and the vibrant list ends "Standard button (On primary container)" (N-13) — both name the *standard* button. The two rules painted every icon button in the toolbar instead, so a button that brings its own container got a primary label on a primary fill and its icon disappeared: SealShare's floating navigation marks the current page with a filled button, and that button came out a blank circle. A button that fills its own container is now left alone — filled, tonal, or either of them selected — and keeps the label colour that goes with the fill. A disabled button still falls back to button.css's disabled ink in a toolbar of either colour, as before. AppBarTest pins the exclusion on both rules. Co-Authored-By: Claude Opus 5 (1M context) --- resources/css/components/toolbar.css | 12 ++++++++---- tests/Feature/Components/AppBarTest.php | 12 +++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/resources/css/components/toolbar.css b/resources/css/components/toolbar.css index 5e7257ba..5ddcc779 100644 --- a/resources/css/components/toolbar.css +++ b/resources/css/components/toolbar.css @@ -72,7 +72,7 @@ color: var(--md-sys-color-on-primary-container); } - [data-md-toolbar][data-md-vibrant] [data-md-icon-button]:not([aria-pressed='true'], :disabled, [aria-disabled='true']) { + [data-md-toolbar][data-md-vibrant] [data-md-icon-button]:not([data-md-variant='filled'], [data-md-variant='tonal'], [data-md-selected='true'], [aria-pressed='true'], :disabled, [aria-disabled='true']) { color: var(--md-sys-color-on-primary-container); } @@ -82,9 +82,13 @@ } /* M3's colour list for a standard toolbar ends "Standard button (Primary)", as the vibrant list - ends "Standard button (On primary container)" — the row above (N-13). A disabled button keeps - button.css's disabled colours in either. */ - [data-md-toolbar]:not([data-md-vibrant]) [data-md-icon-button]:not([aria-pressed='true'], :disabled, [aria-disabled='true']) { + ends "Standard button (On primary container)" — the row above (N-13). Both rows name the + *standard* button, so a button that fills its own container is left alone: it carries the + matching label colour (`filled` on-primary, `tonal` on-tone, either selected), and painting + that label primary put a primary icon on a primary fill — the current page's button in a + navigation toolbar came out a blank circle. A disabled button keeps button.css's disabled + colours in either. */ + [data-md-toolbar]:not([data-md-vibrant]) [data-md-icon-button]:not([data-md-variant='filled'], [data-md-variant='tonal'], [data-md-selected='true'], [aria-pressed='true'], :disabled, [aria-disabled='true']) { color: var(--md-sys-color-primary); } diff --git a/tests/Feature/Components/AppBarTest.php b/tests/Feature/Components/AppBarTest.php index 249fc88b..838235ed 100644 --- a/tests/Feature/Components/AppBarTest.php +++ b/tests/Feature/Components/AppBarTest.php @@ -212,9 +212,19 @@ it('keeps a toolbar at the bottom clear of the navigation bar', function () { ->and($css->declarations('[data-md-toolbar][data-md-vertical][data-md-toolbar-place=\'end\'], [data-md-toolbar-group][data-md-vertical][data-md-toolbar-place=\'end\']')) ->toBe(['inset-inline-end' => 'calc(var(--md-sys-measurement-space300) + var(--material-safe-right, env(safe-area-inset-right)))']) // A standard toolbar's standard buttons are primary (N-13). - ->and($css->declarations('[data-md-toolbar]:not([data-md-vibrant]) [data-md-icon-button]:not([aria-pressed=\'true\'], :disabled, [aria-disabled=\'true\'])')) + ->and($css->declarations('[data-md-toolbar]:not([data-md-vibrant]) [data-md-icon-button]:not([data-md-variant=\'filled\'], [data-md-variant=\'tonal\'], [data-md-selected=\'true\'], [aria-pressed=\'true\'], :disabled, [aria-disabled=\'true\'])')) ->toBe(['color' => 'var(--md-sys-color-primary)']); + // N-13 names the *standard* button, so a button that fills its own container keeps the label + // colour that goes with that fill: a filled button painted primary put a primary icon on a + // primary fill, and the current page's button in a navigation toolbar came out a blank circle. + foreach (['[data-md-toolbar]:not([data-md-vibrant]) [data-md-icon-button]', '[data-md-toolbar][data-md-vibrant] [data-md-icon-button]'] as $scope) { + preg_match_all('/'.preg_quote($scope, '/').':not\([^{]*\{[^}]*\bcolor:/', $css->css, $rules); + + expect($rules[0])->not->toBeEmpty() + ->each->toContain("[data-md-variant='filled']", "[data-md-variant='tonal']", "[data-md-selected='true']"); + } + // A disabled icon button keeps button.css's disabled ink in a toolbar of either colour. foreach (['[data-md-toolbar]:not([data-md-vibrant]) [data-md-icon-button]', '[data-md-toolbar][data-md-vibrant] [data-md-icon-button]'] as $scope) { preg_match_all('/'.preg_quote($scope, '/').'[^{]*\{[^}]*\bcolor:/', $css->css, $rules);