diff --git a/radio/internal/single-selection-controller.ts b/radio/internal/single-selection-controller.ts
index e1bdc667d0..c84922be92 100644
--- a/radio/internal/single-selection-controller.ts
+++ b/radio/internal/single-selection-controller.ts
@@ -236,8 +236,11 @@ export class SingleSelectionController implements ReactiveController {
nextSibling.checked = true;
nextSibling.tabIndex = 0;
nextSibling.focus();
- // Fire a change event since the change is triggered by a user action.
- // This matches native behavior.
+ // Fire an input event first, then a change event since both are triggered
+ // by a user action. This matches native behavior.
+ nextSibling.dispatchEvent(
+ new InputEvent('input', {bubbles: true, composed: true}),
+ );
nextSibling.dispatchEvent(new Event('change', {bubbles: true}));
break;
diff --git a/radio/radio_test.ts b/radio/radio_test.ts
index eed1b08bff..eb56c6d61a 100644
--- a/radio/radio_test.ts
+++ b/radio/radio_test.ts
@@ -183,6 +183,21 @@ describe('', () => {
expect(changeHandler).toHaveBeenCalledTimes(1);
});
+ it('dispatched an input event on user navigation', async () => {
+ const {harnesses, root} = await setupTest(radioGroupPreSelected);
+ const inputHandler = jasmine.createSpy('inputHandler');
+ root.addEventListener('input', inputHandler);
+ const [, a2] = harnesses;
+ expect(a2.element.checked)
+ .withContext('default checked radio')
+ .toBeTrue();
+
+ await simulateKeyDown(a2.element, 'ArrowRight');
+
+ expect(inputHandler).toHaveBeenCalledTimes(1);
+ expect(inputHandler).toHaveBeenCalledWith(jasmine.any(InputEvent));
+ });
+
it('Using arrow right on the last radio should select the first radio in that group', async () => {
const {harnesses} = await setupTest(radioGroupPreSelected);
const [a1, a2, a3, b1] = harnesses;