diff --git a/src/aria/combobox/combobox.spec.ts b/src/aria/combobox/combobox.spec.ts
index 5dacc5cdfb16..bd5a1cba4457 100644
--- a/src/aria/combobox/combobox.spec.ts
+++ b/src/aria/combobox/combobox.spec.ts
@@ -52,7 +52,9 @@ describe('Combobox', () => {
const enter = (modifierKeys?: {}) => keydown('Enter', modifierKeys);
const escape = (modifierKeys?: {}) => keydown('Escape', modifierKeys);
- function setupCombobox(opts: {filterMode?: 'manual' | 'auto-select' | 'highlight'} = {}) {
+ function setupCombobox(
+ opts: {readonly?: boolean; filterMode?: 'manual' | 'auto-select' | 'highlight'} = {},
+ ) {
TestBed.configureTestingModule({});
fixture = TestBed.createComponent(ComboboxListboxExample);
const testComponent = fixture.componentInstance;
@@ -60,6 +62,9 @@ describe('Combobox', () => {
if (opts.filterMode) {
testComponent.filterMode.set(opts.filterMode);
}
+ if (opts.readonly) {
+ testComponent.readonly.set(true);
+ }
fixture.detectChanges();
defineTestVariables();
@@ -526,6 +531,35 @@ describe('Combobox', () => {
});
});
+ describe('Readonly', () => {
+ beforeEach(() => setupCombobox({readonly: true}));
+
+ it('should close on selection', () => {
+ focus();
+ down();
+ click(getOption('Alabama')!);
+ expect(inputElement.value).toBe('Alabama');
+ expect(inputElement.getAttribute('aria-expanded')).toBe('false');
+ });
+
+ it('should close on escape', () => {
+ focus();
+ down();
+ expect(inputElement.getAttribute('aria-expanded')).toBe('true');
+ escape();
+ expect(inputElement.getAttribute('aria-expanded')).toBe('false');
+ });
+
+ it('should clear selection on escape when closed', () => {
+ focus();
+ down();
+ enter();
+ expect(inputElement.value).toBe('Alabama');
+ escape();
+ expect(inputElement.value).toBe('');
+ });
+ });
+
// describe('with programmatic value changes', () => {
// // TODO(wagnermaciel): Figure out if there's a way to automatically update the
// // input value when the popup value signal is updated programmatically.
@@ -590,7 +624,9 @@ describe('Combobox', () => {
const enter = (modifierKeys?: {}) => keydown('Enter', modifierKeys);
const escape = (modifierKeys?: {}) => keydown('Escape', modifierKeys);
- function setupCombobox(opts: {filterMode?: 'manual' | 'auto-select' | 'highlight'} = {}) {
+ function setupCombobox(
+ opts: {readonly?: boolean; filterMode?: 'manual' | 'auto-select' | 'highlight'} = {},
+ ) {
TestBed.configureTestingModule({});
fixture = TestBed.createComponent(ComboboxTreeExample);
const testComponent = fixture.componentInstance;
@@ -598,6 +634,9 @@ describe('Combobox', () => {
if (opts.filterMode) {
testComponent.filterMode.set(opts.filterMode);
}
+ if (opts.readonly) {
+ testComponent.readonly.set(true);
+ }
fixture.detectChanges();
defineTestVariables();
@@ -1053,6 +1092,40 @@ describe('Combobox', () => {
expect(getTreeItem('August')!.getAttribute('aria-selected')).toBe('true');
});
});
+
+ describe('Readonly', () => {
+ beforeEach(() => setupCombobox({readonly: true}));
+
+ it('should close on selection', () => {
+ focus();
+ down();
+ right();
+ right();
+ enter();
+ expect(inputElement.value).toBe('December');
+ expect(inputElement.getAttribute('aria-expanded')).toBe('false');
+ });
+
+ it('should close on escape', () => {
+ focus();
+ down();
+ expect(inputElement.getAttribute('aria-expanded')).toBe('true');
+ escape();
+ expect(inputElement.getAttribute('aria-expanded')).toBe('false');
+ });
+
+ it('should clear selection on escape when closed', () => {
+ focus();
+ down();
+ right();
+ right();
+ enter();
+ expect(inputElement.value).toBe('December');
+ expect(inputElement.getAttribute('aria-expanded')).toBe('false');
+ escape();
+ expect(inputElement.value).toBe('');
+ });
+ });
});
});
@@ -1061,6 +1134,7 @@ describe('Combobox', () => {
{
imports: [Combobox, ComboboxInput, ComboboxPopup, ComboboxPopupContainer, Listbox, Option],
})
class ComboboxListboxExample {
+ readonly = signal(false);
+ searchString = signal('');
value = signal
([]);
-
filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual');
- searchString = signal('');
-
options = computed(() =>
states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())),
);
@@ -1103,6 +1176,7 @@ class ComboboxListboxExample {
@@ -1157,13 +1231,11 @@ class ComboboxListboxExample {
],
})
class ComboboxTreeExample {
- value = signal
([]);
-
- filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual');
-
+ readonly = signal(false);
searchString = signal('');
-
+ value = signal([]);
nodes = computed(() => this.filterTreeNodes(TREE_NODES));
+ filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual');
firstMatch = computed(() => {
const flatNodes = this.flattenTreeNodes(this.nodes());
diff --git a/src/aria/combobox/combobox.ts b/src/aria/combobox/combobox.ts
index 8490508f7476..20d0e170ae23 100644
--- a/src/aria/combobox/combobox.ts
+++ b/src/aria/combobox/combobox.ts
@@ -119,6 +119,7 @@ export class Combobox {
'[attr.aria-controls]': 'combobox.pattern.popupId()',
'[attr.aria-haspopup]': 'combobox.pattern.hasPopup()',
'[attr.aria-autocomplete]': 'combobox.pattern.autocomplete()',
+ '[attr.readonly]': 'combobox.pattern.readonly()',
},
})
export class ComboboxInput {
diff --git a/src/aria/ui-patterns/combobox/combobox.spec.ts b/src/aria/ui-patterns/combobox/combobox.spec.ts
index 9d18703e841e..18fe774b8b9b 100644
--- a/src/aria/ui-patterns/combobox/combobox.spec.ts
+++ b/src/aria/ui-patterns/combobox/combobox.spec.ts
@@ -585,6 +585,35 @@ describe('Combobox with Listbox Pattern', () => {
});
});
});
+
+ describe('Readonly mode', () => {
+ it('should select and close on selection', () => {
+ const {combobox, listbox, inputEl} = getPatterns({readonly: true});
+ combobox.onPointerup(clickOption(listbox.inputs.items(), 2));
+ expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[2]);
+ expect(listbox.inputs.value()).toEqual(['Banana']);
+ expect(inputEl.value).toBe('Banana');
+ expect(combobox.expanded()).toBe(false);
+ });
+
+ it('should close on escape', () => {
+ const {combobox} = getPatterns({readonly: true});
+ combobox.onKeydown(down());
+ expect(combobox.expanded()).toBe(true);
+ combobox.onKeydown(escape());
+ expect(combobox.expanded()).toBe(false);
+ });
+
+ it('should clear selection on escape when already closed', () => {
+ const {combobox, listbox} = getPatterns({readonly: true});
+ combobox.onPointerup(clickOption(listbox.inputs.items(), 2));
+ expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[2]);
+ expect(listbox.inputs.value()).toEqual(['Banana']);
+ combobox.onKeydown(escape());
+ expect(listbox.getSelectedItem()).toBe(undefined);
+ expect(listbox.inputs.value()).toEqual([]);
+ });
+ });
});
describe('Combobox with Tree Pattern', () => {
@@ -894,4 +923,36 @@ describe('Combobox with Tree Pattern', () => {
});
});
});
+
+ describe('Readonly mode', () => {
+ it('should select and close on selection', () => {
+ const {combobox, tree, inputEl} = getPatterns({readonly: true});
+ combobox.onPointerup(clickInput(inputEl));
+ expect(combobox.expanded()).toBe(true);
+ combobox.onPointerup(clickTreeItem(tree.inputs.allItems(), 0));
+ expect(tree.inputs.value()).toEqual(['Fruit']);
+ expect(inputEl.value).toBe('Fruit');
+ expect(combobox.expanded()).toBe(false);
+ });
+
+ it('should close on escape', () => {
+ const {combobox} = getPatterns({readonly: true});
+ combobox.onKeydown(down());
+ expect(combobox.expanded()).toBe(true);
+ combobox.onKeydown(escape());
+ expect(combobox.expanded()).toBe(false);
+ });
+
+ it('should clear selection on escape when already closed', () => {
+ const {combobox, tree, inputEl} = getPatterns({readonly: true});
+ combobox.onPointerup(clickInput(inputEl));
+ combobox.onPointerup(clickTreeItem(tree.inputs.allItems(), 0));
+ expect(tree.inputs.value()).toEqual(['Fruit']);
+ expect(inputEl.value).toBe('Fruit');
+ expect(combobox.expanded()).toBe(false);
+ combobox.onKeydown(escape());
+ expect(tree.inputs.value()).toEqual([]);
+ expect(inputEl.value).toBe('');
+ });
+ });
});
diff --git a/src/aria/ui-patterns/combobox/combobox.ts b/src/aria/ui-patterns/combobox/combobox.ts
index 32887abd1e94..2af80ffcfdab 100644
--- a/src/aria/ui-patterns/combobox/combobox.ts
+++ b/src/aria/ui-patterns/combobox/combobox.ts
@@ -144,16 +144,24 @@ export class ComboboxPattern, V> {
/** The ARIA role of the popup associated with the combobox. */
hasPopup = computed(() => this.inputs.popupControls()?.role() || null);
- /** Whether the combobox is interactive. */
- isInteractive = computed(() => !this.inputs.disabled() && !this.inputs.readonly());
+ /** Whether the combobox is read-only. */
+ readonly = computed(() => this.inputs.readonly() || null);
/** The keydown event manager for the combobox. */
keydown = computed(() => {
if (!this.expanded()) {
- return new KeyboardEventManager()
+ const manager = new KeyboardEventManager()
.on('ArrowDown', () => this.open({first: true}))
.on('ArrowUp', () => this.open({last: true}))
.on('Escape', () => this.close({reset: true}));
+
+ if (this.readonly()) {
+ manager
+ .on('Enter', () => this.open({selected: true}))
+ .on(' ', () => this.open({selected: true}));
+ }
+
+ return manager;
}
const popupControls = this.inputs.popupControls();
@@ -170,6 +178,10 @@ export class ComboboxPattern, V> {
.on('Escape', () => this.close({reset: true}))
.on('Enter', () => this.select({commit: true, close: true}));
+ if (this.readonly()) {
+ manager.on(' ', () => this.select({commit: true, close: true}));
+ }
+
if (popupControls.role() === 'tree') {
const treeControls = popupControls as ComboboxTreeControls;
@@ -196,7 +208,11 @@ export class ComboboxPattern, V> {
}
if (e.target === this.inputs.inputEl()) {
- this.open();
+ if (this.readonly()) {
+ this.expanded() ? this.close() : this.open({selected: true});
+ } else {
+ this.open();
+ }
}
}),
);
@@ -205,21 +221,21 @@ export class ComboboxPattern, V> {
/** Handles keydown events for the combobox. */
onKeydown(event: KeyboardEvent) {
- if (this.isInteractive()) {
+ if (!this.inputs.disabled()) {
this.keydown().handle(event);
}
}
/** Handles pointerup events for the combobox. */
onPointerup(event: PointerEvent) {
- if (this.isInteractive()) {
+ if (!this.inputs.disabled()) {
this.pointerup().handle(event);
}
}
/** Handles input events for the combobox. */
onInput(event: Event) {
- if (!this.isInteractive()) {
+ if (this.inputs.disabled() || this.inputs.readonly()) {
return;
}
@@ -253,7 +269,7 @@ export class ComboboxPattern, V> {
/** Handles focus out events for the combobox. */
onFocusOut(event: FocusEvent) {
- if (this.inputs.disabled() || this.inputs.readonly()) {
+ if (this.inputs.disabled()) {
return;
}
@@ -385,18 +401,23 @@ export class ComboboxPattern, V> {
popupControls?.clearSelection();
}
}
+
+ this.close();
+
+ if (!this.readonly()) {
+ this.inputs.popupControls()?.clearSelection();
+ }
}
/** Opens the combobox. */
- open(nav?: {first?: boolean; last?: boolean}) {
+ open(nav?: {first?: boolean; last?: boolean; selected?: boolean}) {
this.expanded.set(true);
const inputEl = this.inputs.inputEl();
- if (inputEl) {
+ if (inputEl && this.inputs.filterMode() === 'highlight') {
const isHighlighting = inputEl.selectionStart !== inputEl.value.length;
this.inputs.inputValue?.set(inputEl.value.slice(0, inputEl.selectionStart || 0));
-
if (!isHighlighting) {
this.highlightedItem.set(undefined);
}
@@ -408,6 +429,10 @@ export class ComboboxPattern, V> {
if (nav?.last) {
this.last();
}
+ if (nav?.selected) {
+ const selectedItem = this.inputs.popupControls()?.getSelectedItem();
+ selectedItem ? this.inputs.popupControls()?.focus(selectedItem) : this.first();
+ }
}
/** Navigates to the next focusable item in the combobox popup. */
diff --git a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts
index a2cb39aef496..c6064ac2edcd 100644
--- a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts
+++ b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts
@@ -62,7 +62,7 @@ export class ComboboxAutoSelectExample {
if (comboboxRect) {
popoverEl.style.width = `${comboboxRect.width}px`;
- popoverEl.style.top = `${comboboxRect.bottom}px`;
+ popoverEl.style.top = `${comboboxRect.bottom + 4}px`;
popoverEl.style.left = `${comboboxRect.left - 1}px`;
}
diff --git a/src/components-examples/aria/combobox/combobox-examples.css b/src/components-examples/aria/combobox/combobox-examples.css
index 54e96b72490d..a6ff5a0e6e80 100644
--- a/src/components-examples/aria/combobox/combobox-examples.css
+++ b/src/components-examples/aria/combobox/combobox-examples.css
@@ -2,22 +2,30 @@
position: relative;
width: 300px;
display: flex;
- overflow: hidden;
flex-direction: column;
border: 1px solid var(--mat-sys-outline);
border-radius: var(--mat-sys-corner-extra-small);
}
-.example-combobox-container:has(.example-combobox-input[aria-expanded='true']) {
- border-bottom-right-radius: 0;
- border-bottom-left-radius: 0;
-}
-
.example-combobox-input-container {
display: flex;
- overflow: hidden;
position: relative;
align-items: center;
+ border-radius: var(--mat-sys-corner-extra-small);
+}
+
+.example-combobox-input {
+ border-radius: var(--mat-sys-corner-extra-small);
+}
+
+.example-combobox-input[readonly='true'] {
+ cursor: pointer;
+ padding: 0.7rem 1rem;
+}
+
+.example-combobox-container:focus-within .example-combobox-input {
+ outline: 1.5px solid var(--mat-sys-primary);
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--mat-sys-primary) 25%, transparent);
}
.example-icon {
@@ -35,6 +43,18 @@
opacity: 0.8;
}
+.example-arrow-icon {
+ padding: 0 0.5rem;
+ position: absolute;
+ right: 0;
+ opacity: 0.8;
+ transition: transform 0.2s ease;
+}
+
+.example-combobox-input[aria-expanded='true'] + .example-arrow-icon {
+ transform: rotate(180deg);
+}
+
.example-combobox-input {
width: 100%;
border: none;
@@ -48,8 +68,7 @@
margin: 0;
padding: 0;
border: 1px solid var(--mat-sys-outline);
- border-bottom-right-radius: var(--mat-sys-corner-extra-small);
- border-bottom-left-radius: var(--mat-sys-corner-extra-small);
+ border-radius: var(--mat-sys-corner-extra-small);
background-color: var(--mat-sys-surface);
}
diff --git a/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts b/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts
index 074310e88c1f..c5cf68082616 100644
--- a/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts
+++ b/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts
@@ -71,7 +71,7 @@ export class ComboboxHighlightExample {
if (comboboxRect) {
popoverEl.style.width = `${comboboxRect.width}px`;
- popoverEl.style.top = `${comboboxRect.bottom}px`;
+ popoverEl.style.top = `${comboboxRect.bottom + 4}px`;
popoverEl.style.left = `${comboboxRect.left - 1}px`;
}
diff --git a/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts b/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts
index 1493d28eb0b2..b259f5b317f0 100644
--- a/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts
+++ b/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts
@@ -71,7 +71,7 @@ export class ComboboxManualExample {
if (comboboxRect) {
popoverEl.style.width = `${comboboxRect.width}px`;
- popoverEl.style.top = `${comboboxRect.bottom}px`;
+ popoverEl.style.top = `${comboboxRect.bottom + 4}px`;
popoverEl.style.left = `${comboboxRect.left - 1}px`;
}
diff --git a/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html
new file mode 100644
index 000000000000..d32ee7623a8c
--- /dev/null
+++ b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html
@@ -0,0 +1,33 @@
+
+
+
+ arrow_drop_down
+
+
+
+
+
+ @for (option of options(); track option) {
+
+ {{option}}
+ check
+
+ }
+
+
+
+
diff --git a/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts
new file mode 100644
index 000000000000..80570ab5ea42
--- /dev/null
+++ b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts
@@ -0,0 +1,78 @@
+/**
+ * @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 {
+ Combobox,
+ ComboboxInput,
+ ComboboxPopup,
+ ComboboxPopupContainer,
+} from '@angular/aria/combobox';
+import {Listbox, Option} from '@angular/aria/listbox';
+import {
+ afterRenderEffect,
+ ChangeDetectionStrategy,
+ Component,
+ ElementRef,
+ signal,
+ viewChild,
+} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+
+/** @title Readonly combobox. */
+@Component({
+ selector: 'combobox-readonly-example',
+ templateUrl: 'combobox-readonly-example.html',
+ styleUrl: '../combobox-examples.css',
+ imports: [
+ Combobox,
+ ComboboxInput,
+ ComboboxPopup,
+ ComboboxPopupContainer,
+ Listbox,
+ Option,
+ FormsModule,
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ComboboxReadonlyExample {
+ popover = viewChild('popover');
+ listbox = viewChild>(Listbox);
+ combobox = viewChild>(Combobox);
+
+ options = () => states;
+ searchString = signal('');
+
+ constructor() {
+ afterRenderEffect(() => {
+ const popover = this.popover()!;
+ const combobox = this.combobox()!;
+ combobox.pattern.expanded() ? this.showPopover() : popover.nativeElement.hidePopover();
+
+ // TODO(wagnermaciel): Make this easier for developers to do.
+ this.listbox()?.pattern.inputs.activeItem()?.element().scrollIntoView({block: 'nearest'});
+ });
+ }
+
+ showPopover() {
+ const popover = this.popover()!;
+ const combobox = this.combobox()!;
+
+ const comboboxRect = combobox.pattern.inputs.inputEl()?.getBoundingClientRect();
+ const popoverEl = popover.nativeElement;
+
+ if (comboboxRect) {
+ popoverEl.style.width = `${comboboxRect.width}px`;
+ popoverEl.style.top = `${comboboxRect.bottom + 4}px`;
+ popoverEl.style.left = `${comboboxRect.left - 1}px`;
+ }
+
+ popover.nativeElement.showPopover();
+ }
+}
+
+const states = ['Option 1', 'Option 2', 'Option 3'];
diff --git a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts
index 2ccc5c197a82..ed3900a48bde 100644
--- a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts
+++ b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts
@@ -97,7 +97,7 @@ export class ComboboxTreeAutoSelectExample {
if (comboboxRect) {
popoverEl.style.width = `${comboboxRect.width}px`;
- popoverEl.style.top = `${comboboxRect.bottom}px`;
+ popoverEl.style.top = `${comboboxRect.bottom + 4}px`;
popoverEl.style.left = `${comboboxRect.left - 1}px`;
}
diff --git a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts
index 013aaec24480..2432062b06cd 100644
--- a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts
+++ b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts
@@ -97,7 +97,7 @@ export class ComboboxTreeHighlightExample {
if (comboboxRect) {
popoverEl.style.width = `${comboboxRect.width}px`;
- popoverEl.style.top = `${comboboxRect.bottom}px`;
+ popoverEl.style.top = `${comboboxRect.bottom + 4}px`;
popoverEl.style.left = `${comboboxRect.left - 1}px`;
}
diff --git a/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts
index 3e2d77bda2ae..8429cc898e27 100644
--- a/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts
+++ b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts
@@ -97,7 +97,7 @@ export class ComboboxTreeManualExample {
if (comboboxRect) {
popoverEl.style.width = `${comboboxRect.width}px`;
- popoverEl.style.top = `${comboboxRect.bottom}px`;
+ popoverEl.style.top = `${comboboxRect.bottom + 4}px`;
popoverEl.style.left = `${comboboxRect.left - 1}px`;
}
diff --git a/src/components-examples/aria/combobox/index.ts b/src/components-examples/aria/combobox/index.ts
index ca1e1f5c5dde..a5a42a9599e3 100644
--- a/src/components-examples/aria/combobox/index.ts
+++ b/src/components-examples/aria/combobox/index.ts
@@ -4,3 +4,4 @@ export {ComboboxHighlightExample} from './combobox-highlight/combobox-highlight-
export {ComboboxTreeManualExample} from './combobox-tree-manual/combobox-tree-manual-example';
export {ComboboxTreeAutoSelectExample} from './combobox-tree-auto-select/combobox-tree-auto-select-example';
export {ComboboxTreeHighlightExample} from './combobox-tree-highlight/combobox-tree-highlight-example';
+export {ComboboxReadonlyExample} from './combobox-readonly/combobox-readonly-example';
diff --git a/src/dev-app/aria-combobox/combobox-demo.html b/src/dev-app/aria-combobox/combobox-demo.html
index 2a5bd48182a0..1de5491b6488 100644
--- a/src/dev-app/aria-combobox/combobox-demo.html
+++ b/src/dev-app/aria-combobox/combobox-demo.html
@@ -29,5 +29,10 @@ Combobox with tree popup and auto-select filtering
Combobox with tree popup and highlight filtering
+
+
+
Readonly Combobox
+
+
diff --git a/src/dev-app/aria-combobox/combobox-demo.ts b/src/dev-app/aria-combobox/combobox-demo.ts
index a013d1dc1f5f..5dc4c74ce522 100644
--- a/src/dev-app/aria-combobox/combobox-demo.ts
+++ b/src/dev-app/aria-combobox/combobox-demo.ts
@@ -13,6 +13,7 @@ import {
ComboboxTreeAutoSelectExample,
ComboboxTreeHighlightExample,
ComboboxTreeManualExample,
+ ComboboxReadonlyExample,
} from '@angular/components-examples/aria/combobox';
import {ChangeDetectionStrategy, Component} from '@angular/core';
@@ -26,6 +27,7 @@ import {ChangeDetectionStrategy, Component} from '@angular/core';
ComboboxTreeManualExample,
ComboboxTreeAutoSelectExample,
ComboboxTreeHighlightExample,
+ ComboboxReadonlyExample,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})