diff --git a/src/aria/accordion/accordion-trigger.ts b/src/aria/accordion/accordion-trigger.ts index 987145a70dd5..109b5f8b8c64 100644 --- a/src/aria/accordion/accordion-trigger.ts +++ b/src/aria/accordion/accordion-trigger.ts @@ -86,6 +86,11 @@ export class AccordionTrigger implements OnInit, OnDestroy { _pattern!: AccordionTriggerPattern; constructor() { + // Automatically prevent form submission. + if (this.element.tagName === 'BUTTON' && !this.element.hasAttribute('type')) { + this.element.setAttribute('type', 'button'); + } + // Check for any violations after the DOM has been updated. if (typeof ngDevMode === 'undefined' || ngDevMode) { afterRenderEffect({ diff --git a/src/aria/accordion/accordion.spec.ts b/src/aria/accordion/accordion.spec.ts index 77694188d697..6d63b17582c3 100644 --- a/src/aria/accordion/accordion.spec.ts +++ b/src/aria/accordion/accordion.spec.ts @@ -87,6 +87,12 @@ describe('AccordionGroup', () => { expect(getTriggerAttribute(2, 'role')).toBe('button'); }); + it('should set type="button" by default on buttons', () => { + expect(getTriggerAttribute(0, 'type')).toBe('button'); + expect(getTriggerAttribute(1, 'type')).toBe('button'); + expect(getTriggerAttribute(2, 'type')).toBe('button'); + }); + it('should have aria-expanded="false" when collapsed', () => { expect(getTriggerAttribute(0, 'aria-expanded')).toBe('false'); expect(getTriggerAttribute(1, 'aria-expanded')).toBe('false'); diff --git a/src/aria/menu/menu-trigger.ts b/src/aria/menu/menu-trigger.ts index 08aa16614ca8..7ed57871e656 100644 --- a/src/aria/menu/menu-trigger.ts +++ b/src/aria/menu/menu-trigger.ts @@ -90,6 +90,11 @@ export class MenuTrigger { constructor() { effect(() => this.menu()?.parent.set(this)); effect(() => this._pattern.pendingFocusEffect()); + + // Automatically prevent form submission. + if (this.element.tagName === 'BUTTON' && !this.element.hasAttribute('type')) { + this.element.setAttribute('type', 'button'); + } } /** Opens the menu focusing on the first menu item. */ diff --git a/src/aria/menu/menu.spec.ts b/src/aria/menu/menu.spec.ts index 1306cfacd8bc..9ba7d23d562c 100644 --- a/src/aria/menu/menu.spec.ts +++ b/src/aria/menu/menu.spec.ts @@ -861,6 +861,10 @@ describe('CDK Overlay Menu Pattern', () => { await keydown(trigger, 'Enter'); expect(document.activeElement).toBe(getItem('Apple')); }); + + it('should set type="button" by default on button triggers', () => { + expect(getTrigger().getAttribute('type')).toBe('button'); + }); }); describe('Menu Bar Pattern', () => { diff --git a/src/aria/tabs/tab.ts b/src/aria/tabs/tab.ts index 1c2477607d7a..8fdaaf1f83bd 100644 --- a/src/aria/tabs/tab.ts +++ b/src/aria/tabs/tab.ts @@ -92,6 +92,11 @@ export class Tab implements HasElement, OnInit, OnDestroy { } constructor() { + // Automatically prevent form submission. + if (this.element.tagName === 'BUTTON' && !this.element.hasAttribute('type')) { + this.element.setAttribute('type', 'button'); + } + if (typeof ngDevMode === 'undefined' || ngDevMode) { afterRenderEffect({ read: () => {