From 097b4f6868459900819a3a3667d1dfd214eff8e0 Mon Sep 17 00:00:00 2001 From: Ruslan Lekhman Date: Sat, 11 Apr 2026 15:08:11 -0600 Subject: [PATCH] feat(material/core): add option-multiple-selected-state-label-text-color token Adds a new `option-multiple-selected-state-label-text-color` token (`null` default, preserving existing behavior) so users can theme the label text color of selected multi-select options via `mat.option-overrides`. close #32618 Signed-off-by: Ruslan Lekhman --- src/material/core/option/_m2-option.scss | 1 + src/material/core/option/_m3-option.scss | 1 + src/material/core/option/option.scss | 11 +++++ src/material/core/option/option.spec.ts | 54 +++++++++++++++++++++++- 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/material/core/option/_m2-option.scss b/src/material/core/option/_m2-option.scss index 11414eb6dd5b..1ad64bcd45c1 100644 --- a/src/material/core/option/_m2-option.scss +++ b/src/material/core/option/_m2-option.scss @@ -26,6 +26,7 @@ @return ( option-selected-state-label-text-color: map.get($system, primary), + option-multiple-selected-state-label-text-color: null, option-label-text-color: map.get($system, on-surface), option-hover-state-layer-color: m3-utils.color-with-opacity( map.get($system, on-surface), map.get($system, hover-state-layer-opacity)), diff --git a/src/material/core/option/_m3-option.scss b/src/material/core/option/_m3-option.scss index b7eae506d8e8..b205660a957e 100644 --- a/src/material/core/option/_m3-option.scss +++ b/src/material/core/option/_m3-option.scss @@ -23,6 +23,7 @@ map.get($system, on-surface), map.get($system, hover-state-layer-opacity)), option-label-text-color: map.get($system, on-surface), option-selected-state-label-text-color: map.get($system, on-secondary-container), + option-multiple-selected-state-label-text-color: null, option-selected-state-layer-color: map.get($system, secondary-container), ), typography: ( diff --git a/src/material/core/option/option.scss b/src/material/core/option/option.scss index 6a10be1141c0..d39b928822ab 100644 --- a/src/material/core/option/option.scss +++ b/src/material/core/option/option.scss @@ -52,6 +52,17 @@ $_side-padding: 16px; } } + // Opt-in label color for multi-select selected options. Unlike single-select, + // no background-color change is applied here - the checkbox communicates selection. + // Token defaults to null, so this rule only takes effect when the user sets it. + &.mdc-list-item--selected:not(.mdc-list-item--disabled).mat-mdc-option-multiple { + &:not(.mat-mdc-option-active,:focus,:hover) { + .mdc-list-item__primary-text { + color: token-utils.slot(option-multiple-selected-state-label-text-color, $fallbacks); + } + } + } + .mat-pseudo-checkbox { --mat-pseudo-checkbox-minimal-selected-checkmark-color: #{ token-utils.slot(option-selected-state-label-text-color, $fallbacks)}; diff --git a/src/material/core/option/option.spec.ts b/src/material/core/option/option.spec.ts index 741ad692fb94..e185427ee5c6 100644 --- a/src/material/core/option/option.spec.ts +++ b/src/material/core/option/option.spec.ts @@ -8,7 +8,12 @@ import { dispatchEvent, } from '@angular/cdk/testing/private'; import {SPACE, ENTER} from '@angular/cdk/keycodes'; -import {MatOption, MatOptionModule, MAT_OPTION_PARENT_COMPONENT} from './index'; +import { + MatOption, + MatOptionModule, + MAT_OPTION_PARENT_COMPONENT, + MatOptionParentComponent, +} from './index'; describe('MatOption component', () => { it('should complete the `stateChanges` stream on destroy', () => { @@ -190,6 +195,40 @@ describe('MatOption component', () => { }); }); + describe('multi-select selected state label color token', () => { + it('should apply option-multiple-selected-state-label-text-color to selected multi-select option labels', () => { + const fixture = TestBed.createComponent(MultipleOption); + fixture.detectChanges(); + + const host: HTMLElement = fixture.nativeElement; + host.style.setProperty( + '--mat-option-multiple-selected-state-label-text-color', + 'rgb(255, 0, 0)', + ); + + const optionEl: HTMLElement = host.querySelector('mat-option')!; + optionEl.click(); + fixture.detectChanges(); + + const primaryText: HTMLElement = optionEl.querySelector('.mdc-list-item__primary-text')!; + expect(getComputedStyle(primaryText).color).toBe('rgb(255, 0, 0)'); + }); + + it('should not change label color of selected multi-select options when the token is not set', () => { + const fixture = TestBed.createComponent(MultipleOption); + fixture.detectChanges(); + + const optionEl: HTMLElement = fixture.nativeElement.querySelector('mat-option')!; + const primaryText: HTMLElement = optionEl.querySelector('.mdc-list-item__primary-text')!; + const colorBefore = getComputedStyle(primaryText).color; + + optionEl.click(); + fixture.detectChanges(); + + expect(getComputedStyle(primaryText).color).toBe(colorBefore); + }); + }); + it('should have a focus indicator', () => { const fixture = TestBed.createComponent(BasicOption); const optionNativeElement = fixture.debugElement.query(By.directive(MatOption))!.nativeElement; @@ -254,3 +293,16 @@ class BasicOption { changeDetection: ChangeDetectionStrategy.Eager, }) class InsideGroup {} + +@Component({ + template: `Option`, + imports: [MatOptionModule], + changeDetection: ChangeDetectionStrategy.Eager, + providers: [ + { + provide: MAT_OPTION_PARENT_COMPONENT, + useValue: {multiple: true} as MatOptionParentComponent, + }, + ], +}) +class MultipleOption {}