Merge "UI: Replace iron-autogrow-textarea with gr-autogrow-textarea"
diff --git a/.gitignore b/.gitignore
index bc489bc..e04444d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,7 @@
 /polygerrit-ui/coverage/
 /polygerrit-ui/app/plugins/*
 /polygerrit-ui/screenshots/Chrome/failed/
+/polygerrit-ui/screenshots/Chromium/failed/
 !/plugins/.eslintignore
 !/plugins/.eslintrc.js
 !/plugins/.prettierrc.js
diff --git a/polygerrit-ui/app/api/embed.ts b/polygerrit-ui/app/api/embed.ts
index d6425be..37e68e3 100644
--- a/polygerrit-ui/app/api/embed.ts
+++ b/polygerrit-ui/app/api/embed.ts
@@ -68,3 +68,11 @@
   hint?: string;
   setRangeText: (replacement: string, start: number, end: number) => void;
 }
+
+/** <gr-autogrow-textarea> interface that external users can rely on */
+export declare interface GrAutogrowTextarea extends HTMLElement {
+  value?: string;
+  nativeElement?: HTMLElement;
+  placeholder?: string;
+  setRangeText: (replacement: string, start: number, end: number) => void;
+}
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
index 210b17c..99908c2 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import '@polymer/iron-input/iron-input';
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 import '../../../styles/gr-form-styles';
 import '../../../styles/shared-styles';
 import '../../shared/gr-autocomplete/gr-autocomplete';
@@ -32,6 +32,7 @@
 import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
 import {formStyles} from '../../../styles/form-styles';
 import {branchName} from '../../../utils/patch-set-util';
+import {GrAutogrowTextarea} from '../../../api/embed';
 
 const SUGGESTIONS_LIMIT = 15;
 
@@ -101,7 +102,7 @@
       css`
         input:not([type='checkbox']),
         gr-autocomplete,
-        iron-autogrow-textarea {
+        gr-autogrow-textarea {
           width: 100%;
         }
         .value {
@@ -177,19 +178,20 @@
         <section id="description">
           <span class="title">Description</span>
           <span class="value">
-            <iron-autogrow-textarea
+            <gr-autogrow-textarea
               id="messageInput"
               class="message"
               autocomplete="on"
-              rows="4"
-              maxRows="15"
-              .bindValue=${this.subject}
+              .rows=${'4'}
+              .maxRows=${'15'}
+              .value=${this.subject}
               placeholder="Insert the description of the change."
-              @bind-value-changed=${(e: BindValueChangeEvent) => {
-                this.subject = e.detail.value ?? '';
+              @input=${(e: InputEvent) => {
+                const value = (e.target as GrAutogrowTextarea).value ?? '';
+                this.subject = value;
               }}
             >
-            </iron-autogrow-textarea>
+            </gr-autogrow-textarea>
           </span>
         </section>
         <section class=${this.privateChangesEnabled ? 'hide' : ''}>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.ts b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.ts
index 2bcde2c..1ef6426 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.ts
@@ -79,16 +79,13 @@
           <section id="description">
             <span class="title"> Description </span>
             <span class="value">
-              <iron-autogrow-textarea
-                aria-disabled="false"
+              <gr-autogrow-textarea
                 autocomplete="on"
                 class="message"
                 id="messageInput"
-                maxrows="15"
                 placeholder="Insert the description of the change."
-                rows="4"
               >
-              </iron-autogrow-textarea>
+              </gr-autogrow-textarea>
             </span>
           </section>
           <section>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts
index 3095d54..bc44e5a 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.ts
@@ -152,7 +152,7 @@
 
   function getFormFields() {
     const selects = Array.from(queryAll(element, 'select'));
-    const textareas = Array.from(queryAll(element, 'iron-autogrow-textarea'));
+    const textareas = Array.from(queryAll(element, 'gr-autogrow-textarea'));
     const inputs = Array.from(queryAll(element, 'input'));
     return inputs.concat(textareas).concat(selects);
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
index 483d2c1c..acad82f 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.ts
@@ -3,7 +3,7 @@
  * Copyright 2017 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 import '../../shared/gr-button/gr-button';
 import '../../shared/gr-select/gr-select';
 import {encodeURL, getBaseUrl} from '../../../utils/url-util';
@@ -18,6 +18,7 @@
 import {EditablePermissionRuleInfo} from '../gr-repo-access/gr-repo-access-interfaces';
 import {PermissionAction} from '../../../constants/constants';
 import {formStyles} from '../../../styles/form-styles';
+import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 
 const PRIORITY_OPTIONS = [PermissionAction.BATCH, PermissionAction.INTERACTIVE];
 
@@ -174,7 +175,7 @@
         .groupPath {
           color: var(--deemphasized-text-color);
         }
-        iron-autogrow-textarea {
+        gr-autogrow-textarea {
           width: 14em;
         }
       `,
@@ -288,28 +289,46 @@
     if (!this.hasRange) return;
 
     return html`
-      <iron-autogrow-textarea
+      <gr-autogrow-textarea
         id="minInput"
         class="min"
         autocomplete="on"
         placeholder="Min value"
-        .bindValue=${this.rule?.value?.min}
+        .value=${this.rule?.value?.min}
         ?disabled=${!this.editing}
-        @bind-value-changed=${(e: BindValueChangeEvent) => {
-          this.handleMinBindValueChanged(e);
+        @input=${(e: InputEvent) => {
+          const value = (e.target as GrAutogrowTextarea).value ?? '';
+          if (
+            !this.rule?.value ||
+            value === undefined ||
+            this.rule.value.min === Number(value)
+          )
+            return;
+          this.rule.value.min = Number(value);
+
+          this.handleValueChange();
         }}
-      ></iron-autogrow-textarea>
-      <iron-autogrow-textarea
+      ></gr-autogrow-textarea>
+      <gr-autogrow-textarea
         id="maxInput"
         class="max"
         autocomplete="on"
         placeholder="Max value"
-        .bindValue=${this.rule?.value?.max}
+        .value=${this.rule?.value?.max}
         ?disabled=${!this.editing}
-        @bind-value-changed=${(e: BindValueChangeEvent) => {
-          this.handleMaxBindValueChanged(e);
+        @input=${(e: InputEvent) => {
+          const value = (e.target as GrAutogrowTextarea).value ?? '';
+          if (
+            !this.rule?.value ||
+            value === undefined ||
+            this.rule.value.max === Number(value)
+          )
+            return;
+          this.rule.value.max = Number(value);
+
+          this.handleValueChange();
         }}
-      ></iron-autogrow-textarea>
+      ></gr-autogrow-textarea>
     `;
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
index 36641d6..4c36c5f 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog.ts
@@ -3,19 +3,18 @@
  * Copyright 2016 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 import '../../shared/gr-dialog/gr-dialog';
