diff --git a/goldens/cdk/stepper/index.api.md b/goldens/cdk/stepper/index.api.md
index 9295ce4417e2..a26d32c85c00 100644
--- a/goldens/cdk/stepper/index.api.md
+++ b/goldens/cdk/stepper/index.api.md
@@ -28,16 +28,22 @@ export class CdkStep implements OnChanges {
     get completed(): boolean;
     set completed(value: boolean);
     // (undocumented)
-    _completedOverride: boolean | null;
+    _completedOverride: i0.WritableSignal<boolean | null>;
     content: TemplateRef<any>;
     // (undocumented)
     _displayDefaultIndicatorType: boolean;
-    editable: boolean;
+    get editable(): boolean;
+    set editable(value: boolean);
     errorMessage: string;
     get hasError(): boolean;
     set hasError(value: boolean);
-    interacted: boolean;
+    readonly index: i0.WritableSignal<number>;
+    readonly indicatorType: i0.Signal<string>;
+    get interacted(): boolean;
+    set interacted(value: boolean);
     readonly interactedStream: EventEmitter<CdkStep>;
+    readonly isNavigable: i0.Signal<boolean>;
+    readonly isSelected: i0.Signal<boolean>;
     label: string;
     // (undocumented)
     _markAsInteracted(): void;
@@ -55,7 +61,8 @@ export class CdkStep implements OnChanges {
     reset(): void;
     select(): void;
     _showError(): boolean;
-    state: StepState;
+    get state(): StepState;
+    set state(value: StepState);
     stepControl: AbstractControl;
     stepLabel: CdkStepLabel;
     // (undocumented)
@@ -97,7 +104,6 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
     protected _elementRef: ElementRef<HTMLElement>;
     _getAnimationDirection(index: number): StepContentPositionState;
     _getFocusIndex(): number | null;
-    _getIndicatorType(index: number, state?: StepState): StepState;
     _getStepContentId(i: number): string;
     _getStepLabelId(i: number): string;
     linear: boolean;
diff --git a/goldens/material/stepper/index.api.md b/goldens/material/stepper/index.api.md
index c4ecf404a356..b511e57b2aae 100644
--- a/goldens/material/stepper/index.api.md
+++ b/goldens/material/stepper/index.api.md
@@ -82,7 +82,6 @@ export class MatStepHeader extends CdkStepHeader implements AfterViewInit, OnDes
     // (undocumented)
     _getDefaultTextForState(state: StepState): string;
     _getHostElement(): HTMLElement;
-    _getIconContext(): MatStepperIconContext;
     iconOverrides: {
         [key: string]: TemplateRef<MatStepperIconContext>;
     };
@@ -138,8 +137,6 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten
     // (undocumented)
     ngOnDestroy(): void;
     _stepHeader: QueryList<MatStepHeader>;
-    // (undocumented)
-    _stepIsNavigable(index: number, step: MatStep): boolean;
     readonly steps: QueryList<MatStep>;
     _steps: QueryList<MatStep>;
     // (undocumented)
diff --git a/src/cdk/a11y/key-manager/list-key-manager.ts b/src/cdk/a11y/key-manager/list-key-manager.ts
index 55796cbe1d01..c52ebb9697e8 100644
--- a/src/cdk/a11y/key-manager/list-key-manager.ts
+++ b/src/cdk/a11y/key-manager/list-key-manager.ts
@@ -39,7 +39,7 @@ export type ListKeyManagerModifierKey = 'altKey' | 'ctrlKey' | 'metaKey' | 'shif
  * of items, it will set the active item correctly when arrow events occur.
  */
 export class ListKeyManager<T extends ListKeyManagerOption> {
-  private _activeItemIndex = -1;
+  private _activeItemIndex = signal(-1);
   private _activeItem = signal<T | null>(null);
   private _wrap = false;
   private _typeaheadSubscription = Subscription.EMPTY;
@@ -209,7 +209,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
     this.updateActiveItem(item);
 
     if (this._activeItem() !== previousActiveItem) {
-      this.change.next(this._activeItemIndex);
+      this.change.next(this._activeItemIndex());
     }
   }
 
@@ -279,7 +279,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
 
       case PAGE_UP:
         if (this._pageUpAndDown.enabled && isModifierAllowed) {
-          const targetIndex = this._activeItemIndex - this._pageUpAndDown.delta;
+          const targetIndex = this._activeItemIndex() - this._pageUpAndDown.delta;
           this._setActiveItemByIndex(targetIndex > 0 ? targetIndex : 0, 1);
           break;
         } else {
@@ -288,7 +288,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
 
       case PAGE_DOWN:
         if (this._pageUpAndDown.enabled && isModifierAllowed) {
-          const targetIndex = this._activeItemIndex + this._pageUpAndDown.delta;
+          const targetIndex = this._activeItemIndex() + this._pageUpAndDown.delta;
           const itemsLength = this._getItemsArray().length;
           this._setActiveItemByIndex(targetIndex < itemsLength ? targetIndex : itemsLength - 1, -1);
           break;
@@ -312,7 +312,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
 
   /** Index of the currently active item. */
   get activeItemIndex(): number | null {
-    return this._activeItemIndex;
+    return this._activeItemIndex();
   }
 
   /** The active item. */
@@ -337,12 +337,12 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
 
   /** Sets the active item to the next enabled item in the list. */
   setNextItemActive(): void {
-    this._activeItemIndex < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1);
+    this._activeItemIndex() < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1);
   }
 
   /** Sets the active item to a previous enabled item in the list. */
   setPreviousItemActive(): void {
-    this._activeItemIndex < 0 && this._wrap
+    this._activeItemIndex() < 0 && this._wrap
       ? this.setLastItemActive()
       : this._setActiveItemByDelta(-1);
   }
@@ -366,7 +366,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
 
     // Explicitly check for `null` and `undefined` because other falsy values are valid.
     this._activeItem.set(activeItem == null ? null : activeItem);
-    this._activeItemIndex = index;
+    this._activeItemIndex.set(index);
     this._typeahead?.setCurrentSelectedItemIndex(index);
   }
 
@@ -398,7 +398,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
     const items = this._getItemsArray();
 
     for (let i = 1; i <= items.length; i++) {
-      const index = (this._activeItemIndex + delta * i + items.length) % items.length;
+      const index = (this._activeItemIndex() + delta * i + items.length) % items.length;
       const item = items[index];
 
       if (!this._skipPredicateFn(item)) {
@@ -414,7 +414,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
    * it encounters either end of the list, it will stop and not wrap.
    */
   private _setActiveInDefaultMode(delta: -1 | 1): void {
-    this._setActiveItemByIndex(this._activeItemIndex + delta, delta);
+    this._setActiveItemByIndex(this._activeItemIndex() + delta, delta);
   }
 
   /**
@@ -456,8 +456,8 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
     if (activeItem) {
       const newIndex = newItems.indexOf(activeItem);
 
-      if (newIndex > -1 && newIndex !== this._activeItemIndex) {
-        this._activeItemIndex = newIndex;
+      if (newIndex > -1 && newIndex !== this._activeItemIndex()) {
+        this._activeItemIndex.set(newIndex);
         this._typeahead?.setCurrentSelectedItemIndex(newIndex);
       }
     }
diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts
index 66f99022eea9..06d82f8af560 100644
--- a/src/cdk/stepper/stepper.ts
+++ b/src/cdk/stepper/stepper.ts
@@ -32,6 +32,8 @@ import {
   booleanAttribute,
   numberAttribute,
   inject,
+  signal,
+  computed,
 } from '@angular/core';
 import {
   ControlContainer,
@@ -135,7 +137,13 @@ export class CdkStep implements OnChanges {
   @Input() stepControl: AbstractControl;
 
   /** Whether user has attempted to move away from the step. */
-  interacted = false;
+  get interacted(): boolean {
+    return this._interacted();
+  }
+  set interacted(value: boolean) {
+    this._interacted.set(value);
+  }
+  private _interacted = signal(false);
 
   /** Emits when the user has attempted to move away from the step. */
   @Output('interacted')
@@ -157,10 +165,24 @@ export class CdkStep implements OnChanges {
   @Input('aria-labelledby') ariaLabelledby: string;
 
   /** State of the step. */
-  @Input() state: StepState;
+  @Input()
+  get state(): StepState {
+    return this._state()!;
+  }
+  set state(value: StepState) {
+    this._state.set(value);
+  }
+  private _state = signal<StepState | undefined>(undefined);
 
   /** Whether the user can return to this step once it has been marked as completed. */
-  @Input({transform: booleanAttribute}) editable: boolean = true;
+  @Input({transform: booleanAttribute})
+  get editable(): boolean {
+    return this._editable()!;
+  }
+  set editable(value: boolean) {
+    this._editable.set(value);
+  }
+  private _editable = signal(true);
 
   /** Whether the completion of step is optional. */
   @Input({transform: booleanAttribute}) optional: boolean = false;
@@ -168,29 +190,72 @@ export class CdkStep implements OnChanges {
   /** Whether step is marked as completed. */
   @Input({transform: booleanAttribute})
   get completed(): boolean {
-    return this._completedOverride == null ? this._getDefaultCompleted() : this._completedOverride;
+    const override = this._completedOverride();
+    const interacted = this._interacted();
+
+    if (override != null) {
+      return override;
+    }
+
+    return interacted && (!this.stepControl || this.stepControl.valid);
   }
   set completed(value: boolean) {
-    this._completedOverride = value;
+    this._completedOverride.set(value);
   }
-  _completedOverride: boolean | null = null;
+  _completedOverride = signal<boolean | null>(null);
 
-  private _getDefaultCompleted() {
-    return this.stepControl ? this.stepControl.valid && this.interacted : this.interacted;
-  }
+  /** Current index of the step within the stepper. */
+  readonly index = signal(-1);
+
+  /** Whether the step is selected. */
+  readonly isSelected = computed<boolean>(() => this._stepper.selectedIndex === this.index());
+
+  /** Type of indicator that should be shown for the step. */
+  readonly indicatorType = computed<StepState>(() => {
+    const selected = this.isSelected();
+    const completed = this.completed;
+    const defaultState = this._state() ?? STEP_STATE.NUMBER;
+    const editable = this._editable();
+
+    if (this._showError() && this.hasError && !selected) {
+      return STEP_STATE.ERROR;
+    }
+
+    if (this._displayDefaultIndicatorType) {
+      if (!completed || selected) {
+        return STEP_STATE.NUMBER;
+      }
+      return editable ? STEP_STATE.EDIT : STEP_STATE.DONE;
+    } else {
+      if (completed && !selected) {
+        return STEP_STATE.DONE;
+      } else if (completed && selected) {
+        return defaultState;
+      }
+      return editable && selected ? STEP_STATE.EDIT : defaultState;
+    }
+  });
+
+  /** Whether the user can navigate to the step. */
+  readonly isNavigable = computed<boolean>(() => {
+    const isSelected = this.isSelected();
+    const isCompleted = this.completed;
+    return isCompleted || isSelected || !this._stepper.linear;
+  });
 
   /** Whether step has an error. */
   @Input({transform: booleanAttribute})
   get hasError(): boolean {
-    return this._customError == null ? this._getDefaultError() : this._customError;
+    const customError = this._customError();
+    return customError == null ? this._getDefaultError() : customError;
   }
   set hasError(value: boolean) {
-    this._customError = value;
+    this._customError.set(value);
   }
-  private _customError: boolean | null = null;
+  private _customError = signal<boolean | null>(null);
 
   private _getDefaultError() {
-    return this.stepControl && this.stepControl.invalid && this.interacted;
+    return this.interacted && !!this.stepControl?.invalid;
   }
 
   constructor(...args: unknown[]);
@@ -208,14 +273,14 @@ export class CdkStep implements OnChanges {
 
   /** Resets the step to its initial state. Note that this includes resetting form data. */
   reset(): void {
-    this.interacted = false;
+    this._interacted.set(false);
 
-    if (this._completedOverride != null) {
-      this._completedOverride = false;
+    if (this._completedOverride() != null) {
+      this._completedOverride.set(false);
     }
 
-    if (this._customError != null) {
-      this._customError = false;
+    if (this._customError() != null) {
+      this._customError.set(false);
     }
 
     if (this.stepControl) {
@@ -234,8 +299,8 @@ export class CdkStep implements OnChanges {
   }
 
   _markAsInteracted() {
-    if (!this.interacted) {
-      this.interacted = true;
+    if (!this._interacted()) {
+      this._interacted.set(true);
       this.interactedStream.emit(this);
     }
   }
@@ -244,7 +309,7 @@ export class CdkStep implements OnChanges {
   _showError(): boolean {
     // We want to show the error state either if the user opted into/out of it using the
     // global options, or if they've explicitly set it through the `hasError` input.
-    return this._stepperOptions.showError ?? this._customError != null;
+    return this._stepperOptions.showError ?? this._customError() != null;
   }
 }
 
@@ -281,7 +346,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
   /** The index of the selected step. */
   @Input({transform: numberAttribute})
   get selectedIndex(): number {
-    return this._selectedIndex;
+    return this._selectedIndex();
   }
   set selectedIndex(index: number) {
     if (this._steps) {
@@ -290,21 +355,21 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
         throw Error('cdkStepper: Cannot assign out-of-bounds value to `selectedIndex`.');
       }
 
-      if (this._selectedIndex !== index) {
+      if (this.selectedIndex !== index) {
         this.selected?._markAsInteracted();
 
         if (
           !this._anyControlsInvalidOrPending(index) &&
-          (index >= this._selectedIndex || this.steps.toArray()[index].editable)
+          (index >= this.selectedIndex || this.steps.toArray()[index].editable)
         ) {
           this._updateSelectedItemIndex(index);
         }
       }
     } else {
-      this._selectedIndex = index;
+      this._selectedIndex.set(index);
     }
   }
-  private _selectedIndex = 0;
+  private _selectedIndex = signal(0);
 
   /** The step that is selected. */
   @Input()
@@ -347,6 +412,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
       .pipe(startWith(this._steps), takeUntil(this._destroyed))
       .subscribe((steps: QueryList<CdkStep>) => {
         this.steps.reset(steps.filter(step => step._stepper === this));
+        this.steps.forEach((step, index) => step.index.set(index));
         this.steps.notifyOnChanges();
       });
   }
@@ -393,26 +459,26 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
       .pipe(startWith(this._layoutDirection()), takeUntil(this._destroyed))
       .subscribe(direction => this._keyManager?.withHorizontalOrientation(direction));
 
-    this._keyManager.updateActiveItem(this._selectedIndex);
+    this._keyManager.updateActiveItem(this.selectedIndex);
 
     // No need to `takeUntil` here, because we're the ones destroying `steps`.
     this.steps.changes.subscribe(() => {
       if (!this.selected) {
-        this._selectedIndex = Math.max(this._selectedIndex - 1, 0);
+        this._selectedIndex.set(Math.max(this.selectedIndex - 1, 0));
       }
     });
 
     // The logic which asserts that the selected index is within bounds doesn't run before the
     // steps are initialized, because we don't how many steps there are yet so we may have an
     // invalid index on init. If that's the case, auto-correct to the default so we don't throw.
-    if (!this._isValidIndex(this._selectedIndex)) {
-      this._selectedIndex = 0;
+    if (!this._isValidIndex(this.selectedIndex)) {
+      this._selectedIndex.set(0);
     }
 
     // For linear step and selected index is greater than zero,
     // set all the previous steps to interacted so that we can navigate to previous steps.
-    if (this.linear && this._selectedIndex > 0) {
-      const visitedSteps = this.steps.toArray().slice(0, this._selectedIndex);
+    if (this.linear && this.selectedIndex > 0) {
+      const visitedSteps = this.steps.toArray().slice(0, this._selectedIndex());
 
       for (const step of visitedSteps) {
         step._markAsInteracted();
@@ -430,12 +496,12 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
 
   /** Selects and focuses the next step in list. */
   next(): void {
-    this.selectedIndex = Math.min(this._selectedIndex + 1, this.steps.length - 1);
+    this.selectedIndex = Math.min(this._selectedIndex() + 1, this.steps.length - 1);
   }
 
   /** Selects and focuses the previous step in list. */
   previous(): void {
-    this.selectedIndex = Math.max(this._selectedIndex - 1, 0);
+    this.selectedIndex = Math.max(this._selectedIndex() - 1, 0);
   }
 
   /** Resets the stepper to its initial state. Note that this includes clearing form data. */
@@ -462,7 +528,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
 
   /** Returns position state of the step with the given index. */
   _getAnimationDirection(index: number): StepContentPositionState {
-    const position = index - this._selectedIndex;
+    const position = index - this._selectedIndex();
     if (position < 0) {
       return this._layoutDirection() === 'rtl' ? 'next' : 'previous';
     } else if (position > 0) {
@@ -471,60 +537,20 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
     return 'current';
   }
 
-  /** Returns the type of icon to be displayed. */
-  _getIndicatorType(index: number, state: StepState = STEP_STATE.NUMBER): StepState {
-    const step = this.steps.toArray()[index];
-    const isCurrentStep = this._isCurrentStep(index);
-
-    return step._displayDefaultIndicatorType
-      ? this._getDefaultIndicatorLogic(step, isCurrentStep)
-      : this._getGuidelineLogic(step, isCurrentStep, state);
-  }
-
-  private _getDefaultIndicatorLogic(step: CdkStep, isCurrentStep: boolean): StepState {
-    if (step._showError() && step.hasError && !isCurrentStep) {
-      return STEP_STATE.ERROR;
-    } else if (!step.completed || isCurrentStep) {
-      return STEP_STATE.NUMBER;
-    } else {
-      return step.editable ? STEP_STATE.EDIT : STEP_STATE.DONE;
-    }
-  }
-
-  private _getGuidelineLogic(
-    step: CdkStep,
-    isCurrentStep: boolean,
-    state: StepState = STEP_STATE.NUMBER,
-  ): StepState {
-    if (step._showError() && step.hasError && !isCurrentStep) {
-      return STEP_STATE.ERROR;
-    } else if (step.completed && !isCurrentStep) {
-      return STEP_STATE.DONE;
-    } else if (step.completed && isCurrentStep) {
-      return state;
-    } else if (step.editable && isCurrentStep) {
-      return STEP_STATE.EDIT;
-    } else {
-      return state;
-    }
-  }
-
-  private _isCurrentStep(index: number) {
-    return this._selectedIndex === index;
-  }
-
   /** Returns the index of the currently-focused step header. */
-  _getFocusIndex() {
-    return this._keyManager ? this._keyManager.activeItemIndex : this._selectedIndex;
+  _getFocusIndex(): number | null {
+    return this._keyManager ? this._keyManager.activeItemIndex : this._selectedIndex();
   }
 
   private _updateSelectedItemIndex(newIndex: number): void {
     const stepsArray = this.steps.toArray();
+    const selectedIndex = this._selectedIndex();
+
     this.selectionChange.emit({
       selectedIndex: newIndex,
-      previouslySelectedIndex: this._selectedIndex,
+      previouslySelectedIndex: selectedIndex,
       selectedStep: stepsArray[newIndex],
-      previouslySelectedStep: stepsArray[this._selectedIndex],
+      previouslySelectedStep: stepsArray[selectedIndex],
     });
 
     // If focus is inside the stepper, move it to the next header, otherwise it may become
@@ -537,8 +563,8 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
         : this._keyManager.updateActiveItem(newIndex);
     }
 
-    this._selectedIndex = newIndex;
-    this.selectedIndexChange.emit(this._selectedIndex);
+    this._selectedIndex.set(newIndex);
+    this.selectedIndexChange.emit(newIndex);
     this._stateChanged();
   }
 
@@ -569,7 +595,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
           const isIncomplete = control
             ? control.invalid || control.pending || !step.interacted
             : !step.completed;
-          return isIncomplete && !step.optional && !step._completedOverride;
+          return isIncomplete && !step.optional && !step._completedOverride();
         });
     }
 
diff --git a/src/material/list/list-base.ts b/src/material/list/list-base.ts
index eaa942ed0043..401ba7856318 100644
--- a/src/material/list/list-base.ts
+++ b/src/material/list/list-base.ts
@@ -19,6 +19,7 @@ import {
   OnDestroy,
   QueryList,
   Injector,
+  signal,
 } from '@angular/core';
 import {
   _animationsDisabled,
@@ -64,12 +65,12 @@ export abstract class MatListBase {
    */
   @Input()
   get disabled(): boolean {
-    return this._disabled;
+    return this._disabled();
   }
   set disabled(value: BooleanInput) {
-    this._disabled = coerceBooleanProperty(value);
+    this._disabled.set(coerceBooleanProperty(value));
   }
-  private _disabled = false;
+  private _disabled = signal(false);
 
   protected _defaultOptions = inject(MAT_LIST_CONFIG, {optional: true});
 }
@@ -149,12 +150,12 @@ export abstract class MatListItemBase implements AfterViewInit, OnDestroy, Rippl
   /** Whether the list-item is disabled. */
   @Input()
   get disabled(): boolean {
-    return this._disabled || !!this._listBase?.disabled;
+    return this._disabled() || !!this._listBase?.disabled;
   }
   set disabled(value: BooleanInput) {
-    this._disabled = coerceBooleanProperty(value);
+    this._disabled.set(coerceBooleanProperty(value));
   }
-  private _disabled = false;
+  private _disabled = signal(false);
 
   private _subscriptions = new Subscription();
   private _rippleRenderer: RippleRenderer | null = null;
diff --git a/src/material/list/selection-list.spec.ts b/src/material/list/selection-list.spec.ts
index 911dd94d077d..d83c25fa6589 100644
--- a/src/material/list/selection-list.spec.ts
+++ b/src/material/list/selection-list.spec.ts
@@ -10,7 +10,6 @@ import {
   ChangeDetectionStrategy,
   Component,
   DebugElement,
-  provideCheckNoChangesConfig,
   QueryList,
   ViewChildren,
 } from '@angular/core';
@@ -43,21 +42,6 @@ describe('MatSelectionList without forms', () => {
     let listOptions: DebugElement[];
     let selectionList: DebugElement;
 
-    beforeEach(waitForAsync(() => {
-      TestBed.configureTestingModule({
-        providers: [provideCheckNoChangesConfig({exhaustive: false})],
-        imports: [
-          MatListModule,
-          SelectionListWithListOptions,
-          SelectionListWithCheckboxPositionAfter,
-          SelectionListWithListDisabled,
-          SelectionListWithOnlyOneOption,
-          SelectionListWithIndirectChildOptions,
-          SelectionListWithSelectedOptionAndValue,
-        ],
-      });
-    }));
-
     beforeEach(waitForAsync(() => {
       fixture = TestBed.createComponent(SelectionListWithListOptions);
       fixture.detectChanges();
@@ -1277,23 +1261,6 @@ describe('MatSelectionList without forms', () => {
 });
 
 describe('MatSelectionList with forms', () => {
-  beforeEach(waitForAsync(() => {
-    TestBed.configureTestingModule({
-      providers: [provideCheckNoChangesConfig({exhaustive: false})],
-      imports: [
-        MatListModule,
-        FormsModule,
-        ReactiveFormsModule,
-        SelectionListWithModel,
-        SelectionListWithFormControl,
-        SelectionListWithPreselectedOption,
-        SelectionListWithPreselectedOptionAndModel,
-        SelectionListWithPreselectedFormControlOnPush,
-        SelectionListWithCustomComparator,
-      ],
-    });
-  }));
-
   describe('and ngModel', () => {
     let fixture: ComponentFixture<SelectionListWithModel>;
     let selectionListDebug: DebugElement;
diff --git a/src/material/list/selection-list.ts b/src/material/list/selection-list.ts
index ca819bd7e0f4..9a8d186c4cea 100644
--- a/src/material/list/selection-list.ts
+++ b/src/material/list/selection-list.ts
@@ -30,6 +30,7 @@ import {
   ViewEncapsulation,
   forwardRef,
   inject,
+  signal,
 } from '@angular/core';
 import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
 import {ThemePalette} from '../core';
@@ -265,18 +266,18 @@ export class MatSelectionList
    */
   @Input()
   override get disabled(): boolean {
-    return this._selectionListDisabled;
+    return this._selectionListDisabled();
   }
   override set disabled(value: BooleanInput) {
     // Update the disabled state of this list. Write to `this._selectionListDisabled` instead of
     // `super.disabled`. That is to avoid closure compiler compatibility issues with assigning to
     // a super property.
-    this._selectionListDisabled = coerceBooleanProperty(value);
-    if (this._selectionListDisabled) {
+    this._selectionListDisabled.set(coerceBooleanProperty(value));
+    if (this._selectionListDisabled()) {
       this._keyManager?.setActiveItem(-1);
     }
   }
-  private _selectionListDisabled = false;
+  private _selectionListDisabled = signal(false);
 
   /** Implemented as part of ControlValueAccessor. */
   registerOnChange(fn: (value: any) => void): void {
diff --git a/src/material/menu/menu.spec.ts b/src/material/menu/menu.spec.ts
index b2f150e3d139..2dd79464239a 100644
--- a/src/material/menu/menu.spec.ts
+++ b/src/material/menu/menu.spec.ts
@@ -22,7 +22,6 @@ import {
   Input,
   OnDestroy,
   Output,
-  provideCheckNoChangesConfig,
   QueryList,
   signal,
   TemplateRef,
@@ -1744,7 +1743,6 @@ describe('MatMenu', () => {
       direction = 'ltr';
       TestBed.resetTestingModule().configureTestingModule({
         providers: [
-          provideCheckNoChangesConfig({exhaustive: false}),
           {
             provide: Directionality,
             useValue: {
@@ -1907,7 +1905,7 @@ describe('MatMenu', () => {
         .withContext('Expected two open menus')
         .toBe(2);
 
-      items[1].componentInstance.disabled = true;
+      fixture.componentInstance.secondItemDisabled = true;
       fixture.changeDetectorRef.markForCheck();
       fixture.detectChanges();
 
@@ -1932,7 +1930,7 @@ describe('MatMenu', () => {
 
       const item = fixture.debugElement.query(By.directive(MatMenuItem))!;
 
-      item.componentInstance.disabled = true;
+      fixture.componentInstance.firstItemDisabled = true;
       fixture.changeDetectorRef.markForCheck();
       fixture.detectChanges();
 
@@ -2728,8 +2726,9 @@ class CustomMenu {
       <button mat-menu-item
         id="level-one-trigger"
         [matMenuTriggerFor]="levelOne"
+        [disabled]="firstItemDisabled"
         #levelOneTrigger="matMenuTrigger">One</button>
-      <button mat-menu-item>Two</button>
+      <button mat-menu-item [disabled]="secondItemDisabled">Two</button>
       @if (showLazy) {
         <button mat-menu-item
           id="lazy-trigger"
@@ -2779,6 +2778,9 @@ class NestedMenu {
   @ViewChild('lazy') lazyMenu: MatMenu;
   @ViewChild('lazyTrigger') lazyTrigger: MatMenuTrigger;
   showLazy = false;
+
+  firstItemDisabled = false;
+  secondItemDisabled = false;
 }
 
 @Component({
diff --git a/src/material/stepper/step-header.html b/src/material/stepper/step-header.html
index 19ade872e7a4..99b65a5643a4 100644
--- a/src/material/stepper/step-header.html
+++ b/src/material/stepper/step-header.html
@@ -7,7 +7,7 @@
     @if (iconOverrides && iconOverrides[state]) {
       <ng-container
         [ngTemplateOutlet]="iconOverrides[state]"
-        [ngTemplateOutletContext]="_getIconContext()"></ng-container>
+        [ngTemplateOutletContext]="{index, active, optional}"></ng-container>
     } @else {
       @switch (state) {
         @case ('number') {
diff --git a/src/material/stepper/step-header.ts b/src/material/stepper/step-header.ts
index db4bac260578..998e5d3610ad 100644
--- a/src/material/stepper/step-header.ts
+++ b/src/material/stepper/step-header.ts
@@ -128,15 +128,6 @@ export class MatStepHeader extends CdkStepHeader implements AfterViewInit, OnDes
     return this._elementRef.nativeElement;
   }
 
-  /** Template context variables that are exposed to the `matStepperIcon` instances. */
-  _getIconContext(): MatStepperIconContext {
-    return {
-      index: this.index,
-      active: this.active,
-      optional: this.optional,
-    };
-  }
-
   _getDefaultTextForState(state: StepState): string {
     if (state == 'number') {
       return `${this.index + 1}`;
diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html
index a75e48522600..0f75e58c012b 100644
--- a/src/material/stepper/stepper.html
+++ b/src/material/stepper/stepper.html
@@ -15,7 +15,7 @@
         @for (step of steps; track step) {
           <ng-container
             [ngTemplateOutlet]="stepTemplate"
-            [ngTemplateOutletContext]="{step, i: $index}"/>
+            [ngTemplateOutletContext]="{step}"/>
           @if (!$last) {
             <div class="mat-stepper-horizontal-line"></div>
           }
@@ -44,7 +44,7 @@
       <div class="mat-step">
         <ng-container
           [ngTemplateOutlet]="stepTemplate"
-          [ngTemplateOutletContext]="{step, i: $index}"/>
+          [ngTemplateOutletContext]="{step}"/>
         <div
           #animatedContainer
           class="mat-vertical-content-container"
@@ -66,29 +66,29 @@
 }
 
 <!-- Common step templating -->
-<ng-template let-step="step" let-i="i" #stepTemplate>
+<ng-template let-step="step" #stepTemplate>
   <mat-step-header
     [class.mat-horizontal-stepper-header]="orientation === 'horizontal'"
     [class.mat-vertical-stepper-header]="orientation === 'vertical'"
     (click)="step.select()"
     (keydown)="_onKeydown($event)"
-    [tabIndex]="_getFocusIndex() === i ? 0 : -1"
-    [id]="_getStepLabelId(i)"
-    [attr.aria-posinset]="i + 1"
+    [tabIndex]="_getFocusIndex() === step.index() ? 0 : -1"
+    [id]="_getStepLabelId(step.index())"
+    [attr.aria-posinset]="step.index() + 1"
     [attr.aria-setsize]="steps.length"
-    [attr.aria-controls]="_getStepContentId(i)"
-    [attr.aria-selected]="selectedIndex == i"
+    [attr.aria-controls]="_getStepContentId(step.index())"
+    [attr.aria-selected]="step.isSelected()"
     [attr.aria-label]="step.ariaLabel || null"
     [attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null"
-    [attr.aria-disabled]="_stepIsNavigable(i, step) ? null : true"
-    [index]="i"
-    [state]="_getIndicatorType(i, step.state)"
+    [attr.aria-disabled]="step.isNavigable() ? null : true"
+    [index]="step.index()"
+    [state]="step.indicatorType()"
     [label]="step.stepLabel || step.label"
-    [selected]="selectedIndex === i"
-    [active]="_stepIsNavigable(i, step)"
+    [selected]="step.isSelected()"
+    [active]="step.isNavigable()"
     [optional]="step.optional"
     [errorMessage]="step.errorMessage"
     [iconOverrides]="_iconOverrides"
-    [disableRipple]="disableRipple || !_stepIsNavigable(i, step)"
+    [disableRipple]="disableRipple || !step.isNavigable()"
     [color]="step.color || color"/>
 </ng-template>
diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts
index c640438ae327..b66beebe99f4 100644
--- a/src/material/stepper/stepper.spec.ts
+++ b/src/material/stepper/stepper.spec.ts
@@ -33,7 +33,6 @@ import {
   ViewEncapsulation,
   WritableSignal,
   inject,
-  provideCheckNoChangesConfig,
   signal,
 } from '@angular/core';
 import {ComponentFixture, TestBed} from '@angular/core/testing';
@@ -75,59 +74,45 @@ describe('MatStepper', () => {
     });
 
     it('should default to the first step', () => {
-      const stepperComponent: MatStepper = fixture.debugElement.query(
-        By.css('mat-stepper'),
-      )!.componentInstance;
-
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(fixture.componentInstance.stepper.selectedIndex).toBe(0);
     });
 
     it('should throw when a negative `selectedIndex` is assigned', () => {
-      const stepperComponent: MatStepper = fixture.debugElement.query(
-        By.css('mat-stepper'),
-      )!.componentInstance;
-
       expect(() => {
-        stepperComponent.selectedIndex = -10;
+        fixture.componentInstance.stepper.selectedIndex = -10;
         fixture.detectChanges();
       }).toThrowError(/Cannot assign out-of-bounds/);
     });
 
     it('should throw when an out-of-bounds `selectedIndex` is assigned', () => {
-      const stepperComponent: MatStepper = fixture.debugElement.query(
-        By.css('mat-stepper'),
-      )!.componentInstance;
-
       expect(() => {
-        stepperComponent.selectedIndex = 1337;
+        fixture.componentInstance.stepper.selectedIndex = 1337;
         fixture.detectChanges();
       }).toThrowError(/Cannot assign out-of-bounds/);
     });
 
     it('should change selected index on header click', () => {
       const stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
 
-      expect(stepperComponent.selectedIndex).toBe(0);
-      expect(stepperComponent.selected instanceof MatStep).toBe(true);
+      expect(stepper.selectedIndex).toBe(0);
+      expect(stepper.selected instanceof MatStep).toBe(true);
 
       // select the second step
       let stepHeaderEl = stepHeaders[1].nativeElement;
       stepHeaderEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
-      expect(stepperComponent.selected instanceof MatStep).toBe(true);
+      expect(stepper.selectedIndex).toBe(1);
+      expect(stepper.selected instanceof MatStep).toBe(true);
 
       // select the third step
       stepHeaderEl = stepHeaders[2].nativeElement;
       stepHeaderEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(2);
-      expect(stepperComponent.selected instanceof MatStep).toBe(true);
+      expect(stepper.selectedIndex).toBe(2);
+      expect(stepper.selected instanceof MatStep).toBe(true);
     });
 
     it('should set the "tablist" role on stepper', () => {
@@ -136,13 +121,10 @@ describe('MatStepper', () => {
     });
 
     it('should display the correct label', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
       let selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]');
       expect(selectedLabel.textContent).toMatch('Step 1');
 
-      stepperComponent.selectedIndex = 2;
+      fixture.componentInstance.stepper.selectedIndex = 2;
       fixture.detectChanges();
 
       selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]');
@@ -156,32 +138,30 @@ describe('MatStepper', () => {
     });
 
     it('should go to next available step when the next button is clicked', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
 
       let nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0]
         .nativeElement;
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
 
       nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[1]
         .nativeElement;
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(2);
+      expect(stepper.selectedIndex).toBe(2);
 
       nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[2]
         .nativeElement;
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(2);
+      expect(stepper.selectedIndex).toBe(2);
     });
 
     it('should set the next stepper button type to "submit"', () => {
@@ -192,34 +172,32 @@ describe('MatStepper', () => {
     });
 
     it('should go to previous available step when the previous button is clicked', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
 
-      stepperComponent.selectedIndex = 2;
+      stepper.selectedIndex = 2;
       let previousButtonNativeEl = fixture.debugElement.queryAll(
         By.directive(MatStepperPrevious),
       )[2].nativeElement;
       previousButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
 
       previousButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperPrevious))[1]
         .nativeElement;
       previousButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
 
       previousButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperPrevious))[0]
         .nativeElement;
       previousButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
     });
 
     it('should set the previous stepper button type to "button"', () => {
@@ -230,33 +208,29 @@ describe('MatStepper', () => {
     });
 
     it('should set the correct step position for animation', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
 
-      expect(stepperComponent._getAnimationDirection(0)).toBe('current');
-      expect(stepperComponent._getAnimationDirection(1)).toBe('next');
-      expect(stepperComponent._getAnimationDirection(2)).toBe('next');
+      expect(stepper._getAnimationDirection(0)).toBe('current');
+      expect(stepper._getAnimationDirection(1)).toBe('next');
+      expect(stepper._getAnimationDirection(2)).toBe('next');
 
-      stepperComponent.selectedIndex = 1;
+      stepper.selectedIndex = 1;
       fixture.detectChanges();
 
-      expect(stepperComponent._getAnimationDirection(0)).toBe('previous');
-      expect(stepperComponent._getAnimationDirection(2)).toBe('next');
-      expect(stepperComponent._getAnimationDirection(1)).toBe('current');
+      expect(stepper._getAnimationDirection(0)).toBe('previous');
+      expect(stepper._getAnimationDirection(2)).toBe('next');
+      expect(stepper._getAnimationDirection(1)).toBe('current');
 
-      stepperComponent.selectedIndex = 2;
+      stepper.selectedIndex = 2;
       fixture.detectChanges();
 
-      expect(stepperComponent._getAnimationDirection(0)).toBe('previous');
-      expect(stepperComponent._getAnimationDirection(1)).toBe('previous');
-      expect(stepperComponent._getAnimationDirection(2)).toBe('current');
+      expect(stepper._getAnimationDirection(0)).toBe('previous');
+      expect(stepper._getAnimationDirection(1)).toBe('previous');
+      expect(stepper._getAnimationDirection(2)).toBe('current');
     });
 
     it('should not set focus on header of selected step if header is not clicked', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
       const stepHeaderEl = fixture.debugElement.queryAll(By.css('mat-step-header'))[1]
         .nativeElement;
       const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0]
@@ -265,14 +239,12 @@ describe('MatStepper', () => {
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
       expect(stepHeaderEl.focus).not.toHaveBeenCalled();
     });
 
     it('should focus next step header if focus is inside the stepper', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
       const stepHeaderEl = fixture.debugElement.queryAll(By.css('mat-step-header'))[1]
         .nativeElement;
       const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0]
@@ -282,7 +254,7 @@ describe('MatStepper', () => {
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
       expect(stepHeaderEl.focus).toHaveBeenCalled();
     });
 
@@ -296,9 +268,7 @@ describe('MatStepper', () => {
       fixture = createComponent(SimpleMatVerticalStepperApp, [], [], ViewEncapsulation.ShadowDom);
       fixture.detectChanges();
 
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
       const stepHeaderEl = fixture.debugElement.queryAll(By.css('mat-step-header'))[1]
         .nativeElement;
       const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0]
@@ -308,62 +278,56 @@ describe('MatStepper', () => {
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
       expect(stepHeaderEl.focus).toHaveBeenCalled();
     });
 
     it('should only be able to return to a previous step if it is editable', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
 
-      stepperComponent.selectedIndex = 1;
-      stepperComponent.steps.toArray()[0].editable = false;
+      stepper.selectedIndex = 1;
+      stepper.steps.toArray()[0].editable = false;
       const previousButtonNativeEl = fixture.debugElement.queryAll(
         By.directive(MatStepperPrevious),
       )[1].nativeElement;
       previousButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
 
-      stepperComponent.steps.toArray()[0].editable = true;
+      stepper.steps.toArray()[0].editable = true;
       previousButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
     });
 
     it('should set create icon if step is editable and completed', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
       const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0]
         .nativeElement;
-      expect(stepperComponent._getIndicatorType(0)).toBe('number');
-      stepperComponent.steps.toArray()[0].editable = true;
+      expect(stepper.steps.first.indicatorType()).toBe('number');
+      stepper.steps.toArray()[0].editable = true;
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent._getIndicatorType(0)).toBe('edit');
+      expect(stepper.steps.first.indicatorType()).toBe('edit');
     });
 
     it('should set done icon if step is not editable and is completed', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
       const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0]
         .nativeElement;
-      expect(stepperComponent._getIndicatorType(0)).toBe('number');
-      stepperComponent.steps.toArray()[0].editable = false;
+      expect(stepper.steps.first.indicatorType()).toBe('number');
+      stepper.steps.toArray()[0].editable = false;
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent._getIndicatorType(0)).toBe('done');
+      expect(stepper.steps.first.indicatorType()).toBe('done');
     });
 
     it('should emit an event when the enter animation is done', () => {
-      const stepper = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
       const selectionChangeSpy = jasmine.createSpy('selectionChange spy');
       const animationDoneSpy = jasmine.createSpy('animationDone spy');
       const selectionChangeSubscription = stepper.selectionChange.subscribe(selectionChangeSpy);
@@ -389,22 +353,20 @@ describe('MatStepper', () => {
     });
 
     it('should adjust the index when removing a step before the current one', () => {
-      const stepperComponent: MatStepper = fixture.debugElement.query(
-        By.css('mat-stepper'),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
 
-      stepperComponent.selectedIndex = 2;
+      stepper.selectedIndex = 2;
       fixture.detectChanges();
 
       // Re-assert since the setter has some extra logic.
-      expect(stepperComponent.selectedIndex).toBe(2);
+      expect(stepper.selectedIndex).toBe(2);
 
       expect(() => {
         fixture.componentInstance.showStepTwo.set(false);
         fixture.detectChanges();
       }).not.toThrow();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
     });
 
     it('should not do anything when pressing the ENTER key with a modifier', () => {
@@ -433,35 +395,32 @@ describe('MatStepper', () => {
     });
 
     it('should add units to unit-less values passed in to animationDuration', () => {
-      const stepperComponent: MatStepper = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
-
-      stepperComponent.animationDuration = '1337';
-      expect(stepperComponent.animationDuration).toBe('1337ms');
+      const stepper = fixture.componentInstance.stepper;
+      stepper.animationDuration = '1337';
+      expect(stepper.animationDuration).toBe('1337ms');
     });
   });
 
   describe('basic stepper when attempting to set the selected step too early', () => {
     it('should not throw', () => {
       const fixture = createComponent(SimpleMatVerticalStepperApp);
-      const stepperComponent: MatStepper = fixture.debugElement.query(
+      const stepper: MatStepper = fixture.debugElement.query(
         By.css('mat-stepper'),
       )!.componentInstance;
 
-      expect(() => stepperComponent.selected).not.toThrow();
+      expect(() => stepper.selected).not.toThrow();
     });
   });
 
   describe('basic stepper when attempting to set the selected step too early', () => {
     it('should not throw', () => {
       const fixture = createComponent(SimpleMatVerticalStepperApp);
-      const stepperComponent: MatStepper = fixture.debugElement.query(
+      const stepper: MatStepper = fixture.debugElement.query(
         By.css('mat-stepper'),
       )!.componentInstance;
 
-      expect(() => (stepperComponent.selected = null!)).not.toThrow();
-      expect(stepperComponent.selectedIndex).toBe(-1);
+      expect(() => (stepper.selected = null!)).not.toThrow();
+      expect(stepper.selectedIndex).toBe(-1);
     });
   });
 
@@ -499,14 +458,14 @@ describe('MatStepper', () => {
 
     it('should re-render when the completed labels change', () => {
       const intl = TestBed.inject(MatStepperIntl);
-      const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
-      const stepperComponent: MatStepper = stepperDebugElement.componentInstance;
+      const stepperElement = fixture.nativeElement.querySelector('mat-stepper');
+      const stepper = fixture.componentInstance.stepper;
 
-      stepperComponent.steps.toArray()[0].editable = false;
-      stepperComponent.next();
+      stepper.steps.toArray()[0].editable = false;
+      stepper.next();
       fixture.detectChanges();
 
-      const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');
+      const header = stepperElement.querySelector('mat-step-header');
       const completedLabel = header.querySelector('.cdk-visually-hidden');
 
       expect(completedLabel).toBeTruthy();
@@ -526,18 +485,19 @@ describe('MatStepper', () => {
     beforeEach(() => {
       fixture = createComponent(SimpleMatHorizontalStepperApp);
       fixture.detectChanges();
+      fixture.detectChanges();
     });
 
     it('should re-render when the editable label changes', () => {
       const intl = TestBed.inject(MatStepperIntl);
-      const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
-      const stepperComponent: MatStepper = stepperDebugElement.componentInstance;
+      const stepperElement = fixture.nativeElement.querySelector('mat-stepper');
+      const stepper = fixture.componentInstance.stepper;
 
-      stepperComponent.steps.toArray()[0].editable = true;
-      stepperComponent.next();
+      stepper.steps.toArray()[0].editable = true;
+      stepper.next();
       fixture.detectChanges();
 
-      const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');
+      const header = stepperElement.querySelector('mat-step-header');
       const editableLabel = header.querySelector('.cdk-visually-hidden');
 
       expect(editableLabel).toBeTruthy();
@@ -560,34 +520,34 @@ describe('MatStepper', () => {
     });
 
     it('should allow for the `edit` icon to be overridden', () => {
-      const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
-      const stepperComponent: MatStepper = stepperDebugElement.componentInstance;
+      const stepperElement = fixture.nativeElement.querySelector('mat-stepper');
+      const stepper = fixture.componentInstance.stepper;
 
-      stepperComponent.steps.toArray()[0].editable = true;
-      stepperComponent.next();
+      stepper.steps.toArray()[0].editable = true;
+      stepper.next();
       fixture.detectChanges();
 
-      const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');
+      const header = stepperElement.querySelector('mat-step-header');
 
       expect(header.textContent).toContain('Custom edit');
     });
 
     it('should allow for the `done` icon to be overridden', () => {
-      const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
-      const stepperComponent: MatStepper = stepperDebugElement.componentInstance;
+      const stepperElement = fixture.nativeElement.querySelector('mat-stepper');
+      const stepper = fixture.componentInstance.stepper;
 
-      stepperComponent.steps.toArray()[0].editable = false;
-      stepperComponent.next();
+      stepper.steps.toArray()[0].editable = false;
+      stepper.next();
       fixture.detectChanges();
 
-      const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');
+      const header = stepperElement.querySelector('mat-step-header');
 
       expect(header.textContent).toContain('Custom done');
     });
 
     it('should allow for the `number` icon to be overridden with context', () => {
-      const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
-      const headers = stepperDebugElement.nativeElement.querySelectorAll('mat-step-header');
+      const stepperElement = fixture.nativeElement.querySelector('mat-stepper');
+      const headers = stepperElement.querySelectorAll('mat-step-header');
 
       expect(headers[2].textContent).toContain('III');
     });
@@ -603,45 +563,43 @@ describe('MatStepper', () => {
     });
 
     it('should reverse animation in RTL mode', () => {
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
 
-      expect(stepperComponent._getAnimationDirection(0)).toBe('current');
-      expect(stepperComponent._getAnimationDirection(1)).toBe('previous');
-      expect(stepperComponent._getAnimationDirection(2)).toBe('previous');
+      expect(stepper._getAnimationDirection(0)).toBe('current');
+      expect(stepper._getAnimationDirection(1)).toBe('previous');
+      expect(stepper._getAnimationDirection(2)).toBe('previous');
 
-      stepperComponent.selectedIndex = 1;
+      stepper.selectedIndex = 1;
       fixture.detectChanges();
 
-      expect(stepperComponent._getAnimationDirection(0)).toBe('next');
-      expect(stepperComponent._getAnimationDirection(2)).toBe('previous');
-      expect(stepperComponent._getAnimationDirection(1)).toBe('current');
+      expect(stepper._getAnimationDirection(0)).toBe('next');
+      expect(stepper._getAnimationDirection(2)).toBe('previous');
+      expect(stepper._getAnimationDirection(1)).toBe('current');
 
-      stepperComponent.selectedIndex = 2;
+      stepper.selectedIndex = 2;
       fixture.detectChanges();
 
-      expect(stepperComponent._getAnimationDirection(0)).toBe('next');
-      expect(stepperComponent._getAnimationDirection(1)).toBe('next');
-      expect(stepperComponent._getAnimationDirection(2)).toBe('current');
+      expect(stepper._getAnimationDirection(0)).toBe('next');
+      expect(stepper._getAnimationDirection(1)).toBe('next');
+      expect(stepper._getAnimationDirection(2)).toBe('current');
     });
   });
 
   describe('linear stepper', () => {
     let fixture: ComponentFixture<LinearMatVerticalStepperApp>;
     let testComponent: LinearMatVerticalStepperApp;
-    let stepperComponent: MatStepper;
+    let stepper: MatStepper;
 
     beforeEach(() => {
       fixture = createComponent(LinearMatVerticalStepperApp, [], [], undefined, []);
       fixture.detectChanges();
 
       testComponent = fixture.componentInstance;
-      stepperComponent = fixture.debugElement.query(By.css('mat-stepper'))!.componentInstance;
+      stepper = fixture.debugElement.query(By.css('mat-stepper'))!.componentInstance;
     });
 
     it('should have true linear attribute', () => {
-      expect(stepperComponent.linear).toBe(true);
+      expect(stepper.linear).toBe(true);
     });
 
     it('should not move to next step if current step is invalid', () => {
@@ -649,7 +607,7 @@ describe('MatStepper', () => {
       expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false);
       expect(testComponent.oneGroup.valid).toBe(false);
       expect(testComponent.oneGroup.invalid).toBe(true);
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
 
       const stepHeaderEl = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'))[1]
         .nativeElement;
@@ -657,21 +615,21 @@ describe('MatStepper', () => {
       stepHeaderEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
 
       const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0]
         .nativeElement;
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
 
       testComponent.oneGroup.get('oneCtrl')!.setValue('answer');
       stepHeaderEl.click();
       fixture.detectChanges();
 
       expect(testComponent.oneGroup.valid).toBe(true);
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
     });
 
     it('should not move to next step if current step is pending', () => {
@@ -683,9 +641,9 @@ describe('MatStepper', () => {
 
       testComponent.oneGroup.get('oneCtrl')!.setValue('input');
       testComponent.twoGroup.get('twoCtrl')!.setValue('input');
-      stepperComponent.selectedIndex = 1;
+      stepper.selectedIndex = 1;
       fixture.detectChanges();
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
 
       // Step status = PENDING
       // Assert that linear stepper does not allow step selection change
@@ -694,12 +652,12 @@ describe('MatStepper', () => {
       stepHeaderEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
 
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
 
       // Trigger asynchronous validation
       testComponent.validationTrigger.next();
@@ -711,16 +669,16 @@ describe('MatStepper', () => {
       stepHeaderEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(2);
+      expect(stepper.selectedIndex).toBe(2);
 
-      stepperComponent.selectedIndex = 1;
+      stepper.selectedIndex = 1;
       fixture.detectChanges();
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
 
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(2);
+      expect(stepper.selectedIndex).toBe(2);
     });
 
     it('should be able to focus step header upon click if it is unable to be selected', () => {
@@ -736,13 +694,13 @@ describe('MatStepper', () => {
       testComponent.oneGroup.get('oneCtrl')!.setValue('input');
       testComponent.twoGroup.get('twoCtrl')!.setValue('input');
       testComponent.validationTrigger.next();
-      stepperComponent.selectedIndex = 1;
+      stepper.selectedIndex = 1;
       fixture.detectChanges();
-      stepperComponent.selectedIndex = 2;
+      stepper.selectedIndex = 2;
       fixture.detectChanges();
 
-      expect(stepperComponent.steps.toArray()[2].optional).toBe(true);
-      expect(stepperComponent.selectedIndex).toBe(2);
+      expect(stepper.steps.toArray()[2].optional).toBe(true);
+      expect(stepper.selectedIndex).toBe(2);
       expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(true);
 
       const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[2]
@@ -750,34 +708,34 @@ describe('MatStepper', () => {
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex)
+      expect(stepper.selectedIndex)
         .withContext('Expected selectedIndex to change when optional step input is empty.')
         .toBe(3);
 
-      stepperComponent.selectedIndex = 2;
+      stepper.selectedIndex = 2;
       testComponent.threeGroup.get('threeCtrl')!.setValue('input');
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
       expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(false);
-      expect(stepperComponent.selectedIndex)
+      expect(stepper.selectedIndex)
         .withContext('Expected selectedIndex to change when optional step input is invalid.')
         .toBe(3);
     });
 
     it('should be able to reset the stepper to its initial state', () => {
-      const steps = stepperComponent.steps.toArray();
+      const steps = stepper.steps.toArray();
 
       testComponent.oneGroup.get('oneCtrl')!.setValue('value');
       fixture.detectChanges();
 
-      stepperComponent.next();
+      stepper.next();
       fixture.detectChanges();
 
-      stepperComponent.next();
+      stepper.next();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
       expect(steps[0].interacted).toBe(true);
       expect(steps[0].completed).toBe(true);
       expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(true);
@@ -787,10 +745,10 @@ describe('MatStepper', () => {
       expect(steps[1].completed).toBe(false);
       expect(testComponent.twoGroup.get('twoCtrl')!.valid).toBe(false);
 
-      stepperComponent.reset();
+      stepper.reset();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
       expect(steps[0].interacted).toBe(false);
       expect(steps[0].completed).toBe(false);
       expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false);
@@ -802,36 +760,36 @@ describe('MatStepper', () => {
     });
 
     it('should reset back to the first step when some of the steps are not editable', () => {
-      const steps = stepperComponent.steps.toArray();
+      const steps = stepper.steps.toArray();
 
       steps[0].editable = false;
 
       testComponent.oneGroup.get('oneCtrl')!.setValue('value');
       fixture.detectChanges();
 
-      stepperComponent.next();
+      stepper.next();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
 
-      stepperComponent.reset();
+      stepper.reset();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
     });
 
     it('should not clobber the `complete` binding when resetting', () => {
-      const steps: CdkStep[] = stepperComponent.steps.toArray();
+      const steps: CdkStep[] = stepper.steps.toArray();
       const fillOutStepper = () => {
         testComponent.oneGroup.get('oneCtrl')!.setValue('input');
         testComponent.twoGroup.get('twoCtrl')!.setValue('input');
         testComponent.threeGroup.get('threeCtrl')!.setValue('valid');
         testComponent.validationTrigger.next();
-        stepperComponent.selectedIndex = 1;
+        stepper.selectedIndex = 1;
         fixture.detectChanges();
-        stepperComponent.selectedIndex = 2;
+        stepper.selectedIndex = 2;
         fixture.detectChanges();
-        stepperComponent.selectedIndex = 3;
+        stepper.selectedIndex = 3;
         fixture.detectChanges();
       };
 
@@ -841,7 +799,7 @@ describe('MatStepper', () => {
         .withContext('Expected third step to be considered complete after the first run through.')
         .toBe(true);
 
-      stepperComponent.reset();
+      stepper.reset();
       fixture.detectChanges();
       fillOutStepper();
 
@@ -856,21 +814,21 @@ describe('MatStepper', () => {
       expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe('');
       expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false);
       expect(testComponent.oneGroup.valid).toBe(false);
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
 
       const nextButtonNativeEl = fixture.debugElement.queryAll(By.directive(MatStepperNext))[0]
         .nativeElement;
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(0);
+      expect(stepper.selectedIndex).toBe(0);
 
-      stepperComponent.steps.first.completed = true;
+      stepper.steps.first.completed = true;
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
       expect(testComponent.oneGroup.valid).toBe(false);
-      expect(stepperComponent.selectedIndex).toBe(1);
+      expect(stepper.selectedIndex).toBe(1);
     });
 
     it('should set aria-disabled if the user is not able to navigate to a step', () => {
@@ -894,7 +852,7 @@ describe('MatStepper', () => {
     beforeEach(() => {
       preselectedFixture = createComponent(SimplePreselectedMatHorizontalStepperApp);
       preselectedFixture.detectChanges();
-      stepper = preselectedFixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
+      stepper = preselectedFixture.componentInstance.stepper;
     });
 
     it('should not throw', () => {
@@ -917,7 +875,7 @@ describe('MatStepper', () => {
     beforeEach(() => {
       fixture = createComponent(LinearMatVerticalStepperAppForAlreadyFilledForm);
       fixture.detectChanges();
-      stepper = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
+      stepper = fixture.componentInstance.stepper;
     });
 
     it('should navigate to previous steps', () => {
@@ -938,9 +896,7 @@ describe('MatStepper', () => {
       noStepControlFixture.detectChanges();
     });
     it('should not move to the next step if the current one is not completed ', () => {
-      const stepper: MatStepper = noStepControlFixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = noStepControlFixture.componentInstance.stepper;
 
       const headers = noStepControlFixture.debugElement.queryAll(
         By.css('.mat-horizontal-stepper-header'),
@@ -966,9 +922,7 @@ describe('MatStepper', () => {
       expect(controlAndBindingFixture.componentInstance.steps[0].control.valid).toBe(true);
       expect(controlAndBindingFixture.componentInstance.steps[0].completed).toBe(false);
 
-      const stepper: MatStepper = controlAndBindingFixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = controlAndBindingFixture.componentInstance.stepper;
 
       const headers = controlAndBindingFixture.debugElement.queryAll(
         By.css('.mat-horizontal-stepper-header'),
@@ -1196,9 +1150,7 @@ describe('MatStepper', () => {
       const fixture = createComponent(SimpleMatHorizontalStepperApp);
       fixture.detectChanges();
 
-      const stepper: MatStepper = fixture.debugElement.query(
-        By.directive(MatStepper),
-      ).componentInstance;
+      const stepper = fixture.componentInstance.stepper;
 
       expect(stepper.steps.map(step => step.interacted)).toEqual([false, false, false]);
 
@@ -1219,9 +1171,7 @@ describe('MatStepper', () => {
       const fixture = createComponent(SimpleMatHorizontalStepperApp);
       fixture.detectChanges();
 
-      const stepper: MatStepper = fixture.debugElement.query(
-        By.directive(MatStepper),
-      ).componentInstance;
+      const stepper = fixture.componentInstance.stepper;
       const interactedSteps: number[] = [];
       const subscription = merge(...stepper.steps.map(step => step.interactedStream)).subscribe(
         step => interactedSteps.push(stepper.steps.toArray().indexOf(step as MatStep)),
@@ -1247,9 +1197,7 @@ describe('MatStepper', () => {
       const fixture = createComponent(SimpleMatHorizontalStepperApp);
       fixture.detectChanges();
 
-      const stepper: MatStepper = fixture.debugElement.query(
-        By.directive(MatStepper),
-      ).componentInstance;
+      const stepper = fixture.componentInstance.stepper;
       const interactedSteps: number[] = [];
       const subscription = merge(...stepper.steps.map(step => step.interactedStream)).subscribe(
         step => interactedSteps.push(stepper.steps.toArray().indexOf(step as MatStep)),
@@ -1383,7 +1331,7 @@ describe('MatStepper', () => {
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.ERROR);
+      expect(stepper.steps.first.indicatorType()).toBe(STEP_STATE.ERROR);
     });
 
     it('should respect a custom falsy hasError value', () => {
@@ -1395,12 +1343,12 @@ describe('MatStepper', () => {
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.ERROR);
+      expect(stepper.steps.first.indicatorType()).toBe(STEP_STATE.ERROR);
 
       stepper.steps.first.hasError = false;
       fixture.detectChanges();
 
-      expect(stepper._getIndicatorType(0)).not.toBe(STEP_STATE.ERROR);
+      expect(stepper.steps.first.indicatorType()).not.toBe(STEP_STATE.ERROR);
     });
 
     it('should show error state if explicitly enabled, even when disabled globally', () => {
@@ -1413,7 +1361,7 @@ describe('MatStepper', () => {
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.ERROR);
+      expect(stepper.steps.first.indicatorType()).toBe(STEP_STATE.ERROR);
     });
   });
 
@@ -1445,7 +1393,7 @@ describe('MatStepper', () => {
       nextButtonNativeEl.click();
       fixture.detectChanges();
 
-      expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.DONE);
+      expect(stepper.steps.first.indicatorType()).toBe(STEP_STATE.DONE);
     });
 
     it('should show edit state when step is editable and its the current step', () => {
@@ -1453,7 +1401,7 @@ describe('MatStepper', () => {
       stepper.steps.toArray()[1].editable = true;
       fixture.detectChanges();
 
-      expect(stepper._getIndicatorType(1)).toBe(STEP_STATE.EDIT);
+      expect(stepper.steps.get(1)!.indicatorType()).toBe(STEP_STATE.EDIT);
     });
   });
 
@@ -1463,42 +1411,40 @@ describe('MatStepper', () => {
       fixture.detectChanges();
 
       const stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
-      const stepperComponent = fixture.debugElement.query(
-        By.directive(MatStepper),
-      )!.componentInstance;
+      const stepper = fixture.componentInstance.stepper;
 
-      expect(stepperComponent.selectedIndex).toBe(0);
-      expect(stepperComponent.selected instanceof MatStep).toBe(true);
+      expect(stepper.selectedIndex).toBe(0);
+      expect(stepper.selected instanceof MatStep).toBe(true);
 
       // select the second step
       let stepHeaderEl = stepHeaders[1].nativeElement;
       stepHeaderEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(1);
-      expect(stepperComponent.selected instanceof MatStep).toBe(true);
+      expect(stepper.selectedIndex).toBe(1);
+      expect(stepper.selected instanceof MatStep).toBe(true);
 
       // select the third step
       stepHeaderEl = stepHeaders[2].nativeElement;
       stepHeaderEl.click();
       fixture.detectChanges();
 
-      expect(stepperComponent.selectedIndex).toBe(2);
-      expect(stepperComponent.selected instanceof MatStep).toBe(true);
+      expect(stepper.selectedIndex).toBe(2);
+      expect(stepper.selected instanceof MatStep).toBe(true);
     });
 
     it('should allow for the `edit` icon to be overridden', () => {
       const fixture = createComponent(IndirectDescendantIconOverridesStepper);
       fixture.detectChanges();
 
-      const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
-      const stepperComponent: MatStepper = stepperDebugElement.componentInstance;
+      const stepperElement = fixture.nativeElement.querySelector('mat-stepper');
+      const stepper = fixture.componentInstance.stepper;
 
-      stepperComponent.steps.toArray()[0].editable = true;
-      stepperComponent.next();
+      stepper.steps.toArray()[0].editable = true;
+      stepper.next();
       fixture.detectChanges();
 
-      const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');
+      const header = stepperElement.querySelector('mat-step-header');
 
       expect(header.textContent).toContain('Custom edit');
     });
@@ -1507,14 +1453,14 @@ describe('MatStepper', () => {
       const fixture = createComponent(IndirectDescendantIconOverridesStepper);
       fixture.detectChanges();
 
-      const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
-      const stepperComponent: MatStepper = stepperDebugElement.componentInstance;
+      const stepperElement = fixture.nativeElement.querySelector('mat-stepper');
+      const stepper = fixture.componentInstance.stepper;
 
-      stepperComponent.steps.toArray()[0].editable = false;
-      stepperComponent.next();
+      stepper.steps.toArray()[0].editable = false;
+      stepper.next();
       fixture.detectChanges();
 
-      const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');
+      const header = stepperElement.querySelector('mat-step-header');
 
       expect(header.textContent).toContain('Custom done');
     });
@@ -1523,8 +1469,8 @@ describe('MatStepper', () => {
       const fixture = createComponent(IndirectDescendantIconOverridesStepper);
       fixture.detectChanges();
 
-      const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
-      const headers = stepperDebugElement.nativeElement.querySelectorAll('mat-step-header');
+      const stepperElement = fixture.nativeElement.querySelector('mat-stepper');
+      const headers = stepperElement.querySelectorAll('mat-step-header');
 
       expect(headers[2].textContent).toContain('III');
     });
@@ -1638,25 +1584,25 @@ describe('MatStepper', () => {
 
 /** Asserts that keyboard interaction works correctly. */
 function assertCorrectKeyboardInteraction(
-  fixture: ComponentFixture<any>,
+  fixture: ComponentFixture<{stepper: MatStepper}>,
   stepHeaders: DebugElement[],
   orientation: StepperOrientation,
 ) {
-  const stepperComponent = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
+  const stepper = fixture.componentInstance.stepper;
   const nextKey = orientation === 'vertical' ? DOWN_ARROW : RIGHT_ARROW;
   const prevKey = orientation === 'vertical' ? UP_ARROW : LEFT_ARROW;
 
-  expect(stepperComponent._getFocusIndex()).toBe(0);
-  expect(stepperComponent.selectedIndex).toBe(0);
+  expect(stepper._getFocusIndex()).toBe(0);
+  expect(stepper.selectedIndex).toBe(0);
 
   let stepHeaderEl = stepHeaders[0].nativeElement;
   dispatchKeyboardEvent(stepHeaderEl, 'keydown', nextKey);
   fixture.detectChanges();
 
-  expect(stepperComponent._getFocusIndex())
+  expect(stepper._getFocusIndex())
     .withContext('Expected index of focused step to increase by 1 after pressing the next key.')
     .toBe(1);
-  expect(stepperComponent.selectedIndex)
+  expect(stepper.selectedIndex)
     .withContext('Expected index of selected step to remain unchanged after pressing the next key.')
     .toBe(0);
 
@@ -1664,10 +1610,10 @@ function assertCorrectKeyboardInteraction(
   dispatchKeyboardEvent(stepHeaderEl, 'keydown', ENTER);
   fixture.detectChanges();
 
-  expect(stepperComponent._getFocusIndex())
+  expect(stepper._getFocusIndex())
     .withContext('Expected index of focused step to remain unchanged after ENTER event.')
     .toBe(1);
-  expect(stepperComponent.selectedIndex)
+  expect(stepper.selectedIndex)
     .withContext(
       'Expected index of selected step to change to index of focused step ' + 'after ENTER event.',
     )
@@ -1677,12 +1623,12 @@ function assertCorrectKeyboardInteraction(
   dispatchKeyboardEvent(stepHeaderEl, 'keydown', prevKey);
   fixture.detectChanges();
 
-  expect(stepperComponent._getFocusIndex())
+  expect(stepper._getFocusIndex())
     .withContext(
       'Expected index of focused step to decrease by 1 after pressing the ' + 'previous key.',
     )
     .toBe(0);
-  expect(stepperComponent.selectedIndex)
+  expect(stepper.selectedIndex)
     .withContext(
       'Expected index of selected step to remain unchanged after pressing the ' + 'previous key.',
     )
@@ -1690,18 +1636,18 @@ function assertCorrectKeyboardInteraction(
 
   // When the focus is on the last step and right arrow key is pressed, the focus should cycle
   // through to the first step.
-  stepperComponent._keyManager.updateActiveItem(2);
+  (stepper as any)._keyManager.updateActiveItem(2);
   stepHeaderEl = stepHeaders[2].nativeElement;
   dispatchKeyboardEvent(stepHeaderEl, 'keydown', nextKey);
   fixture.detectChanges();
 
-  expect(stepperComponent._getFocusIndex())
+  expect(stepper._getFocusIndex())
     .withContext(
       'Expected index of focused step to cycle through to index 0 after pressing ' +
         'the next key.',
     )
     .toBe(0);
-  expect(stepperComponent.selectedIndex)
+  expect(stepper.selectedIndex)
     .withContext(
       'Expected index of selected step to remain unchanged after pressing ' + 'the next key.',
     )
@@ -1711,17 +1657,17 @@ function assertCorrectKeyboardInteraction(
   dispatchKeyboardEvent(stepHeaderEl, 'keydown', SPACE);
   fixture.detectChanges();
 
-  expect(stepperComponent._getFocusIndex())
+  expect(stepper._getFocusIndex())
     .withContext('Expected index of focused to remain unchanged after SPACE event.')
     .toBe(0);
-  expect(stepperComponent.selectedIndex)
+  expect(stepper.selectedIndex)
     .withContext(
       'Expected index of selected step to change to index of focused step ' + 'after SPACE event.',
     )
     .toBe(0);
 
   const endEvent = dispatchKeyboardEvent(stepHeaderEl, 'keydown', END);
-  expect(stepperComponent._getFocusIndex())
+  expect(stepper._getFocusIndex())
     .withContext('Expected last step to be focused when pressing END.')
     .toBe(stepHeaders.length - 1);
   expect(endEvent.defaultPrevented)
@@ -1729,7 +1675,7 @@ function assertCorrectKeyboardInteraction(
     .toBe(true);
 
   const homeEvent = dispatchKeyboardEvent(stepHeaderEl, 'keydown', HOME);
-  expect(stepperComponent._getFocusIndex())
+  expect(stepper._getFocusIndex())
     .withContext('Expected first step to be focused when pressing HOME.')
     .toBe(0);
   expect(homeEvent.defaultPrevented)
@@ -1739,38 +1685,37 @@ function assertCorrectKeyboardInteraction(
 
 /** Asserts that arrow key direction works correctly in RTL mode. */
 function assertArrowKeyInteractionInRtl(
-  fixture: ComponentFixture<any>,
+  fixture: ComponentFixture<{stepper: MatStepper}>,
   stepHeaders: DebugElement[],
 ) {
-  const stepperComponent = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
-
-  expect(stepperComponent._getFocusIndex()).toBe(0);
+  const stepper = fixture.componentInstance.stepper;
+  expect(stepper._getFocusIndex()).toBe(0);
 
   let stepHeaderEl = stepHeaders[0].nativeElement;
   dispatchKeyboardEvent(stepHeaderEl, 'keydown', LEFT_ARROW);
   fixture.detectChanges();
 
-  expect(stepperComponent._getFocusIndex()).toBe(1);
+  expect(stepper._getFocusIndex()).toBe(1);
 
   stepHeaderEl = stepHeaders[1].nativeElement;
   dispatchKeyboardEvent(stepHeaderEl, 'keydown', RIGHT_ARROW);
   fixture.detectChanges();
 
-  expect(stepperComponent._getFocusIndex()).toBe(0);
+  expect(stepper._getFocusIndex()).toBe(0);
 }
 
 /** Asserts that keyboard interaction works correctly when the user is pressing a modifier key. */
 function assertSelectKeyWithModifierInteraction(
-  fixture: ComponentFixture<any>,
+  fixture: ComponentFixture<{stepper: MatStepper}>,
   stepHeaders: DebugElement[],
   orientation: StepperOrientation,
   selectionKey: number,
 ) {
-  const stepperComponent = fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
+  const stepper = fixture.componentInstance.stepper;
   const modifiers = ['altKey', 'shiftKey', 'ctrlKey', 'metaKey'];
 
-  expect(stepperComponent._getFocusIndex()).toBe(0);
-  expect(stepperComponent.selectedIndex).toBe(0);
+  expect(stepper._getFocusIndex()).toBe(0);
+  expect(stepper.selectedIndex).toBe(0);
 
   dispatchKeyboardEvent(
     stepHeaders[0].nativeElement,
@@ -1779,12 +1724,12 @@ function assertSelectKeyWithModifierInteraction(
   );
   fixture.detectChanges();
 
-  expect(stepperComponent._getFocusIndex())
+  expect(stepper._getFocusIndex())
     .withContext(
       'Expected index of focused step to increase by 1 after pressing ' + 'the next key.',
     )
     .toBe(1);
-  expect(stepperComponent.selectedIndex)
+  expect(stepper.selectedIndex)
     .withContext(
       'Expected index of selected step to remain unchanged after pressing ' + 'the next key.',
     )
@@ -1796,7 +1741,7 @@ function assertSelectKeyWithModifierInteraction(
     dispatchEvent(stepHeaders[1].nativeElement, event);
     fixture.detectChanges();
 
-    expect(stepperComponent.selectedIndex)
+    expect(stepper.selectedIndex)
       .withContext(
         `Expected selected index to remain unchanged ` +
           `when pressing the selection key with ${modifier} modifier.`,
@@ -1827,7 +1772,6 @@ function createComponent<T>(
   TestBed.configureTestingModule({
     imports: [MatStepperModule, ReactiveFormsModule, ...imports],
     providers: [
-      provideCheckNoChangesConfig({exhaustive: false}),
       provideFakeDirectionality(dir),
       {provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}},
       ...providers,
@@ -1917,6 +1861,7 @@ class MatHorizontalStepperWithErrorsApp {
   standalone: false,
 })
 class SimpleMatHorizontalStepperApp {
+  @ViewChild(MatStepper) stepper: MatStepper;
   inputLabel = 'Step 3';
   disableRipple = signal(false);
   stepperTheme = signal<ThemePalette>(undefined);
@@ -1957,6 +1902,7 @@ class SimpleMatHorizontalStepperApp {
   standalone: false,
 })
 class SimpleMatVerticalStepperApp {
+  @ViewChild(MatStepper) stepper: MatStepper;
   inputLabel = signal('Step 3');
   showStepTwo = signal(true);
   disableRipple = signal(false);
@@ -2028,6 +1974,7 @@ class LinearMatVerticalStepperApp {
   standalone: false,
 })
 class SimplePreselectedMatHorizontalStepperApp {
+  @ViewChild(MatStepper) stepper: MatStepper;
   index = 0;
 }
 
@@ -2073,6 +2020,7 @@ class SimplePreselectedMatHorizontalStepperApp {
   standalone: false,
 })
 class LinearMatVerticalStepperAppForAlreadyFilledForm {
+  @ViewChild(MatStepper) stepper: MatStepper;
   selectedIndex = signal(2);
 
   oneGroup = new FormGroup({
@@ -2097,6 +2045,7 @@ class LinearMatVerticalStepperAppForAlreadyFilledForm {
   standalone: false,
 })
 class SimpleStepperWithoutStepControl {
+  @ViewChild(MatStepper) stepper: MatStepper;
   steps = [
     {label: 'One', completed: false},
     {label: 'Two', completed: false},
@@ -2118,6 +2067,8 @@ class SimpleStepperWithoutStepControl {
   standalone: false,
 })
 class SimpleStepperWithStepControlAndCompletedBinding {
+  @ViewChild(MatStepper) stepper: MatStepper;
+
   steps = [
     {label: 'One', completed: false, control: new FormControl('')},
     {label: 'Two', completed: false, control: new FormControl('')},
@@ -2142,6 +2093,8 @@ class SimpleStepperWithStepControlAndCompletedBinding {
   standalone: false,
 })
 class IconOverridesStepper {
+  @ViewChild(MatStepper) stepper: MatStepper;
+
   getRomanNumeral(value: number) {
     const numberMap: {[key: number]: string} = {
       1: 'I',
@@ -2219,7 +2172,9 @@ class StepperWithAriaInputs {
   `,
   standalone: false,
 })
-class StepperWithIndirectDescendantSteps {}
+class StepperWithIndirectDescendantSteps {
+  @ViewChild(MatStepper) stepper: MatStepper;
+}
 
 @Component({
   template: `
@@ -2310,6 +2265,7 @@ class StepperWithLazyContent {
   standalone: false,
 })
 class HorizontalStepperWithDelayedStep {
+  @ViewChild(MatStepper) stepper: MatStepper;
   renderSecondStep = signal(false);
 }
 
diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts
index 17b7781e1907..6d1399dae0df 100644
--- a/src/material/stepper/stepper.ts
+++ b/src/material/stepper/stepper.ts
@@ -283,10 +283,6 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten
     this._cleanupTransition?.();
   }
 
-  _stepIsNavigable(index: number, step: MatStep): boolean {
-    return step.completed || this.selectedIndex === index || !this.linear;
-  }
-
   _getAnimationDuration() {
     if (this._animationsDisabled) {
       return '0ms';