diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-active-descendant/cdk-tree-active-descendant-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-active-descendant/cdk-tree-active-descendant-example.html
new file mode 100644
index 000000000000..3664c058e89a
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-active-descendant/cdk-tree-active-descendant-example.html
@@ -0,0 +1,35 @@
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ } }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-active-descendant/cdk-tree-active-descendant-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-active-descendant/cdk-tree-active-descendant-example.ts
new file mode 100644
index 000000000000..796e04456b6e
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-active-descendant/cdk-tree-active-descendant-example.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {Component} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {TreeNode, NODES} from '../tree-data';
+
+/**
+ * @title Tree with active descendant focus.
+ */
+@Component({
+ selector: 'cdk-tree-active-descendant-example',
+ exportAs: 'cdkTreeActiveDescendantExample',
+ templateUrl: 'cdk-tree-active-descendant-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
+})
+export class CdkTreeActiveDescendantExample {
+ nodes: TreeNode[] = NODES;
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-configurable/cdk-tree-configurable-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-configurable/cdk-tree-configurable-example.html
new file mode 100644
index 000000000000..eaa03543ae97
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-configurable/cdk-tree-configurable-example.html
@@ -0,0 +1,76 @@
+
+ Wrap
+ Multi
+ Disabled
+ Skip Disabled
+ Nav Mode
+
+
+ Selection Strategy
+
+ Explicit
+ Follow
+
+
+
+
+ Focus Strategy
+
+ Roving
+ Active Descendant
+
+
+
+
+
+ Selected Values: {{ selectedValues().join(', ') || 'None' }}
+
+
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ }
+ }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-configurable/cdk-tree-configurable-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-configurable/cdk-tree-configurable-example.ts
new file mode 100644
index 000000000000..5eb796390743
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-configurable/cdk-tree-configurable-example.ts
@@ -0,0 +1,55 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+import {Component, model} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
+import {MatCheckboxModule} from '@angular/material/checkbox';
+import {MatFormFieldModule} from '@angular/material/form-field';
+import {MatSelectModule} from '@angular/material/select';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {NODES, TreeNode} from '../tree-data';
+
+/** @title Configurable Tree. */
+@Component({
+ selector: 'cdk-tree-configurable-example',
+ exportAs: 'cdkTreeConfigurableExample',
+ templateUrl: 'cdk-tree-configurable-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [
+ FormsModule,
+ ReactiveFormsModule,
+ MatCheckboxModule,
+ MatFormFieldModule,
+ MatSelectModule,
+ NgTemplateOutlet,
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+ ],
+})
+export class CdkTreeConfigurableExample {
+ nodes: TreeNode[] = NODES;
+
+ selectionMode: 'explicit' | 'follow' = 'explicit';
+ focusMode: 'roving' | 'activedescendant' = 'roving';
+
+ multi = new FormControl(false, {nonNullable: true});
+ disabled = new FormControl(false, {nonNullable: true});
+ wrap = new FormControl(true, {nonNullable: true});
+ skipDisabled = new FormControl(true, {nonNullable: true});
+ nav = new FormControl(false, {nonNullable: true});
+
+ selectedValues = model(['package.json']);
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-focusable/cdk-tree-disabled-focusable-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-focusable/cdk-tree-disabled-focusable-example.html
new file mode 100644
index 000000000000..9e33a443440a
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-focusable/cdk-tree-disabled-focusable-example.html
@@ -0,0 +1,35 @@
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ } }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-focusable/cdk-tree-disabled-focusable-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-focusable/cdk-tree-disabled-focusable-example.ts
new file mode 100644
index 000000000000..76b7e790d5e8
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-focusable/cdk-tree-disabled-focusable-example.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {Component} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {TreeNode, NODES} from '../tree-data';
+
+/**
+ * @title Tree with focusable disabled items.
+ */
+@Component({
+ selector: 'cdk-tree-disabled-focusable-example',
+ exportAs: 'cdkTreeDisabledFocusableExample',
+ templateUrl: 'cdk-tree-disabled-focusable-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
+})
+export class CdkTreeDisabledFocusableExample {
+ nodes: TreeNode[] = NODES;
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-skipped/cdk-tree-disabled-skipped-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-skipped/cdk-tree-disabled-skipped-example.html
new file mode 100644
index 000000000000..240ffa174316
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-skipped/cdk-tree-disabled-skipped-example.html
@@ -0,0 +1,35 @@
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ } }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-skipped/cdk-tree-disabled-skipped-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-skipped/cdk-tree-disabled-skipped-example.ts
new file mode 100644
index 000000000000..47001f183eed
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled-skipped/cdk-tree-disabled-skipped-example.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {Component} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {TreeNode, NODES} from '../tree-data';
+
+/**
+ * @title Tree with skipped disabled items.
+ */
+@Component({
+ selector: 'cdk-tree-disabled-skipped-example',
+ exportAs: 'cdkTreeDisabledSkippedExample',
+ templateUrl: 'cdk-tree-disabled-skipped-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
+})
+export class CdkTreeDisabledSkippedExample {
+ nodes: TreeNode[] = NODES;
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-disabled/cdk-tree-disabled-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled/cdk-tree-disabled-example.html
new file mode 100644
index 000000000000..82709b9c04d7
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled/cdk-tree-disabled-example.html
@@ -0,0 +1,35 @@
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ } }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-disabled/cdk-tree-disabled-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled/cdk-tree-disabled-example.ts
new file mode 100644
index 000000000000..e913c03084b1
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-disabled/cdk-tree-disabled-example.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {Component} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {TreeNode, NODES} from '../tree-data';
+
+/**
+ * @title Tree with disabled state.
+ */
+@Component({
+ selector: 'cdk-tree-disabled-example',
+ exportAs: 'cdkTreeDisabledExample',
+ templateUrl: 'cdk-tree-disabled-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
+})
+export class CdkTreeDisabledExample {
+ nodes: TreeNode[] = NODES;
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select-follow-focus/cdk-tree-multi-select-follow-focus-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select-follow-focus/cdk-tree-multi-select-follow-focus-example.html
new file mode 100644
index 000000000000..25a07e19fe0d
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select-follow-focus/cdk-tree-multi-select-follow-focus-example.html
@@ -0,0 +1,35 @@
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ } }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select-follow-focus/cdk-tree-multi-select-follow-focus-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select-follow-focus/cdk-tree-multi-select-follow-focus-example.ts
new file mode 100644
index 000000000000..d88e35b1b740
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select-follow-focus/cdk-tree-multi-select-follow-focus-example.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {Component} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {TreeNode, NODES} from '../tree-data';
+
+/**
+ * @title Tree with multi-selection and follow focus.
+ */
+@Component({
+ selector: 'cdk-tree-multi-select-follow-focus-example',
+ exportAs: 'cdkTreeMultiSelectFollowFocusExample',
+ templateUrl: 'cdk-tree-multi-select-follow-focus-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
+})
+export class CdkTreeMultiSelectFollowFocusExample {
+ nodes: TreeNode[] = NODES;
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select/cdk-tree-multi-select-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select/cdk-tree-multi-select-example.html
new file mode 100644
index 000000000000..410b78b6aa7e
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select/cdk-tree-multi-select-example.html
@@ -0,0 +1,36 @@
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ }
+ }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select/cdk-tree-multi-select-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select/cdk-tree-multi-select-example.ts
new file mode 100644
index 000000000000..e2c57f7050d0
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-multi-select/cdk-tree-multi-select-example.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {Component} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {TreeNode, NODES} from '../tree-data';
+
+/**
+ * @title Tree with multi-selection.
+ */
+@Component({
+ selector: 'cdk-tree-multi-select-example',
+ exportAs: 'cdkTreeMultiSelectExample',
+ templateUrl: 'cdk-tree-multi-select-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
+})
+export class CdkTreeMultiSelectExample {
+ nodes: TreeNode[] = NODES;
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-nav/cdk-tree-nav-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-nav/cdk-tree-nav-example.html
new file mode 100644
index 000000000000..ad9e4aae2e6f
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-nav/cdk-tree-nav-example.html
@@ -0,0 +1,48 @@
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ }
+ }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-nav/cdk-tree-nav-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-nav/cdk-tree-nav-example.ts
new file mode 100644
index 000000000000..66ff271015ac
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-nav/cdk-tree-nav-example.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {Component} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {TreeNode, NODES} from '../tree-data';
+
+/**
+ * @title Tree with nav mode.
+ */
+@Component({
+ selector: 'cdk-tree-nav-example',
+ exportAs: 'cdkTreeNavExample',
+ templateUrl: 'cdk-tree-nav-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
+})
+export class CdkTreeNavExample {
+ nodes: TreeNode[] = NODES;
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-single-select-follow-focus/cdk-tree-single-select-follow-focus-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-single-select-follow-focus/cdk-tree-single-select-follow-focus-example.html
new file mode 100644
index 000000000000..5397f9620ce7
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-single-select-follow-focus/cdk-tree-single-select-follow-focus-example.html
@@ -0,0 +1,35 @@
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ } }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-single-select-follow-focus/cdk-tree-single-select-follow-focus-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-single-select-follow-focus/cdk-tree-single-select-follow-focus-example.ts
new file mode 100644
index 000000000000..b6b1295199df
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-single-select-follow-focus/cdk-tree-single-select-follow-focus-example.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {Component} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {TreeNode, NODES} from '../tree-data';
+
+/**
+ * @title Tree with single selection and follow focus.
+ */
+@Component({
+ selector: 'cdk-tree-single-select-follow-focus-example',
+ exportAs: 'cdkTreeSingleSelectFollowFocusExample',
+ templateUrl: 'cdk-tree-single-select-follow-focus-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
+})
+export class CdkTreeSingleSelectFollowFocusExample {
+ nodes: TreeNode[] = NODES;
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-single-select/cdk-tree-single-select-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree-single-select/cdk-tree-single-select-example.html
new file mode 100644
index 000000000000..0b5c27e9b945
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-single-select/cdk-tree-single-select-example.html
@@ -0,0 +1,35 @@
+
+
+
+ @for (node of nodes; track node.value) {
+
+ {{node.children ? 'chevron_right' : ''}}
+ {{node.children ? 'folder' : 'docs'}}
+ {{ node.name }}
+ check
+
+
+ @if (node.children) {
+
+ } }
+
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree-single-select/cdk-tree-single-select-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree-single-select/cdk-tree-single-select-example.ts
new file mode 100644
index 000000000000..17af4f90c448
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/cdk-tree-single-select/cdk-tree-single-select-example.ts
@@ -0,0 +1,32 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import {Component} from '@angular/core';
+import {NgTemplateOutlet} from '@angular/common';
+import {
+ CdkTree,
+ CdkTreeItem,
+ CdkTreeItemGroup,
+ CdkTreeItemGroupContent,
+} from '@angular/cdk-experimental/tree';
+import {TreeNode, NODES} from '../tree-data';
+
+/**
+ * @title Tree with single selection.
+ */
+@Component({
+ selector: 'cdk-tree-single-select-example',
+ exportAs: 'cdkTreeSingleSelectExample',
+ templateUrl: 'cdk-tree-single-select-example.html',
+ styleUrl: '../tree-common.css',
+ standalone: true,
+ imports: [CdkTree, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent, NgTemplateOutlet],
+})
+export class CdkTreeSingleSelectExample {
+ nodes: TreeNode[] = NODES;
+}
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.html b/src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.html
deleted file mode 100644
index 55c66aad4daf..000000000000
--- a/src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.html
+++ /dev/null
@@ -1,93 +0,0 @@
-
- Wrap
- Multi
- Disabled
- Skip Disabled
- Nav Mode
-
-
- Orientation
-
- Vertical
- Horizontal
-
-
-
-
- Selection Strategy
-
- Explicit
- Follow
-
-
-
-
- Focus Strategy
-
- Roving
- Active Descendant
-
-
-
-
-
- Selected Values: {{ selectedValues().join(', ') || 'None' }}
-
-
-
- @if (nav.value) {
- @for (node of treeData; track node) {
-
- }
- } @else {
- @for (node of treeData; track node) {
-
- }
- }
-
-
-
-
-
-
- @if (treeItem.pattern.expandable()) {
- {{ treeItem.pattern.expanded() ? 'expand_less' : 'expand_more' }}
- }
-
- {{ node.label }}
-
-
- @if (node.children !== undefined && node.children!.length > 0) {
-
-
- @for (child of node.children; track child) {
-
- }
-
-
- }
-
-
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.ts b/src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.ts
deleted file mode 100644
index 4a0923d70f13..000000000000
--- a/src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import {Component, model, input} from '@angular/core';
-import {NgTemplateOutlet} from '@angular/common';
-import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
-import {MatCheckboxModule} from '@angular/material/checkbox';
-import {MatFormFieldModule} from '@angular/material/form-field';
-import {MatSelectModule} from '@angular/material/select';
-import {MatIconModule} from '@angular/material/icon';
-import {
- CdkTree,
- CdkTreeItem,
- CdkTreeItemGroup,
- CdkTreeItemGroupContent,
-} from '@angular/cdk-experimental/tree';
-
-interface ExampleNode {
- value: string;
- label?: string;
- disabled?: boolean;
- expanded?: boolean;
- children?: ExampleNode[];
-}
-
-@Component({
- selector: 'example-node',
- styleUrl: 'cdk-tree-example.css',
- template: `
-
-
-
- @if (treeItem.pattern.expandable()) {
- {{ treeItem.pattern.expanded() ? 'expand_less' : 'expand_more' }}
- }
-
- {{ node().label }}
-
-
- @if (node().children !== undefined && node().children!.length > 0) {
-
-
- @for (child of node().children; track child) {
-
- }
-
-
- }
-
- `,
- imports: [MatIconModule, CdkTreeItem, CdkTreeItemGroup, CdkTreeItemGroupContent],
-})
-export class ExampleNodeComponent {
- node = input.required();
-
- parent = input.required | CdkTreeItemGroup>();
-}
-
-/** @title Tree using CdkTree and CdkTreeItem. */
-@Component({
- selector: 'cdk-tree-example',
- exportAs: 'cdkTreeExample',
- templateUrl: 'cdk-tree-example.html',
- styleUrl: 'cdk-tree-example.css',
- imports: [
- FormsModule,
- ReactiveFormsModule,
- MatCheckboxModule,
- MatFormFieldModule,
- MatSelectModule,
- MatIconModule,
- NgTemplateOutlet,
- CdkTree,
- CdkTreeItem,
- CdkTreeItemGroup,
- CdkTreeItemGroupContent,
- ExampleNodeComponent,
- ],
-})
-export class CdkTreeExample {
- // Tree data
- treeData: ExampleNode[] = [
- {
- value: 'electronics',
- label: 'electronics',
- children: [
- {
- value: 'audio',
- label: 'audio equipment',
- children: [
- {value: 'headphones', label: 'headphones'},
- {value: 'speakers', label: 'speakers (disabled)', disabled: true},
- {value: 'amps', label: 'amplifiers'},
- ],
- },
- {
- value: 'computers',
- label: 'computers & tablets',
- children: [
- {value: 'laptops', label: 'laptops'},
- {value: 'desktops', label: 'desktops'},
- {value: 'tablets', label: 'tablets'},
- ],
- },
- {value: 'cameras', label: 'cameras'},
- ],
- },
- {
- value: 'furniture',
- label: 'furniture',
- children: [
- {value: 'tables', label: 'tables'},
- {value: 'chairs', label: 'chairs'},
- {value: 'sofas', label: 'sofas'},
- ],
- },
- {
- value: 'books',
- label: 'books (no children)',
- },
- {
- value: 'clothing',
- label: 'clothing (disabled parent)',
- disabled: true,
- children: [
- {value: 'shirts', label: 'shirts'},
- {value: 'pants', label: 'pants'},
- ],
- },
- ];
-
- // TODO(ok7sai): add styling to horizontal tree view.
- orientation: 'vertical' | 'horizontal' = 'vertical';
- selectionMode: 'explicit' | 'follow' = 'explicit';
- focusMode: 'roving' | 'activedescendant' = 'roving';
-
- multi = new FormControl(false, {nonNullable: true});
- disabled = new FormControl(false, {nonNullable: true});
- wrap = new FormControl(true, {nonNullable: true});
- skipDisabled = new FormControl(true, {nonNullable: true});
- nav = new FormControl(false, {nonNullable: true});
-
- selectedValues = model(['books']);
-}
diff --git a/src/components-examples/cdk-experimental/tree/index.ts b/src/components-examples/cdk-experimental/tree/index.ts
index 731d29286979..06ce14065313 100644
--- a/src/components-examples/cdk-experimental/tree/index.ts
+++ b/src/components-examples/cdk-experimental/tree/index.ts
@@ -1 +1,10 @@
-export {CdkTreeExample} from './cdk-tree/cdk-tree-example';
+export {CdkTreeConfigurableExample} from './cdk-tree-configurable/cdk-tree-configurable-example';
+export {CdkTreeActiveDescendantExample} from './cdk-tree-active-descendant/cdk-tree-active-descendant-example';
+export {CdkTreeDisabledExample} from './cdk-tree-disabled/cdk-tree-disabled-example';
+export {CdkTreeDisabledFocusableExample} from './cdk-tree-disabled-focusable/cdk-tree-disabled-focusable-example';
+export {CdkTreeDisabledSkippedExample} from './cdk-tree-disabled-skipped/cdk-tree-disabled-skipped-example';
+export {CdkTreeMultiSelectExample} from './cdk-tree-multi-select/cdk-tree-multi-select-example';
+export {CdkTreeMultiSelectFollowFocusExample} from './cdk-tree-multi-select-follow-focus/cdk-tree-multi-select-follow-focus-example';
+export {CdkTreeNavExample} from './cdk-tree-nav/cdk-tree-nav-example';
+export {CdkTreeSingleSelectExample} from './cdk-tree-single-select/cdk-tree-single-select-example';
+export {CdkTreeSingleSelectFollowFocusExample} from './cdk-tree-single-select-follow-focus/cdk-tree-single-select-follow-focus-example';
diff --git a/src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.css b/src/components-examples/cdk-experimental/tree/tree-common.css
similarity index 54%
rename from src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.css
rename to src/components-examples/cdk-experimental/tree/tree-common.css
index 8b1e8377f4d0..6f936085d232 100644
--- a/src/components-examples/cdk-experimental/tree/cdk-tree/cdk-tree-example.css
+++ b/src/components-examples/cdk-experimental/tree/tree-common.css
@@ -20,16 +20,32 @@
.example-tree-item {
cursor: pointer;
- user-select: none;
list-style: none;
-}
-
-.example-tree-item-content {
+ text-decoration: none;
display: flex;
align-items: center;
- padding: 2px 0; /* Minimal padding for item itself */
+ gap: 1rem;
+ padding: 0.3rem 1rem;
+}
+
+.example-icon {
+ margin: 0;
+ width: 24px;
+}
+
+.example-parent-icon {
+ transition: transform 0.2s ease;
+}
+
+.example-tree-item[aria-expanded='true'] .example-parent-icon {
+ transform: rotate(90deg);
+}
+
+.example-selected-icon {
+ visibility: hidden;
+ margin-left: auto;
}
-.example-tree-item-icon {
- margin-right: 8px;
+.example-tree-item[aria-selected='true'] .example-selected-icon {
+ visibility: visible;
}
diff --git a/src/components-examples/cdk-experimental/tree/tree-data.ts b/src/components-examples/cdk-experimental/tree/tree-data.ts
new file mode 100644
index 000000000000..8436794a43b8
--- /dev/null
+++ b/src/components-examples/cdk-experimental/tree/tree-data.ts
@@ -0,0 +1,60 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+export type TreeNode = {
+ name: string;
+ value: string;
+ children?: TreeNode[];
+ disabled?: boolean;
+};
+
+export const NODES: TreeNode[] = [
+ {
+ name: 'public',
+ value: 'public',
+ children: [
+ {name: 'index.html', value: 'public/index.html'},
+ {name: 'favicon.ico', value: 'public/favicon.ico'},
+ {name: 'styles.css', value: 'public/styles.css'},
+ ],
+ },
+ {
+ name: 'src',
+ value: 'src',
+ children: [
+ {
+ name: 'app',
+ value: 'src/app',
+ children: [
+ {name: 'app.component.ts', value: 'src/app/app.component.ts'},
+ {name: 'app.module.ts', value: 'src/app/app.module.ts', disabled: true},
+ {name: 'app.css', value: 'src/app/app.css'},
+ ],
+ },
+ {
+ name: 'assets',
+ value: 'src/assets',
+ children: [{name: 'logo.png', value: 'src/assets/logo.png'}],
+ },
+ {
+ name: 'environments',
+ value: 'src/environments',
+ children: [
+ {name: 'environment.prod.ts', value: 'src/environments/environment.prod.ts'},
+ {name: 'environment.ts', value: 'src/environments/environment.ts'},
+ ],
+ },
+ {name: 'main.ts', value: 'src/main.ts'},
+ {name: 'polyfills.ts', value: 'src/polyfills.ts'},
+ {name: 'styles.css', value: 'src/styles.css', disabled: true},
+ {name: 'test.ts', value: 'src/test.ts'},
+ ],
+ },
+ {name: 'angular.json', value: 'angular.json'},
+ {name: 'package.json', value: 'package.json'},
+ {name: 'README.md', value: 'README.md'},
+];
diff --git a/src/dev-app/cdk-experimental-tree/BUILD.bazel b/src/dev-app/cdk-experimental-tree/BUILD.bazel
index 9091a214d35a..09b225cf66cd 100644
--- a/src/dev-app/cdk-experimental-tree/BUILD.bazel
+++ b/src/dev-app/cdk-experimental-tree/BUILD.bazel
@@ -5,6 +5,9 @@ package(default_visibility = ["//visibility:public"])
ng_project(
name = "cdk-experimental-tree",
srcs = glob(["**/*.ts"]),
- assets = ["cdk-tree-demo.html"],
+ assets = [
+ "cdk-tree-demo.html",
+ "cdk-tree-demo.css",
+ ],
deps = ["//src/components-examples/cdk-experimental/tree"],
)
diff --git a/src/dev-app/cdk-experimental-tree/cdk-tree-demo.css b/src/dev-app/cdk-experimental-tree/cdk-tree-demo.css
new file mode 100644
index 000000000000..d20f23bdfdd4
--- /dev/null
+++ b/src/dev-app/cdk-experimental-tree/cdk-tree-demo.css
@@ -0,0 +1,20 @@
+.example-tree-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
+ gap: 20px;
+}
+
+.example-tree-container {
+ width: 500px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+}
+
+.example-configurable-tree-container {
+ padding-top: 40px;
+}
+
+h2 {
+ font-size: 1.1rem;
+}
diff --git a/src/dev-app/cdk-experimental-tree/cdk-tree-demo.html b/src/dev-app/cdk-experimental-tree/cdk-tree-demo.html
index 76cfa8843aef..3d530113dd1f 100644
--- a/src/dev-app/cdk-experimental-tree/cdk-tree-demo.html
+++ b/src/dev-app/cdk-experimental-tree/cdk-tree-demo.html
@@ -1,4 +1,53 @@
-
Tree View using UI Patterns
-
+
+
+
Multi Select with Selection Follows Focus
+
+
+
+
+
Single Select with Selection Follows Focus
+
+
+
+
+
Multi Select
+
+
+
+
+
Single Select
+
+
+
+
+
Disabled
+
+
+
+
+
Disabled Options are Skipped
+
+
+
+
+
Disabled Options are Focusable
+
+
+
+
+
Active Descendant
+
+
+
+
+
Nav Mode
+
+
+
+
+
+
Configurable
+
+
diff --git a/src/dev-app/cdk-experimental-tree/cdk-tree-demo.ts b/src/dev-app/cdk-experimental-tree/cdk-tree-demo.ts
index c9b973635b0a..635c2c401a3d 100644
--- a/src/dev-app/cdk-experimental-tree/cdk-tree-demo.ts
+++ b/src/dev-app/cdk-experimental-tree/cdk-tree-demo.ts
@@ -6,12 +6,36 @@
* found in the LICENSE file at https://angular.dev/license
*/
-import {ChangeDetectionStrategy, Component} from '@angular/core';
-import {CdkTreeExample} from '@angular/components-examples/cdk-experimental/tree';
+import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
+import {
+ CdkTreeConfigurableExample,
+ CdkTreeActiveDescendantExample,
+ CdkTreeDisabledExample,
+ CdkTreeDisabledFocusableExample,
+ CdkTreeDisabledSkippedExample,
+ CdkTreeMultiSelectExample,
+ CdkTreeMultiSelectFollowFocusExample,
+ CdkTreeNavExample,
+ CdkTreeSingleSelectExample,
+ CdkTreeSingleSelectFollowFocusExample,
+} from '@angular/components-examples/cdk-experimental/tree';
@Component({
templateUrl: 'cdk-tree-demo.html',
- imports: [CdkTreeExample],
+ imports: [
+ CdkTreeConfigurableExample,
+ CdkTreeActiveDescendantExample,
+ CdkTreeDisabledExample,
+ CdkTreeDisabledFocusableExample,
+ CdkTreeDisabledSkippedExample,
+ CdkTreeMultiSelectExample,
+ CdkTreeMultiSelectFollowFocusExample,
+ CdkTreeNavExample,
+ CdkTreeSingleSelectExample,
+ CdkTreeSingleSelectFollowFocusExample,
+ ],
+ styleUrl: 'cdk-tree-demo.css',
+ encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CdkExperimentalTreeDemo {}
diff --git a/src/dev-app/common-classes.css b/src/dev-app/common-classes.css
index 07a7e4122edc..5036839dc7c1 100644
--- a/src/dev-app/common-classes.css
+++ b/src/dev-app/common-classes.css
@@ -9,10 +9,11 @@
[aria-disabled='true'] .example-stateful,
.example-stateful[aria-disabled='true'] {
- color: color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent);
+ color: color-mix(in srgb, var(--mat-sys-on-surface) 70%, transparent);
}
-.example-stateful:focus {
+.example-stateful:focus,
+[aria-activedescendant]:focus-within .example-stateful.cdk-active {
background: color-mix(
in srgb,
var(--mat-sys-on-surface) calc(var(--mat-sys-focus-state-layer-opacity) * 100%),
@@ -35,20 +36,28 @@
background: var(--mat-sys-surface);
}
+.example-selectable {
+ color: var(--mat-sys-on-surface);
+ outline: transparent solid 2px;
+}
+
.example-selectable[aria-selected='true'],
.example-selectable[aria-checked='true'],
-.example-selectable[aria-current] {
+.example-selectable[aria-current],
+[aria-activedescendant]:focus-within .example-selectable[aria-selected='true'],
+[aria-activedescendant]:focus-within .example-selectable[aria-checked='true'],
+[aria-activedescendant]:focus-within .example-selectable[aria-current] {
background: color-mix(
in srgb,
var(--mat-sys-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%),
transparent
);
+ color: var(--mat-sys-primary);
}
-.example-selectable[aria-selected='true']:focus-within,
-.example-selectable[aria-checked='true']:focus-within,
-.example-selectable[aria-current]:focus-within {
+.example-selectable:focus-within {
outline: var(--mat-sys-primary) solid 2px;
+ border-radius: var(--mat-sys-corner-extra-small);
}
[aria-disabled='true'] .example-selectable[aria-selected='true'],
@@ -57,5 +66,14 @@
.example-selectable[aria-disabled='true'][aria-checked='true'],
[aria-disabled='true'] .example-selectable[aria-current],
.example-selectable[aria-disabled='true'][aria-current] {
+ color: color-mix(in srgb, var(--mat-sys-on-surface) 70%, transparent);
background: color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent);
}
+
+.example-selectable[aria-selected='true'] .example-icon {
+ color: var(--mat-sys-primary);
+}
+
+[aria-disabled='true'] .example-selectable[aria-selected='true'] .example-icon {
+ color: color-mix(in srgb, var(--mat-sys-on-surface) 70%, transparent);
+}