-import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import {Key, Modifier} from '../../../utils/dom-util';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {css, html, LitElement} from 'lit';
 import {customElement, property, query} from 'lit/decorators.js';
 import {assertIsDefined} from '../../../utils/common-util';
-import {BindValueChangeEvent} from '../../../types/events';
 import {ShortcutController} from '../../lit/shortcut-controller';
 import {ChangeActionDialog} from '../../../types/common';
 import {fireNoBubble} from '../../../utils/event-util';
 import {formStyles} from '../../../styles/form-styles';
+import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -40,7 +39,7 @@
    * @event cancel
    */
 
-  @query('#messageInput') private messageInput?: IronAutogrowTextareaElement;
+  @query('#messageInput') private messageInput?: GrAutogrowTextarea;
 
   @property({type: String})
   message = '';
@@ -82,7 +81,7 @@
           display: block;
           width: 100%;
         }
-        iron-autogrow-textarea {
+        gr-autogrow-textarea {
           font-family: var(--monospace-font-family);
           font-size: var(--font-size-mono);
           line-height: var(--line-height-mono);
@@ -106,16 +105,14 @@
         <div class="header" slot="header">Abandon Change</div>
         <div class="main" slot="main">
           <label for="messageInput">Abandon Message</label>
-          <iron-autogrow-textarea
+          <gr-autogrow-textarea
             id="messageInput"
             class="message"
             autocomplete="on"
             placeholder="&lt;Insert reasoning here&gt;"
-            .bindValue=${this.message}
-            @bind-value-changed=${(e: BindValueChangeEvent) => {
-              this.handleBindValueChanged(e);
-            }}
-          ></iron-autogrow-textarea>
+            .value=${this.message}
+            @input=${this.handleInputChanged}
+          ></gr-autogrow-textarea>
         </div>
       </gr-dialog>
     `;
@@ -123,7 +120,7 @@
 
   resetFocus() {
     assertIsDefined(this.messageInput, 'messageInput');
-    this.messageInput.textarea.focus();
+    this.messageInput.focus();
   }
 
   // private but used in test
@@ -145,7 +142,8 @@
     fireNoBubble(this, 'cancel', {});
   }
 
-  private handleBindValueChanged(e: BindValueChangeEvent) {
-    this.message = e.detail.value ?? '';
+  private handleInputChanged(e: InputEvent) {
+    const value = (e.target as GrAutogrowTextarea).value ?? '';
+    this.message = value;
   }
 }
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.ts
index 3842f7a..ee2d541 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.ts
@@ -28,14 +28,13 @@
           <div class="header" slot="header">Abandon Change</div>
           <div class="main" slot="main">
             <label for="messageInput"> Abandon Message </label>
-            <iron-autogrow-textarea
-              aria-disabled="false"
+            <gr-autogrow-textarea
               autocomplete="on"
               class="message"
               id="messageInput"
               placeholder="<Insert reasoning here>"
             >
-            </iron-autogrow-textarea>
+            </gr-autogrow-textarea>
           </div>
         </gr-dialog>
       `
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index b15a4e4..d74ac52 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -3,7 +3,7 @@
  * Copyright 2016 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 import '@polymer/iron-input/iron-input';
 import '../../../styles/shared-styles';
 import '../../shared/gr-autocomplete/gr-autocomplete';
@@ -48,6 +48,7 @@
 import {formStyles} from '../../../styles/form-styles';
 import {branchName} from '../../../utils/patch-set-util';
 import {changeModelToken} from '../../../models/change/change-model';
+import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 
 const SUGGESTIONS_LIMIT = 15;
 const CHANGE_SUBJECT_LIMIT = 50;
@@ -209,7 +210,7 @@
           display: block;
           width: 100%;
         }
-        iron-autogrow-textarea {
+        gr-autogrow-textarea {
           font-family: var(--monospace-font-family);
           font-size: var(--font-size-mono);
           line-height: var(--line-height-mono);
@@ -354,16 +355,18 @@
         />
       </iron-input>
       <label for="messageInput"> Cherry Pick Commit Message </label>
-      <iron-autogrow-textarea
+      <gr-autogrow-textarea
         id="messageInput"
         class="message"
         autocomplete="on"
-        rows="4"
+        .rows=${4}
         .maxRows=${15}
-        .bindValue=${this.message}
-        @bind-value-changed=${(e: BindValueChangeEvent) =>
-          (this.message = e.detail.value ?? '')}
-      ></iron-autogrow-textarea>
+        .value=${this.message}
+        @input=${(e: InputEvent) => {
+          const value = (e.target as GrAutogrowTextarea).value ?? '';
+          this.message = value;
+        }}
+      ></gr-autogrow-textarea>
       ${when(
         this.canShowEmailDropdown(),
         () => html`<div id="cherryPickEmailDropdown">Cherry Pick Committer Email
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
index 7f20c61..5ae607a 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
@@ -136,14 +136,12 @@
                 />
               </iron-input>
               <label for="messageInput"> Cherry Pick Commit Message </label>
-              <iron-autogrow-textarea
-                aria-disabled="false"
+              <gr-autogrow-textarea
                 autocomplete="on"
                 class="message"
                 id="messageInput"
-                rows="4"
               >
-              </iron-autogrow-textarea>
+              </gr-autogrow-textarea>
               <gr-endpoint-slot name="bottom"></gr-endpoint-slot>
             </gr-endpoint-decorator>
           </div>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
index a9bb86e..206fa46 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.ts
@@ -10,7 +10,7 @@
 import {getAppContext} from '../../../services/app-context';
 import '../../shared/gr-autocomplete/gr-autocomplete';
 import '../../shared/gr-dialog/gr-dialog';
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 import {Key, Modifier} from '../../../utils/dom-util';
 import {ValueChangedEvent} from '../../../types/events';
 import {ShortcutController} from '../../lit/shortcut-controller';
@@ -18,6 +18,7 @@
 import {fireNoBubble} from '../../../utils/event-util';
 import {formStyles} from '../../../styles/form-styles';
 import {branchName} from '../../../utils/patch-set-util';
+import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 
 const SUGGESTIONS_LIMIT = 15;
 
@@ -130,14 +131,18 @@
           >
           </gr-autocomplete>
           <label for="messageInput"> Move Change Message </label>
-          <iron-autogrow-textarea
+          <gr-autogrow-textarea
             id="messageInput"
             class="message"
             autocomplete="on"
             .rows=${4}
             .maxRows=${15}
-            .bindValue=${this.message}
-          ></iron-autogrow-textarea>
+            .value=${this.message}
+            @input=${(e: InputEvent) => {
+              const value = (e.target as GrAutogrowTextarea).value ?? '';
+              this.message = value;
+            }}
+          ></gr-autogrow-textarea>
         </div>
       </gr-dialog>
     `;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.ts
index 42d8654..3fe2e0b 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.ts
@@ -49,12 +49,11 @@
             <gr-autocomplete id="branchInput" placeholder="Destination branch">
             </gr-autocomplete>
             <label for="messageInput"> Move Change Message </label>
-            <iron-autogrow-textarea
-              aria-disabled="false"
+            <gr-autogrow-textarea
               id="messageInput"
               class="message"
               autocomplete="on"
-            ></iron-autogrow-textarea>
+            ></gr-autogrow-textarea>
           </div>
         </gr-dialog>
       `
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
index d038140..2854905 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
@@ -6,7 +6,7 @@
 import '../../shared/gr-dialog/gr-dialog';
 import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
 import '../gr-validation-options/gr-validation-options';
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 import {css, html, LitElement, nothing} from 'lit';
 import {customElement, query, state} from 'lit/decorators.js';
 import {
@@ -17,7 +17,6 @@
 } from '../../../types/common';
 import {fire, fireAlert} from '../../../utils/event-util';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {BindValueChangeEvent} from '../../../types/events';
 import {resolve} from '../../../models/dependency';
 import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
 import {createSearchUrl} from '../../../models/views/search';
@@ -25,6 +24,7 @@
 import {formStyles} from '../../../styles/form-styles';
 import {GrValidationOptions} from '../gr-validation-options/gr-validation-options';
 import {parseCommitMessageString} from '../../../utils/commit-message-formatter-util';
+import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 
 const ERR_COMMIT_NOT_FOUND = 'Unable to find the commit hash of this change.';
 const SPECIFY_REASON_STRING = '<MUST SPECIFY REASON HERE>';
@@ -106,7 +106,7 @@
         .label {
           margin-left: var(--spacing-m);
         }
-        iron-autogrow-textarea {
+        gr-autogrow-textarea {
           font-family: var(--monospace-font-family);
           font-size: var(--font-size-mono);
           line-height: var(--line-height-mono);
@@ -168,14 +168,17 @@
             : nothing}
           <gr-endpoint-decorator name="confirm-revert-change">
             <label for="messageInput"> Revert Commit Message </label>
-            <iron-autogrow-textarea
+            <gr-autogrow-textarea
               id="messageInput"
               class="message"
               .autocomplete=${'on'}
               .maxRows=${15}
-              .bindValue=${this.message}
-              @bind-value-changed=${this.handleBindValueChanged}
-            ></iron-autogrow-textarea>
+              .value=${this.message}
+              @input=${(e: InputEvent) => {
+                const value = (e.target as GrAutogrowTextarea).value ?? '';
+                this.message = value;
+              }}
+            ></gr-autogrow-textarea>
           </gr-endpoint-decorator>
           <gr-validation-options
             .validationOptions=${this.validationOptions}
@@ -316,10 +319,6 @@
     this.showRevertSubmission = true;
   }
 
-  private handleBindValueChanged(e: BindValueChangeEvent) {
-    this.message = e.detail.value ?? '';
-  }
-
   private handleRevertSingleChangeClicked() {
     this.showErrorMessage = false;
     if (this.message)
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts
index 0daa30e..cd75f2c 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts
@@ -32,11 +32,10 @@
             </div>
             <gr-endpoint-decorator name="confirm-revert-change">
               <label for="messageInput"> Revert Commit Message </label>
-              <iron-autogrow-textarea
+              <gr-autogrow-textarea
                 id="messageInput"
                 class="message"
-                aria-disabled="false"
-              ></iron-autogrow-textarea>
+              ></gr-autogrow-textarea>
             </gr-endpoint-decorator>
             <gr-validation-options></gr-validation-options>
           </div>
diff --git a/polygerrit-ui/app/elements/gr-css-mixins.ts b/polygerrit-ui/app/elements/gr-css-mixins.ts
index 0a00819..a4b01c2 100644
--- a/polygerrit-ui/app/elements/gr-css-mixins.ts
+++ b/polygerrit-ui/app/elements/gr-css-mixins.ts
@@ -63,10 +63,6 @@
           --paper-listbox: {
             padding: 0;
           };
-          --iron-autogrow-textarea: {
-            box-sizing: border-box;
-            padding: var(--spacing-s);
-          };
         }
       </style>
     `;
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
index 1ed0d9b..ccc1a61 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
@@ -3,8 +3,8 @@
  * Copyright 2016 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import '@polymer/iron-input/iron-input';
+import '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 import '../../shared/gr-avatar/gr-avatar';
 import '../../shared/gr-date-formatter/gr-date-formatter';
 import '../../shared/gr-tooltip-content/gr-tooltip-content';
@@ -27,6 +27,7 @@
 import {subscribe} from '../../lit/subscription-controller';
 import {resolve} from '../../../models/dependency';
 import {configModelToken} from '../../../models/config/config-model';
+import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 
 @customElement('gr-account-info')
 export class GrAccountInfo extends LitElement {
@@ -93,7 +94,7 @@
           border-radius: var(--border-radius);
           box-shadow: var(--elevation-level-5);
         }
-        iron-autogrow-textarea {
+        gr-autogrow-textarea {
           background-color: var(--view-background-color);
           color: var(--primary-text-color);
         }
@@ -243,19 +244,20 @@
           </div>
         </span>
         <span class="value">
-          <iron-autogrow-textarea
+          <gr-autogrow-textarea
             id="statusInput"
             .label=${'statusInput'}
             ?disabled=${this.saving}
             maxlength="140"
             .value=${this.account?.status}
-            @bind-value-changed=${(e: BindValueChangeEvent) => {
+            @input=${(e: InputEvent) => {
               const oldAccount = this.account;
-              if (!oldAccount || oldAccount.status === e.detail.value) return;
-              this.account = {...oldAccount, status: e.detail.value};
+              const value = (e.target as GrAutogrowTextarea).value ?? '';
+              if (!oldAccount || oldAccount.status === value) return;
+              this.account = {...oldAccount, status: value};
               this.hasStatusChange = true;
             }}
-          ></iron-autogrow-textarea>
+          ></gr-autogrow-textarea>
         </span>
       </section>
       <section>
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
index fda7b8a..e272c4e 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
@@ -25,7 +25,7 @@
 import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
 import {EditableAccountField} from '../../../api/rest-api';
 import {assert, fixture, html} from '@open-wc/testing';
-import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea';
+import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 
 suite('gr-account-info tests', () => {
   let element!: GrAccountInfo;
@@ -121,11 +121,7 @@
               <div class="lengthCounter">0/140</div>
             </span>
             <span class="value">
-              <iron-autogrow-textarea
-                aria-disabled="false"
-                id="statusInput"
-                maxlength="140"
-              />
+              <gr-autogrow-textarea id="statusInput" maxlength="140" />
             </span>
           </section>
           <section>
@@ -296,11 +292,14 @@
     test('status', async () => {
       assert.isFalse(element.hasUnsavedChanges);
 
-      const statusTextarea = queryAndAssert<IronAutogrowTextareaElement>(
+      const statusTextarea = queryAndAssert<GrAutogrowTextarea>(
         element,
         '#statusInput'
       );
       statusTextarea.value = 'new status';
+      statusTextarea.dispatchEvent(
+        new Event('input', {bubbles: true, composed: true})
+      );
       await element.updateComplete;
       assert.isFalse(element.hasNameChange);
       assert.isTrue(element.hasStatusChange);
@@ -343,11 +342,14 @@
       await element.updateComplete;
       assert.isTrue(element.hasNameChange);
 
-      const statusTextarea = queryAndAssert<IronAutogrowTextareaElement>(
+      const statusTextarea = queryAndAssert<GrAutogrowTextarea>(
         element,
         '#statusInput'
       );
       statusTextarea.value = 'new status';
+      statusTextarea.dispatchEvent(
+        new Event('input', {bubbles: true, composed: true})
+      );
       await element.updateComplete;
       assert.isTrue(element.hasStatusChange);
 
@@ -392,11 +394,14 @@
       assert.equal(displaySpan.textContent, account.name);
       assert.isUndefined(inputSpan);
 
-      const statusTextarea = queryAndAssert<IronAutogrowTextareaElement>(
+      const statusTextarea = queryAndAssert<GrAutogrowTextarea>(
         element,
         '#statusInput'
       );
       statusTextarea.value = 'new status';
+      statusTextarea.dispatchEvent(
+        new Event('input', {bubbles: true, composed: true})
+      );
       await element.updateComplete;
       assert.isTrue(element.hasStatusChange);
 
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts
index 2fc93cf..98d0df9 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts
@@ -3,7 +3,7 @@
  * Copyright 2017 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 import '../../shared/gr-button/gr-button';
 import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
 import {GpgKeyId, GpgKeyInfo} from '../../../types/common';
@@ -15,10 +15,10 @@
 import {grFormStyles} from '../../../styles/gr-form-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {assertIsDefined} from '../../../utils/common-util';
-import {BindValueChangeEvent} from '../../../types/events';
 import {fire} from '../../../utils/event-util';
 import {modalStyles} from '../../../styles/gr-modal-styles';
 import {formStyles} from '../../../styles/form-styles';
+import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -75,8 +75,19 @@
         #existing {
           margin-bottom: var(--spacing-l);
         }
-        iron-autogrow-textarea {
+        gr-autogrow-textarea {
           background-color: var(--view-background-color);
+          position: relative;
+          height: auto;
+          min-height: 4em;
+          --gr-autogrow-textarea-border-width: 0px;
+          --gr-autogrow-textarea-border-color: var(--border-color);
+          --input-field-bg: var(--view-background-color);
+          --input-field-disabled-bg: var(--view-background-color);
+          --secondary-bg-color: var(--background-color-secondary);
+          --text-default: var(--primary-text-color);
+          --text-disabled: var(--deemphasized-text-color);
+          --text-secondary: var(--deemphasized-text-color);
         }
       `,
     ];
@@ -128,14 +139,13 @@
           <section>
             <span class="title">New GPG key</span>
             <span class="value">
-              <iron-autogrow-textarea
+              <gr-autogrow-textarea
                 id="newKey"
                 autocomplete="on"
-                .bindValue=${this.newKey}
-                @bind-value-changed=${(e: BindValueChangeEvent) =>
-                  this.handleNewKeyChanged(e)}
+                .value=${this.newKey}
+                @input=${(e: InputEvent) => this.handleNewKeyChanged(e)}
                 placeholder="New GPG Key"
-              ></iron-autogrow-textarea>
+              ></gr-autogrow-textarea>
             </span>
           </section>
           <gr-button
@@ -208,8 +218,9 @@
     this.viewKeyModal?.showModal();
   }
 
-  private handleNewKeyChanged(e: BindValueChangeEvent) {
-    this.newKey = e.detail.value ?? '';
+  private handleNewKeyChanged(e: InputEvent) {
+    const rawValue = (e.target as GrAutogrowTextarea).value ?? '';
+    this.newKey = rawValue;
   }
 
   private handleDeleteKey(index: number) {
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.ts b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.ts
index f7c837d..0455eab 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.ts
@@ -170,13 +170,12 @@
           <section>
             <span class="title"> New GPG key </span>
             <span class="value">
-              <iron-autogrow-textarea
-                aria-disabled="false"
+              <gr-autogrow-textarea
                 autocomplete="on"
                 id="newKey"
                 placeholder="New GPG Key"
               >
-              </iron-autogrow-textarea>
+              </gr-autogrow-textarea>
             </span>
           </section>
           <gr-button
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts
index 0911c51..5de113c 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts
@@ -3,20 +3,19 @@
  * Copyright 2016 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 import '../../shared/gr-button/gr-button';
 import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
 import {SshKeyInfo} from '../../../types/common';
 import {GrButton} from '../../shared/gr-button/gr-button';
-import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import {getAppContext} from '../../../services/app-context';
 import {css, html, LitElement, PropertyValues} from 'lit';
 import {customElement, property, query, state} from 'lit/decorators.js';
 import {grFormStyles} from '../../../styles/gr-form-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {fire} from '../../../utils/event-util';
-import {BindValueChangeEvent} from '../../../types/events';
 import {modalStyles} from '../../../styles/gr-modal-styles';
+import {GrAutogrowTextarea} from '../../shared/gr-autogrow-textarea/gr-autogrow-textarea';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -44,7 +43,7 @@
 
   @query('#addButton') addButton!: GrButton;
 
-  @query('#newKey') newKeyEditor!: IronAutogrowTextareaElement;
+  @query('#newKey') newKeyEditor!: GrAutogrowTextarea;
 
   @query('#viewKeyModal') viewKeyModal!: HTMLDialogElement;
 
@@ -86,7 +85,7 @@
           min-width: 27em;
           width: auto;
         }
-        iron-autogrow-textarea {
+        gr-autogrow-textarea {
           background-color: var(--view-background-color);
         }
       `,
@@ -154,15 +153,16 @@
           <section>
             <span class="title">New SSH key</span>
             <span class="value">
-              <iron-autogrow-textarea
+              <gr-autogrow-textarea
                 id="newKey"
                 autocomplete="on"
                 placeholder="New SSH Key"
-                .bindValue=${this.newKey}
-                @bind-value-changed=${(e: BindValueChangeEvent) => {
-                  this.newKey = e.detail.value ?? '';
+                .value=${this.newKey}
+                @input=${(e: InputEvent) => {
+                  const value = (e.target as GrAutogrowTextarea).value ?? '';
+                  this.newKey = value;
                 }}
-              ></iron-autogrow-textarea>
+              ></gr-autogrow-textarea>
             </span>
           </section>
           <gr-button
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.ts b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.ts
index c7a6c71..e3fbeb5 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.ts
@@ -165,13 +165,12 @@
             <section>
               <span class="title"> New SSH key </span>
               <span class="value">
-                <iron-autogrow-textarea
-                  aria-disabled="false"
+                <gr-autogrow-textarea
                   autocomplete="on"
                   id="newKey"
                   placeholder="New SSH Key"
                 >
-                </iron-autogrow-textarea>
+                </gr-autogrow-textarea>
               </span>
             </section>
             <gr-button
diff --git a/polygerrit-ui/app/elements/shared/gr-autogrow-textarea/gr-autogrow-textarea.ts b/polygerrit-ui/app/elements/shared/gr-autogrow-textarea/gr-autogrow-textarea.ts
new file mode 100644
index 0000000..dc3f814
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-autogrow-textarea/gr-autogrow-textarea.ts
@@ -0,0 +1,355 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import {css, html, LitElement} from 'lit';
+import {
+  customElement,
+  property,
+  query,
+  queryAsync,
+  state,
+} from 'lit/decorators.js';
+import {ifDefined} from 'lit/directives/if-defined.js';
+import {
+  CursorPositionChangeEventDetail,
+  GrAutogrowTextarea as GrAutogrowTextareaApi,
+} from '../../../api/embed';
+
+/**
+ * Waits for the next animation frame.
+ */
+async function animationFrame(): Promise<void> {
+  return new Promise(resolve => {
+    requestAnimationFrame(() => {
+      resolve();
+    });
+  });
+}
+
+/**
+ * A custom textarea component which allows autocomplete functionality.
+ *
+ * Example usage:
+ * <gr-autogrow-textarea></gr-autogrow-textarea>
+ */
+@customElement('gr-autogrow-textarea')
+export class GrAutogrowTextarea
+  extends LitElement
+  implements GrAutogrowTextareaApi
+{
+  // editableTextAreaElement is available right away where it may be undefined. This
+  // is used for calls for scrollTop as if it is undefined then we can fallback
+  // to 0. For other usecases use editableTextArea.
+  @query('.editableTextArea')
+  private readonly editableTextAreaElement?: HTMLTextAreaElement;
+
+  @queryAsync('.editableTextArea')
+  private readonly editableTextArea?: Promise<HTMLTextAreaElement>;
+
+  @property({type: Boolean, reflect: true}) disabled = false;
+
+  @property({type: String, reflect: true}) placeholder: string | undefined;
+
+  @property({type: String}) value: string = '';
+
+  @property({type: Number}) rows = 1;
+
+  @property({type: Number}) maxRows = 0;
+
+  /**
+   * Sets cursor at the end of content on focus.
+   */
+  @property({type: Boolean}) putCursorAtEndOnFocus = false;
+
+  @property({type: String}) autocomplete = 'off';
+
+  @property({type: String}) inputmode?: string;
+
+  @property({type: String}) readonly?: string;
+
+  @property({type: Boolean}) required = false;
+
+  @property({type: Number}) minlength?: number;
+
+  @property({type: Number}) maxlength?: number;
+
+  @property({type: String}) label?: string;
+
+  /*
+   * Is textarea focused. This is a readonly property.
+   */
+  get isFocused(): boolean {
+    return !!this.focused;
+  }
+
+  /**
+   * Native element for editable div.
+   */
+  get nativeElement() {
+    return this.editableTextAreaElement;
+  }
+
+  /**
+   * Scroll Top for editable div.
+   */
+  override get scrollTop() {
+    return this.editableTextAreaElement?.scrollTop ?? 0;
+  }
+
+  private focused = false;
+
+  @state() private tokens: string[] = [];
+
+  static override get styles() {
+    return [
+      css`
+        :host {
+          display: inline-block;
+          position: relative;
+          width: 400px;
+          border: var(--gr-autogrow-textarea-border-width, 2px) solid
+            var(--gr-textarea-border-color, white);
+          border-radius: 4px;
+          padding: 2px;
+          -moz-appearance: textarea;
+          -webkit-appearance: textarea;
+          appearance: textarea;
+          overflow: hidden;
+        }
+        :host([disabled]) {
+          textarea {
+            background-color: var(--input-field-disabled-bg, lightgrey);
+            color: var(--text-disabled, black);
+            cursor: default;
+          }
+        }
+        .mirror-text {
+          visibility: hidden;
+          word-wrap: break-word;
+          padding: var(--gr-autogrow-textarea-padding);
+          box-sizing: var(--gr-autogrow-textarea-box-sizing);
+        }
+        .textarea-container {
+          position: absolute;
+          top: 0;
+          left: 0;
+          right: 0;
+          bottom: 0;
+        }
+        textarea {
+          position: relative;
+          outline: none;
+          border: none;
+          resize: none;
+          background: inherit;
+          color: inherit;
+          width: 100%;
+          height: 100%;
+          font-size: inherit;
+          font-family: inherit;
+          line-height: inherit;
+          text-align: inherit;
+          padding: var(--gr-autogrow-textarea-padding);
+          box-sizing: var(--gr-autogrow-textarea-box-sizing);
+        }
+        textarea:focus-visible {
+          border-color: var(--gr-textarea-focus-outline-color, black);
+          outline: none;
+        }
+      `,
+    ];
+  }
+
+  override render() {
+    return html` <div id="mirror" class="mirror-text" aria-hidden="true">
+        ${this.tokens.length === 1 && this.tokens[0] === ''
+          ? html`&#160;`
+          : this.tokens.map((t, i) =>
+              i === this.tokens.length - 1 ? html`${t}&#160;` : html`${t}<br />`
+            )}
+      </div>
+      <div class="textarea-container">
+        <textarea
+          class="editableTextArea"
+          .value=${this.value}
+          aria-label=${ifDefined(this.label)}
+          aria-disabled=${this.disabled}
+          aria-multiline="true"
+          aria-placeholder=${ifDefined(this.placeholder)}
+          autocomplete=${this.autocomplete}
+          ?autofocus=${this.autofocus}
+          autocapitalize=${this.autocapitalize}
+          inputmode=${ifDefined(this.inputmode)}
+          placeholder=${ifDefined(this.placeholder)}
+          ?disabled=${this.disabled}
+          rows=${this.rows}
+          minlength=${ifDefined(this.minlength)}
+          maxlength=${ifDefined(this.maxlength)}
+          spellcheck=${this.spellcheck}
+          @input=${this.onInput}
+          @focus=${this.onFocus}
+          @blur=${this.onBlur}
+          @keyup=${this.handleKeyUp}
+          @mouseup=${this.handleMouseUp}
+        ></textarea>
+      </div>`;
+  }
+
+  override updated(changed: Map<string, unknown>) {
+    if (changed.has('value')) {
+      if (
+        this.editableTextAreaElement &&
+        this.editableTextAreaElement.value !== this.value
+      ) {
+        this.editableTextAreaElement.value = this.value ?? '';
+      }
+      this.updateMirror();
+    }
+    if (changed.has('rows') || changed.has('maxRows')) {
+      this.updateMirror();
+    }
+  }
+
+  override async focus() {
+    const editableTextAreaElement = await this.editableTextArea;
+    const isFocused = this.isFocused;
+    editableTextAreaElement?.focus?.();
+    // If already focused, do not change the cursor position.
+    if (this.putCursorAtEndOnFocus && !isFocused) {
+      await this.putCursorAtEnd();
+    }
+  }
+
+  /**
+   * Puts the cursor at the end of existing content.
+   * Scrolls the content of textarea towards the end.
+   */
+  async putCursorAtEnd() {
+    const editableTextAreaElement = await this.editableTextArea;
+    if (!editableTextAreaElement) return;
+
+    const length = this.value.length;
+    editableTextAreaElement.selectionStart = length;
+    editableTextAreaElement.selectionEnd = length;
+    editableTextAreaElement.focus();
+  }
+
+  /**
+   * Sets cursor position to given position and scrolls the content to cursor
+   * position.
+   *
+   * If position is out of bounds of value of textarea then cursor is places at
+   * end of content of textarea.
+   */
+  public setCursorPosition(position: number) {
+    if (!this.editableTextAreaElement) return;
+
+    this.editableTextAreaElement.selectionStart = position;
+    this.editableTextAreaElement.selectionEnd = position;
+
+    this.onCursorPositionChange();
+  }
+
+  /**
+   * Replaces text from start and end cursor position.
+   */
+  async setRangeText(replacement: string, start: number, end: number) {
+    const pre = this.value?.substring(0, start) ?? '';
+    const post = this.value?.substring(end, this.value?.length ?? 0) ?? '';
+    this.value = pre + replacement + post;
+
+    await animationFrame();
+
+    this.setCursorPosition(pre.length + replacement.length);
+  }
+
+  private onInput(event: Event) {
+    event.preventDefault();
+    event.stopImmediatePropagation();
+
+    const target = event.target as HTMLTextAreaElement;
+    this.value = target.value;
+
+    this.fire('input', {value: this.value});
+  }
+
+  private onFocus() {
+    this.focused = true;
+    this.onCursorPositionChange();
+  }
+
+  private onBlur() {
+    this.focused = false;
+    this.onCursorPositionChange();
+  }
+
+  private handleKeyUp() {
+    this.onCursorPositionChange();
+  }
+
+  private handleMouseUp() {
+    this.onCursorPositionChange();
+  }
+
+  private fire<T>(type: string, detail?: T) {
+    this.dispatchEvent(
+      new CustomEvent(type, {detail, bubbles: true, composed: true})
+    );
+  }
+
+  private onCursorPositionChange() {
+    const cursorPosition = this.getCursorPosition();
+    this.fire('cursorPositionChange', {position: cursorPosition});
+  }
+
+  public getCursorPosition() {
+    return this.editableTextAreaElement?.selectionStart ?? -1;
+  }
+
+  public async getCursorPositionAsync() {
+    const editableTextAreaElement = await this.editableTextArea;
+    return editableTextAreaElement?.selectionStart ?? -1;
+  }
+
+  private updateMirror() {
+    if (!this.editableTextAreaElement) return;
+    this.tokens = this.constrain(
+      this.tokenize(this.editableTextAreaElement.value)
+    );
+  }
+
+  private tokenize(val: string): string[] {
+    return val
+      ? val
+          .replace(/&/g, '&amp;')
+          .replace(/"/g, '&quot;')
+          .replace(/'/g, '&#39;')
+          .replace(/</g, '&lt;')
+          .replace(/>/g, '&gt;')
+          .split('\n')
+      : [''];
+  }
+
+  private constrain(tokens: string[]): string[] {
+    let result = tokens.slice();
+    if (this.maxRows > 0 && result.length > this.maxRows) {
+      result = result.slice(0, this.maxRows);
+    }
+    while (this.rows > 0 && result.length < this.rows) {
+      result.push('');
+    }
+    return result;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-autogrow-textarea': GrAutogrowTextarea;
+  }
+  interface HTMLElementEventMap {
+    // prettier-ignore
+    'cursorPositionChange': CustomEvent<CursorPositionChangeEventDetail>;
+  }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-autogrow-textarea/gr-autogrow-textarea_test.ts b/polygerrit-ui/app/elements/shared/gr-autogrow-textarea/gr-autogrow-textarea_test.ts
new file mode 100644
index 0000000..9b33189
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-autogrow-textarea/gr-autogrow-textarea_test.ts
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../../../test/common-test-setup';
+import {assert, fixture, html} from '@open-wc/testing';
+import {GrAutogrowTextarea} from './gr-autogrow-textarea';
+import './gr-autogrow-textarea';
+
+suite('gr-autogrow-textarea tests', () => {
+  let element: GrAutogrowTextarea;
+
+  setup(async () => {
+    element = await fixture(
+      html`<gr-autogrow-textarea></gr-autogrow-textarea>`
+    );
+  });
+
+  test('render', async () => {
+    // prettier and shadowDom string disagree about wrapping in <p> tag.
+    assert.shadowDom.equal(
+      element,
+      /* prettier-ignore */ /* HTML */ `
+      <div
+        aria-hidden="true"
+        class="mirror-text"
+        id="mirror"
+      >
+      </div>
+      <div class="textarea-container">
+        <textarea
+          aria-disabled="false"
+          aria-multiline="true"
+          autocapitalize=""
+          autocomplete="off"
+          class="editableTextArea"
+          rows="1"
+          spellcheck="true"
+        >
+        </textarea>
+      </div>
+    `
+    );
+  });
+});
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
index ab04d00..c011476 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
@@ -6,12 +6,11 @@
 import '../gr-dialog/gr-dialog';
 import {css, html, LitElement} from 'lit';
 import {customElement, property, query} from 'lit/decorators.js';
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
-import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea';
+import '../gr-autogrow-textarea/gr-autogrow-textarea';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {assertIsDefined} from '../../../utils/common-util';
-import {BindValueChangeEvent} from '../../../types/events';
 import {fireNoBubble} from '../../../utils/event-util';
+import {GrAutogrowTextarea} from '../gr-autogrow-textarea/gr-autogrow-textarea';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -34,7 +33,7 @@
    */
 
   @query('#messageInput')
-  messageInput?: IronAutogrowTextareaElement;
+  messageInput?: GrAutogrowTextarea;
 
   @property({type: String})
   message = '';
@@ -63,7 +62,7 @@
           display: block;
           width: 100%;
         }
-        iron-autogrow-textarea {
+        gr-autogrow-textarea {
           font-family: var(--monospace-font-family);
           font-size: var(--font-size-mono);
           line-height: var(--line-height-mono);
@@ -87,23 +86,24 @@
           circumstances.
         </p>
         <label for="messageInput">Enter comment delete reason</label>
-        <iron-autogrow-textarea
+        <gr-autogrow-textarea
           id="messageInput"
           class="message"
           autocomplete="on"
           placeholder="&lt;Insert reasoning here&gt;"
-          .bindValue=${this.message}
-          @bind-value-changed=${(e: BindValueChangeEvent) => {
-            this.message = e.detail.value ?? '';
+          .value=${this.message}
+          @input=${(e: InputEvent) => {
+            const value = (e.target as GrAutogrowTextarea).value ?? '';
+            this.message = value;
           }}
-        ></iron-autogrow-textarea>
+        ></gr-autogrow-textarea>
       </div>
     </gr-dialog>`;
   }
 
   resetFocus() {
     assertIsDefined(this.messageInput, 'messageInput');
-    this.messageInput.textarea.focus();
+    this.messageInput.focus();
   }
 
   private handleConfirmTap(e: Event) {
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog_test.ts b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog_test.ts
index ac041586..366edbd 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog_test.ts
@@ -34,14 +34,13 @@
           circumstances.
           </p>
           <label for="messageInput"> Enter comment delete reason </label>
-          <iron-autogrow-textarea
-            aria-disabled="false"
+          <gr-autogrow-textarea
             autocomplete="on"
             class="message"
             id="messageInput"
             placeholder="<Insert reasoning here>"
           >
-          </iron-autogrow-textarea>
+          </gr-autogrow-textarea>
         </div>
       </gr-dialog>
     `
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
index 00b67f4..d490423 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
@@ -3,7 +3,7 @@
  * Copyright 2016 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
+import '../gr-autogrow-textarea/gr-autogrow-textarea';
 import '../../../styles/shared-styles';
 import '../gr-button/gr-button';
 import '../gr-icon/gr-icon';
@@ -17,7 +17,6 @@
 import {getAppContext} from '../../../services/app-context';
 import {debounce, DelayedTask} from '../../../utils/async-util';
 import {assertIsDefined} from '../../../utils/common-util';
-import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import {Interaction} from '../../../constants/reporting';
 import {html, LitElement} from 'lit';
 import {customElement, property, query, state} from 'lit/decorators.js';
@@ -25,7 +24,6 @@
 import {css} from 'lit';
 import {PropertyValues} from 'lit';
 import {
-  BindValueChangeEvent,
   EditableContentSaveEvent,
   ValueChangedEvent,
 } from '../../../types/events';
@@ -52,6 +50,7 @@
   FormattingError,
 } from '../../../utils/commit-message-formatter-util';
 import {modalStyles} from '../../../styles/gr-modal-styles';
+import {GrAutogrowTextarea} from '../gr-autogrow-textarea/gr-autogrow-textarea';
 
 const RESTORED_MESSAGE = 'Content restored from a previous edit.';
 const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
@@ -76,8 +75,8 @@
  */
 @customElement('gr-editable-content')
 export class GrEditableContent extends LitElement {
-  @query('iron-autogrow-textarea')
-  private textarea?: IronAutogrowTextareaElement;
+  @query('gr-autogrow-textarea')
+  private textarea?: GrAutogrowTextarea;
 
   @query('#uploaderConfirmDialog')
   private readonly uploaderConfirmDialog?: HTMLDialogElement;
@@ -221,7 +220,7 @@
         :host {
           display: block;
         }
-        :host([disabled]) iron-autogrow-textarea {
+        :host([disabled]) gr-autogrow-textarea {
           opacity: 0.5;
         }
         .viewer {
@@ -236,15 +235,15 @@
           max-height: var(--collapsed-max-height, 300px);
           overflow: hidden;
         }
-        .editor iron-autogrow-textarea,
+        .editor gr-autogrow-textarea,
         .viewer {
           min-height: 100px;
         }
-        .editor iron-autogrow-textarea {
+        .editor gr-autogrow-textarea {
           background-color: var(--view-background-color);
           width: 100%;
           display: block;
-          --iron-autogrow-textarea_-_padding: var(--spacing-m);
+          --gr-autogrow-textarea-padding: var(--spacing-m);
         }
         .editButtons {
           display: flex;
@@ -338,14 +337,15 @@
     return html`
       <div class="editor">
         <div>
-          <iron-autogrow-textarea
+          <gr-autogrow-textarea
             autocomplete="on"
-            .bindValue=${this.newContent}
+            .value=${this.newContent}
             ?disabled=${this.disabled}
-            @bind-value-changed=${(e: BindValueChangeEvent) => {
-              this.newContent = e.detail.value ?? '';
+            @input=${(e: InputEvent) => {
+              const value = (e.target as GrAutogrowTextarea).value ?? '';
+              this.newContent = value;
             }}
-          ></iron-autogrow-textarea>
+          ></gr-autogrow-textarea>
         </div>
       </div>
     `;
@@ -488,7 +488,7 @@
   }
 
   focusTextarea() {
-    this.textarea?.textarea.focus();
+    this.textarea?.focus();
   }
 
   /**
@@ -693,7 +693,7 @@
   handleFormat(e: Event) {
     e.preventDefault();
 
-    const textarea = this.textarea?.textarea;
+    const textarea = this.textarea?.nativeElement;
     if (!textarea) return;
 
     // If we have lastFormattedContent, we're undoing
@@ -757,7 +757,7 @@
   }
 
   private getCurrentCursorLine(): number {
-    const textarea = this.textarea?.textarea;
+    const textarea = this.textarea?.nativeElement;
     if (!textarea) return -1;
 
     // Calculate which line the cursor is on
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.ts
index c0c9938..60cfc34 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.ts
@@ -22,8 +22,8 @@
   RevisionPatchSetNum,
 } from '../../../api/rest-api';
 import {changeViewModelToken} from '../../../models/views/change';
-import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import {GrDialog} from '../gr-dialog/gr-dialog';
+import {GrAutogrowTextarea} from '../gr-autogrow-textarea/gr-autogrow-textarea';
 
 const emails = [
   {
@@ -428,10 +428,10 @@
       );
 
       element.newContent = 'line1\nline2    \nline3';
-      const textarea = queryAndAssert<IronAutogrowTextareaElement>(
+      const textarea = queryAndAssert<GrAutogrowTextarea>(
         element,
-        'iron-autogrow-textarea'
-      ).textarea;
+        'gr-autogrow-textarea'
+      ).nativeElement!;
 
       textarea.setSelectionRange(7, 7); // Position cursor after "line2"
       await element.updateComplete;
@@ -451,10 +451,10 @@
       );
 
       element.newContent = 'line1    \nline2    \nline3    ';
-      const textarea = queryAndAssert<IronAutogrowTextareaElement>(
+      const textarea = queryAndAssert<GrAutogrowTextarea>(
         element,
-        'iron-autogrow-textarea'
-      ).textarea;
+        'gr-autogrow-textarea'
+      ).nativeElement!;
 
       textarea.setSelectionRange(7, 7); // Position cursor after "line2"
       await element.updateComplete;
diff --git a/polygerrit-ui/app/elements/shared/gr-suggestion-textarea/gr-suggestion-textarea.ts b/polygerrit-ui/app/elements/shared/gr-suggestion-textarea/gr-suggestion-textarea.ts
index 17461c7..ac87d07 100644
--- a/polygerrit-ui/app/elements/shared/gr-suggestion-textarea/gr-suggestion-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-suggestion-textarea/gr-suggestion-textarea.ts
@@ -5,7 +5,6 @@
  */
 import '../gr-autocomplete-dropdown/gr-autocomplete-dropdown';
 import '../gr-cursor-manager/gr-cursor-manager';
-import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
 import '../../../styles/shared-styles';
 import '../../../embed/gr-textarea';
 import {getAppContext} from '../../../services/app-context';
@@ -252,7 +251,6 @@
           --text-default: var(--primary-text-color);
           --text-disabled: var(--deemphasized-text-color);
           --text-secondary: var(--deemphasized-text-color);
-          --iron-autogrow-textarea_-_padding: var(--spacing-s);
         }
         #hiddenText #emojiSuggestions {
           visibility: visible;
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index e136ae2..e327117 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -7,7 +7,6 @@
     "@polymer/decorators": "^3.0.0",
     "@polymer/font-roboto-local": "^3.0.2",
     "@polymer/iron-a11y-announcer": "^3.2.0",
-    "@polymer/iron-autogrow-textarea": "^3.0.3",
     "@polymer/iron-input": "^3.0.1",
     "@polymer/paper-button": "^3.0.1",
     "@polymer/paper-input": "^3.2.1",
diff --git a/polygerrit-ui/app/styles/form-styles.ts b/polygerrit-ui/app/styles/form-styles.ts
index 47f610e03..1b9d3a4 100644
--- a/polygerrit-ui/app/styles/form-styles.ts
+++ b/polygerrit-ui/app/styles/form-styles.ts
@@ -18,18 +18,18 @@
   }
   /* prettier formatter removes semi-colons after css mixins. */
   /* prettier-ignore */
-  iron-autogrow-textarea {
+  gr-autogrow-textarea {
     background-color: inherit;
     color: var(--primary-text-color);
     border: 1px solid var(--border-color);
     border-radius: var(--border-radius);
     padding: 0;
     box-sizing: border-box;
-    /* iron-autogrow-textarea has a "-webkit-appearance: textarea" :host
+    /* gr-autogrow-textarea has a "-webkit-appearance: textarea" :host
         css rule, which prevents overriding the border color. Clear that. */
     -webkit-appearance: none;
-    --iron-autogrow-textarea_-_box-sizing: border-box;
-    --iron-autogrow-textarea_-_padding: var(--spacing-s);
+    --gr-autogrow-textarea-box-sizing: border-box;
+    --gr-autogrow-textarea-padding: var(--spacing-s);
   }
   input,
   textarea,
diff --git a/polygerrit-ui/app/styles/gr-form-styles.ts b/polygerrit-ui/app/styles/gr-form-styles.ts
index 283dadc..58a1c74 100644
--- a/polygerrit-ui/app/styles/gr-form-styles.ts
+++ b/polygerrit-ui/app/styles/gr-form-styles.ts
@@ -82,7 +82,7 @@
   .gr-form-styles td:last-child gr-button {
     width: 100%;
   }
-  .gr-form-styles iron-autogrow-textarea {
+  .gr-form-styles gr-autogrow-textarea {
     height: auto;
     min-height: 4em;
   }
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index 2b0477f..4ebb3fe 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -68,7 +68,7 @@
   dependencies:
     "@polymer/polymer" "^3.0.0"
 
-"@polymer/iron-autogrow-textarea@^3.0.0-pre.26", "@polymer/iron-autogrow-textarea@^3.0.3":
+"@polymer/iron-autogrow-textarea@^3.0.0-pre.26":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@polymer/iron-autogrow-textarea/-/iron-autogrow-textarea-3.0.3.tgz#b75dbebc23ce47d428a26156709d4a8a4c05823e"
   integrity sha512-5r0VkWrIlm0JIp5E5wlnvkw7slK72lFRZXncmrsLZF+6n1dg2rI8jt7xpFzSmUWrqpcyXwyKaGaDvUjl3j4JLA==
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor-dark.png
index 77839e2..cfa4162c 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor-dark.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor.png
index 6a6f015..134237b 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor.png
Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix-dark.png
index b8bd9f4..34fca7b 100644
--- a/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix-dark.png
+++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix-dark.png
Binary files differ