Skip to content

feat: [DSR] Add React TextArea component#1036

Merged
georgewrmarshall merged 9 commits into
mainfrom
textarea
Jun 2, 2026
Merged

feat: [DSR] Add React TextArea component#1036
georgewrmarshall merged 9 commits into
mainfrom
textarea

Conversation

@georgewrmarshall

@georgewrmarshall georgewrmarshall commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Description

Adds the React TextArea component to @metamask/design-system-react, migrated from the MetaMask Extension component library and aligned with the current design-system API.

The React component renders the native <textarea> element directly. Native textarea attributes, events, className, style, data-testid, and ref are applied to the top-level TextArea instead of going through wrapper-only props such as inputProps or inputRef.

This PR is intentionally React-only. React Native TextArea and TextAreaPropsShared were restored to current main behavior. Follow-up #1202 covers removing React Native TextArea inputElement and textVariant support after the mobile usage audit.

What is included

@metamask/design-system-react

  • TextArea component as a controlled multiline native <textarea>
  • TextAreaResize const object with None, Both, Horizontal, and Vertical; default is None
  • Direct native textarea prop support for rows, cols, maxLength, required, id, name, data-testid, onKeyDown, onPaste, style, className, and ref
  • No React inputElement, inputProps, inputRef, or textVariant API
  • Focus, error, disabled, and read-only state handling
  • aria-invalid support when isError is true
  • Storybook stories, README docs with Canvas examples, Figma Code Connect, unit tests, and package exports

Reviewer notes

  • inputElement was intentionally not added to the React API. The remaining React Native use is being tracked separately in Remove React Native TextArea inputElement and textVariant support #1202, along with textVariant cleanup.
  • cols is supported as a native attribute, but the React docs/stories treat width as a layout concern (w-full / container width), so rows is the primary sizing control shown to reviewers.
  • The README uses Canvas-based examples so the rendered example and the displayed code stay aligned with the actual React TextArea API instead of a wrapper helper.

Related issues

Fixes:

Follow-up:

Manual testing steps

  1. Run yarn workspace @metamask/design-system-react test --testPathPattern=TextArea --passWithNoTests --no-coverage
  2. Run yarn eslint packages/design-system-react/src/components/TextArea/TextArea.tsx packages/design-system-react/src/components/TextArea/TextArea.types.ts packages/design-system-react/src/components/TextArea/TextArea.test.tsx packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
  3. Run yarn workspace @metamask/design-system-react build
  4. Run yarn workspace @metamask/storybook-react build-storybook --quiet
  5. Run git diff --check

Screenshots/Recordings

Before

No React TextArea component in @metamask/design-system-react.

After

React TextArea in storybook with MDX docs and controls

textarea.after720.mov

React TextArea in MMDS aligns with Extension

mmds.vs.extension720.mov

React TextArea in MMDS aligns with MMDS React Native

react.vs.reactnative.mov

React TextArea and Figma alignment

React.figma.alignment720.mov

Pre-merge author checklist

  • I have followed MetaMask Contributor Docs
  • I have completed the PR template to the best of my ability
  • I have included tests if applicable
  • I have documented my code using JSDoc format if applicable
  • I have applied the right labels on the PR (see labeling guidelines). Not required for external contributors.

Pre-merge reviewer checklist

  • I have manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Low Risk
New public UI component in the design system with tests and docs; no auth, data, or shared-package contract changes beyond documentation.

Overview
Adds a React-only TextArea to @metamask/design-system-react: a controlled multiline field that renders a native <textarea> as the root element, with design-system styling and package exports.

The API drops Extension-style wrappers (inputElement, inputProps, inputRef, textVariant) in favor of forwarding native textarea props, events, className, style, data-testid, and ref directly. Shared behavior comes from TextAreaPropsShared (minus those RN-oriented fields); resize is React-only via TextAreaResize and Tailwind class mapping. States include disabled, read-only, error (aria-invalid), and CSS-driven focus borders.

Also ships Storybook (MDX + stories), Figma Code Connect, unit tests, and architecture doc updates: standard ...props forwarding on the rendered element, and guidance to keep platform-only consts (e.g. TextAreaResize) in the React package rather than shared.

Reviewed by Cursor Bugbot for commit 12cb440. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Web Textarea uses visible border instead of transparent
    • Replaced the non-error border class with border-transparent to match borderless design and Input behavior.
  • ✅ Fixed: Textarea metrics constant duplicates Input's identical constant
    • Removed duplicated RN Textarea metrics object and re-exported the Input metrics constant instead.

Create PR

Or push these changes by commenting:

@cursor push 33e5ca596b
Preview (33e5ca596b)
diff --git a/packages/design-system-react-native/src/components/Textarea/Textarea.constants.ts b/packages/design-system-react-native/src/components/Textarea/Textarea.constants.ts
--- a/packages/design-system-react-native/src/components/Textarea/Textarea.constants.ts
+++ b/packages/design-system-react-native/src/components/Textarea/Textarea.constants.ts
@@ -1,73 +1,11 @@
-import { typography } from '@metamask/design-tokens';
+import { MAP_TEXT_VARIANT_INPUT_METRICS } from '../Input/Input.constants';
 
-import { TextVariant } from '../../types';
-
 /**
  * Typographic metrics for Textarea: same tokens as `text-*` utilities but **without** `lineHeight`.
  * React Native `TextInput` with multiline aligns text more predictably when line height is not set
  * from the design-system paragraph specs.
  */
-export const MAP_TEXT_VARIANT_TEXTAREA_METRICS: Record<
-  TextVariant,
-  { fontSize: number; letterSpacing: number }
-> = {
-  [TextVariant.DisplayLg]: {
-    fontSize: typography.sDisplayLG.fontSize,
-    letterSpacing: typography.sDisplayLG.letterSpacing,
-  },
-  [TextVariant.DisplayMd]: {
-    fontSize: typography.sDisplayMD.fontSize,
-    letterSpacing: typography.sDisplayMD.letterSpacing,
-  },
-  [TextVariant.HeadingLg]: {
-    fontSize: typography.sHeadingLG.fontSize,
-    letterSpacing: typography.sHeadingLG.letterSpacing,
-  },
-  [TextVariant.HeadingMd]: {
-    fontSize: typography.sHeadingMD.fontSize,
-    letterSpacing: typography.sHeadingMD.letterSpacing,
-  },
-  [TextVariant.HeadingSm]: {
-    fontSize: typography.sHeadingSM.fontSize,
-    letterSpacing: typography.sHeadingSM.letterSpacing,
-  },
-  [TextVariant.BodyLg]: {
-    fontSize: typography.sBodyLGMedium.fontSize,
-    letterSpacing: typography.sBodyLGMedium.letterSpacing,
-  },
-  [TextVariant.BodyMd]: {
-    fontSize: typography.sBodyMD.fontSize,
-    letterSpacing: typography.sBodyMD.letterSpacing,
-  },
-  [TextVariant.BodySm]: {
-    fontSize: typography.sBodySM.fontSize,
-    letterSpacing: typography.sBodySM.letterSpacing,
-  },
-  [TextVariant.BodyXs]: {
-    fontSize: typography.sBodyXS.fontSize,
-    letterSpacing: typography.sBodyXS.letterSpacing,
-  },
-  [TextVariant.PageHeading]: {
-    fontSize: typography.sPageHeading.fontSize,
-    letterSpacing: typography.sPageHeading.letterSpacing,
-  },
-  [TextVariant.SectionHeading]: {
-    fontSize: typography.sSectionHeading.fontSize,
-    letterSpacing: typography.sSectionHeading.letterSpacing,
-  },
-  [TextVariant.ButtonLabelMd]: {
-    fontSize: typography.sButtonLabelMd.fontSize,
-    letterSpacing: typography.sButtonLabelMd.letterSpacing,
-  },
-  [TextVariant.ButtonLabelLg]: {
-    fontSize: typography.sButtonLabelLg.fontSize,
-    letterSpacing: typography.sButtonLabelLg.letterSpacing,
-  },
-  [TextVariant.AmountDisplayLg]: {
-    fontSize: typography.sAmountDisplayLg.fontSize,
-    letterSpacing: typography.sAmountDisplayLg.letterSpacing,
-  },
-};
+export const MAP_TEXT_VARIANT_TEXTAREA_METRICS = MAP_TEXT_VARIANT_INPUT_METRICS;
 
 /**
  * Default number of lines displayed in the Textarea.

diff --git a/packages/design-system-react/src/components/Textarea/Textarea.tsx b/packages/design-system-react/src/components/Textarea/Textarea.tsx
--- a/packages/design-system-react/src/components/Textarea/Textarea.tsx
+++ b/packages/design-system-react/src/components/Textarea/Textarea.tsx
@@ -30,7 +30,7 @@
       'placeholder:text-alternative',
       isError
         ? 'border-error-default focus:border-error-default'
-        : 'border-default focus:border-primary-default',
+        : 'border-transparent focus:border-primary-default',
       CLASSMAP_TEXTAREA_RESIZE[resize],
       CLASSMAP_TEXT_VARIANT_FONTSTYLE[textVariant],
       CLASSMAP_TEXT_VARIANT_FONTWEIGHT[textVariant],

You can send follow-ups to this agent here.

Comment thread packages/design-system-react/src/components/Textarea/Textarea.tsx Outdated
Comment thread packages/design-system-react-native/src/components/Textarea/Textarea.constants.ts Outdated
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Storybook argTypes includes forbidden className control
    • Removed the className entry from TextArea.stories.tsx argTypes per project rules.

Create PR

Or push these changes by commenting:

@cursor push 67e9b2eb28
Preview (67e9b2eb28)
diff --git a/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx b/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
--- a/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
+++ b/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
@@ -62,10 +62,6 @@
     rows: {
       control: 'number',
     },
-    className: {
-      control: 'text',
-      description: 'Additional CSS classes merged with the component defaults',
-    },
   },
 };

You can send follow-ups to the cloud agent here.

@georgewrmarshall georgewrmarshall changed the title feat: [DSR+DSRN] Migrate Textarea component from extension feat: [DSR] Add TextArea component May 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Default story uses named wrapper hiding component in View Code
    • Replaced the Default story’s wrapper with inline useState in render so View Code shows TextArea.

Create PR

Or push these changes by commenting:

@cursor push c5f346b89c
Preview (c5f346b89c)
diff --git a/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx b/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
--- a/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
+++ b/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
@@ -78,7 +78,19 @@
     value: '',
     placeholder: 'Sample placeholder',
   },
-  render: (args) => <ControlledTextArea {...args} />,
+  render: (args) => {
+    const [value, setValue] = useState(args.value ?? '');
+    useEffect(() => {
+      setValue(args.value ?? '');
+    }, [args.value]);
+    return (
+      <TextArea
+        {...args}
+        value={value}
+        onChange={(event) => setValue(event.target.value)}
+      />
+    );
+  },
 };
 
 export const TextVariantStory: Story = {

You can send follow-ups to the cloud agent here.

@github-actions

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Story wrapper silently drops onChange callback
    • Updated ControlledTextArea to call props.onChange after setValue so Storybook args receive change events.

Create PR

Or push these changes by commenting:

@cursor push a3c3f60e24
Preview (a3c3f60e24)
diff --git a/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx b/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
--- a/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
+++ b/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
@@ -16,7 +16,10 @@
     <TextArea
       {...props}
       value={value}
-      onChange={(event) => setValue(event.target.value)}
+      onChange={(event) => {
+        setValue(event.target.value);
+        props.onChange?.(event);
+      }}
     />
   );
 }

You can send follow-ups to the cloud agent here.

Comment thread packages/design-system-react/src/components/TextArea/TextArea.stories.tsx Outdated
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Rest parameter uses ...rest instead of ...props
    • Renamed the TextArea component’s spread parameter from ...rest to ...props and updated its spread usage on the root element to match.

Create PR

Or push these changes by commenting:

@cursor push 955c5e84e7
Preview (955c5e84e7)
diff --git a/packages/design-system-react-native/src/components/TextArea/TextArea.tsx b/packages/design-system-react-native/src/components/TextArea/TextArea.tsx
--- a/packages/design-system-react-native/src/components/TextArea/TextArea.tsx
+++ b/packages/design-system-react-native/src/components/TextArea/TextArea.tsx
@@ -22,7 +22,6 @@
       inputRef,
       isDisabled = false,
       isError = false,
-      textVariant,
       inputElement,
       style,
       twClassName,
@@ -97,7 +96,6 @@
             onChangeText={onChangeText}
             placeholder={placeholder}
             isReadOnly={isReadOnly}
-            textVariant={textVariant}
             isDisabled={isDisabled}
             autoFocus={autoFocus}
             onBlur={onBlurHandler}

diff --git a/packages/design-system-react-native/src/components/TextArea/TextArea.types.ts b/packages/design-system-react-native/src/components/TextArea/TextArea.types.ts
--- a/packages/design-system-react-native/src/components/TextArea/TextArea.types.ts
+++ b/packages/design-system-react-native/src/components/TextArea/TextArea.types.ts
@@ -9,9 +9,10 @@
  * Additional props merged onto the inner `Input` (`../Input/Input.tsx`).
  *
  * TextArea owns `value`, `onChangeText`, `placeholder`, `isReadOnly`, `onFocus`,
- * `onBlur`, `isDisabled`, `autoFocus`, `textVariant`, multiline (always on), and inner
- * layout (merged with any `twClassName` you pass here). `placeholderTextColor` is
- * omitted (Input sets it from theme).
+ * `onBlur`, `isDisabled`, `autoFocus`, multiline (always on), and inner layout
+ * (merged with any `twClassName` you pass here). `textVariant` is omitted so
+ * TextArea keeps fixed text styling. `placeholderTextColor` is omitted (Input
+ * sets it from theme).
  */
 type TextAreaInputProps = Omit<
   InputProps,

diff --git a/packages/design-system-react/src/components/TextArea/README.mdx b/packages/design-system-react/src/components/TextArea/README.mdx
new file mode 100644
--- /dev/null
+++ b/packages/design-system-react/src/components/TextArea/README.mdx
@@ -1,0 +1,328 @@
+import { Canvas, Controls } from '@storybook/addon-docs/blocks';
+import * as TextAreaStories from './TextArea.stories';
+
+# TextArea
+
+TextArea renders a controlled, multiline text input inside a bordered container. Use TextField when you need a single-line field or optional leading and trailing accessories.
+
+```tsx
+import { TextArea } from '@metamask/design-system-react';
+
+<TextArea value="" placeholder="Enter multiple lines..." />;
+```
+
+<Canvas of={TextAreaStories.Default} />
+
+## Props
+
+### `value`
+
+Required controlled value for the TextArea.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>string</code>
+      </td>
+      <td align="left">Yes</td>
+      <td align="left">N/A</td>
+    </tr>
+  </tbody>
+</table>
+
+### `onChange`
+
+Optional callback when the text changes.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>(event: ChangeEvent&lt;HTMLTextAreaElement&gt;) =&gt; void</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>undefined</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+### `placeholder`
+
+Optional placeholder string for the inner textarea.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>string</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>undefined</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+### `resize`
+
+Controls whether users can resize the inner textarea.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>TextAreaResize</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>TextAreaResize.None</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+<Canvas of={TextAreaStories.Resize} />
+
+### `isDisabled`
+
+When true, the field applies reduced opacity and forwards disabled state to the inner textarea.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>boolean</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>false</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+<Canvas of={TextAreaStories.IsDisabled} />
+
+### `isReadOnly`
+
+When true, the inner textarea is not editable.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>boolean</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>false</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+<Canvas of={TextAreaStories.IsReadOnly} />
+
+### `isError`
+
+When true, the field shows an error state on the container border and marks the inner textarea as invalid.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>boolean</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>false</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+<Canvas of={TextAreaStories.IsError} />
+
+### `rows`
+
+Optional number of visible text rows for the inner textarea.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>number</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>undefined</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+<Canvas of={TextAreaStories.Rows} />
+
+### `inputProps`
+
+Additional props forwarded to the inner `textarea`. Do not pass `value`, `placeholder`, `isReadOnly`, `onFocus`, `onBlur`, `onChange`, `rows`, `cols`, or `required` here; use the TextArea-level props where applicable.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>TextAreaProps['inputProps']</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>undefined</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+### `inputRef`
+
+Ref to the inner `textarea`. The component `ref` points at the root `div`.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>Ref&lt;HTMLTextAreaElement&gt;</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>undefined</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+### `inputElement`
+
+Optional node that replaces the default textarea. `inputRef` is only forwarded when the default textarea is rendered.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>ReactNode</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>undefined</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+### `className`
+
+Use the `className` prop to add Tailwind CSS classes to the root container. These classes are merged with the component defaults using `twMerge`.
+
+<table>
+  <thead>
+    <tr>
+      <th align="left">TYPE</th>
+      <th align="left">REQUIRED</th>
+      <th align="left">DEFAULT</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td align="left">
+        <code>string</code>
+      </td>
+      <td align="left">No</td>
+      <td align="left">
+        <code>undefined</code>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+## Controls
+
+<Controls of={TextAreaStories.Default} />

diff --git a/packages/design-system-react/src/components/TextArea/TextArea.constants.ts b/packages/design-system-react/src/components/TextArea/TextArea.constants.ts
new file mode 100644
--- /dev/null
+++ b/packages/design-system-react/src/components/TextArea/TextArea.constants.ts
@@ -1,0 +1,34 @@
+/**
+ * TextAreaResize - resize
+ * Controls the resize behavior of the textarea element.
+ */
+export const TextAreaResize = {
+  /**
+   * The textarea cannot be resized.
+   */
+  None: 'none',
+  /**
+   * The textarea can be resized both horizontally and vertically.
+   */
+  Both: 'both',
+  /**
+   * The textarea can only be resized horizontally.
+   */
+  Horizontal: 'horizontal',
+  /**
+   * The textarea can only be resized vertically.
+   */
+  Vertical: 'vertical',
+} as const;
+export type TextAreaResize =
+  (typeof TextAreaResize)[keyof typeof TextAreaResize];
+
+/**
+ * Maps TextAreaResize values to Tailwind CSS resize classes.
+ */
+export const CLASSMAP_TEXTAREA_RESIZE: Record<TextAreaResize, string> = {
+  [TextAreaResize.None]: 'resize-none',
+  [TextAreaResize.Both]: 'resize',
+  [TextAreaResize.Horizontal]: 'resize-x',
+  [TextAreaResize.Vertical]: 'resize-y',
+};

diff --git a/packages/design-system-react/src/components/TextArea/TextArea.figma.tsx b/packages/design-system-react/src/components/TextArea/TextArea.figma.tsx
new file mode 100644
--- /dev/null
+++ b/packages/design-system-react/src/components/TextArea/TextArea.figma.tsx
@@ -1,0 +1,46 @@
+// import figma needs to remain as figma otherwise it breaks code connect
+// eslint-disable-next-line import-x/no-named-as-default
+import figma from '@figma/code-connect';
+import React from 'react';
+
+import { TextArea } from './TextArea';
+
+import { TextAreaResize } from '.';
+
+/**
+ * -- This file was auto-generated by Code Connect --
+ * React web implementation of TextArea component
+ * `props` includes a mapping from Figma properties and variants to
+ * suggested values. You should update this to match the props of your
+ * code component, and update the `example` function to return the
+ * code example you'd like to see in Figma
+ */
+
+figma.connect(
+  TextArea,
+  'https://www.figma.com/design/1D6tnzXqWgnUC3spaAOELN/%F0%9F%A6%8A-WIP--MMDS-Components?node-id=12091%3A104',
+  {
+    props: {
+      isDisabled: figma.boolean('isDisabled'),
+      isError: figma.boolean('isError'),
+      isReadOnly: figma.boolean('isReadOnly'),
+      resize: figma.enum('resize', {
+        Vertical: TextAreaResize.Vertical,
+        Horizontal: TextAreaResize.Horizontal,
+        Both: TextAreaResize.Both,
+        None: TextAreaResize.None,
+      }),
+      placeholder: figma.string('placeholder'),
+    },
+    example: ({ isDisabled, isError, isReadOnly, resize, placeholder }) => (
+      <TextArea
+        value=""
+        isDisabled={isDisabled}
+        isError={isError}
+        isReadOnly={isReadOnly}
+        resize={resize}
+        placeholder={placeholder ?? 'Enter text'}
+      />
+    ),
+  },
+);

diff --git a/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx b/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
new file mode 100644
--- /dev/null
+++ b/packages/design-system-react/src/components/TextArea/TextArea.stories.tsx
@@ -1,0 +1,155 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import React, { useEffect, useState } from 'react';
+
+import README from './README.mdx';
+import { TextArea } from './TextArea';
+import { TextAreaResize } from './TextArea.constants';
+import type { TextAreaProps } from './TextArea.types';
+
+function ControlledTextArea(props: TextAreaProps) {
+  const [value, setValue] = useState(props.value ?? '');
+  useEffect(() => {
+    setValue(props.value ?? '');
+  }, [props.value]);
+
+  return (
+    <TextArea
+      {...props}
+      value={value}
+      onChange={(event) => setValue(event.target.value)}
+    />
+  );
+}
+
+const meta: Meta<TextAreaProps> = {
+  title: 'React Components/TextArea',
+  component: TextArea,
+  parameters: {
+    docs: {
+      page: README,
+    },
+  },
+  argTypes: {
+    isError: {
+      control: 'boolean',
+      description: 'When true, applies error styling to the textarea',
+    },
+    isDisabled: {
+      control: 'boolean',
+      description: 'When true, disables the textarea',
+    },
+    isReadOnly: {
+      control: 'boolean',
+      description: 'When true, makes the textarea read-only',
+    },
+    value: {
+      control: 'text',
+    },
+    placeholder: {
+      control: 'text',
+    },
+    resize: {
+      control: 'select',
+      options: Object.values(TextAreaResize),
+      description: 'Controls the resize behavior of the textarea',
+    },
+    rows: {
+      control: 'number',
+    },
+    className: {
+      control: 'text',
+      description: 'Additional CSS classes merged with the component defaults',
+    },
+  },
+};
+
+export default meta;
+
+type Story = StoryObj<TextAreaProps>;
+
+export const Default: Story = {
+  args: {
+    value: '',
+    placeholder: 'Enter multiple lines...',
+  },
+  render: (args) => <ControlledTextArea {...args} />,
+};
+
+export const IsError: Story = {
+  render: () => (
+    <div className="flex flex-col gap-4">
+      <ControlledTextArea value="" placeholder="Default" />
+      <ControlledTextArea value="" placeholder="Error state" isError />
+    </div>
+  ),
+};
+
+export const IsDisabled: Story = {
+  render: () => (
+    <div className="flex flex-col gap-4">
+      <ControlledTextArea value="Editable" placeholder="Enabled" />
+      <ControlledTextArea
+        value="Not editable"
+        placeholder="Disabled"
+        isDisabled
+      />
+    </div>
+  ),
+};
+
+export const IsReadOnly: Story = {
+  render: () => (
+    <div className="flex flex-col gap-4">
+      <ControlledTextArea value="Editable" placeholder="Editable field" />
+      <ControlledTextArea
+        value="Cannot edit this value"
+        placeholder="Read-only"
+        isReadOnly
+      />
+    </div>
+  ),
+};
+
+export const Resize: Story = {
+  render: () => (
+    <div className="flex flex-col gap-4">
+      <ControlledTextArea
+        value="Cannot be resized"
+        placeholder="No resize (default)"
+        resize={TextAreaResize.None}
+      />
+      <ControlledTextArea
+        value="Resize vertical only"
+        placeholder="Vertical resize"
+        resize={TextAreaResize.Vertical}
+      />
+      <ControlledTextArea
+        value="Resize horizontal only"
+        placeholder="Horizontal resize"
+        resize={TextAreaResize.Horizontal}
+      />
+      <ControlledTextArea
+        value="Resize in both directions"
+        placeholder="Both directions"
+        resize={TextAreaResize.Both}
+      />
+    </div>
+  ),
+};
+
+export const Rows: Story = {
+  render: () => (
+    <div className="flex flex-col gap-4">
+      <ControlledTextArea
+        placeholder="3 rows"
+        rows={3}
+        value="3-row textarea"
+      />
+      <ControlledTextArea
+        placeholder="6 rows"
+        rows={6}
+        value="6-row textarea for longer content"
+      />
+    </div>
+  ),
+};

diff --git a/packages/design-system-react/src/components/TextArea/TextArea.test.tsx b/packages/design-system-react/src/components/TextArea/TextArea.test.tsx
new file mode 100644
--- /dev/null
+++ b/packages/design-system-react/src/components/TextArea/TextArea.test.tsx
@@ -1,0 +1,392 @@
+import { render, screen, fireEvent } from '@testing-library/react';
+import React, { createRef } from 'react';
+
+import { TextArea } from './TextArea';
+import { TextAreaResize } from './TextArea.constants';
+
+const ROOT_TEST_ID = 'text-area';
+const noop = () => undefined;
+
+describe('TextArea', () => {
+  describe('rendering', () => {
+    it('renders with default props', () => {
+      render(
+        <TextArea
+          data-testid={ROOT_TEST_ID}
+          onChange={noop}
+          placeholder="Enter text"
+          value=""
+        />,
+      );
+
+      expect(screen.getByTestId(ROOT_TEST_ID)).toBeInTheDocument();
+      expect(screen.getByRole('textbox')).toHaveAttribute(
+        'placeholder',
+        'Enter text',
+      );
+      expect(screen.getByRole('textbox')).not.toBeDisabled();
+      expect(screen.getByRole('textbox')).not.toHaveAttribute('readonly');
+      expect(screen.getByRole('textbox')).not.toHaveAttribute('aria-invalid');
+    });
+
+    it('renders placeholder and value on the inner textarea', () => {
+      render(
+        <TextArea placeholder="Enter value" value="hello" onChange={noop} />,
+      );
+
+      expect(screen.getByRole('textbox')).toHaveValue('hello');
+    });
+
+    it('renders a custom inputElement when provided', () => {
+      render(
+        <TextArea
+          value=""
+          inputElement={<textarea data-testid="custom-input" />}
+        />,
+      );
+
+      expect(screen.getByTestId('custom-input')).toBeInTheDocument();
+      expect(screen.queryByRole('textbox')).toBe(
+        screen.getByTestId('custom-input'),
+      );
+    });
+  });
+
+  describe('inputProps', () => {
+    it('forwards inputProps to the inner textarea', () => {
+      render(
+        <TextArea
+          value=""
+          onChange={noop}
+          placeholder="forwarded"
+          inputProps={{ 'aria-label': 'forwarded-label' }}
+        />,
+      );
+
+      expect(screen.getByLabelText('forwarded-label')).toBe(
+        screen.getByRole('textbox'),
+      );
+    });
+
+    it('merges inputProps.className with the inner textarea default classes', () => {
+      render(
+        <TextArea
+          value=""
+          onChange={noop}
+          inputProps={{ className: 'mt-2' }}
+        />,
+      );
+
+      expect(screen.getByRole('textbox')).toHaveClass('mt-2', 'flex-1');
+    });
+  });
+
+  describe('resize', () => {
+    const cases: { resize: TextAreaResize; resizeClass: string }[] = [
+      { resize: TextAreaResize.Vertical, resizeClass: 'resize-y' },
+      { resize: TextAreaResize.None, resizeClass: 'resize-none' },
+      { resize: TextAreaResize.Both, resizeClass: 'resize' },
+      { resize: TextAreaResize.Horizontal, resizeClass: 'resize-x' },
+    ];
+
+    cases.forEach(({ resize, resizeClass }) => {
+      it(`applies ${resizeClass} when resize is ${resize}`, () => {
+        render(<TextArea value="" onChange={noop} resize={resize} />);
+
+        expect(screen.getByRole('textbox')).toHaveClass(resizeClass);
+      });
+    });
+
+    it('does not allow resize by default', () => {
+      render(<TextArea value="" onChange={noop} />);
+
+      expect(screen.getByRole('textbox')).toHaveClass('resize-none');
+    });
+  });
+
+  describe('state', () => {
+    it('applies disabled state when isDisabled is true', () => {
+      render(
+        <TextArea
+          data-testid={ROOT_TEST_ID}
+          isDisabled
+          onChange={noop}
+          value=""
+        />,
+      );
+
+      expect(screen.getByTestId(ROOT_TEST_ID)).toHaveClass(
+        'cursor-not-allowed',
+        'opacity-50',
+      );
+      expect(screen.getByRole('textbox')).toBeDisabled();
+    });
+
+    it('marks the inner textarea readonly when isReadOnly is true', () => {
+      render(<TextArea isReadOnly value="Locked" onChange={noop} />);
+
+      expect(screen.getByRole('textbox')).toHaveAttribute('readonly');
+      expect(screen.getByRole('textbox')).toHaveValue('Locked');
+    });
+
+    it('applies error styling and aria-invalid when isError is true', () => {
+      render(
+        <TextArea
+          data-testid={ROOT_TEST_ID}
+          isError
+          onChange={noop}
+          value=""
+        />,
+      );
+
+      expect(screen.getByTestId(ROOT_TEST_ID)).toHaveClass(
+        'border-error-default',
+      );
+      expect(screen.getByRole('textbox')).toHaveAttribute(
+        'aria-invalid',
+        'true',
+      );
+    });
+
+    it('applies focused border on focus and restores muted border on blur', () => {
+      render(<TextArea data-testid={ROOT_TEST_ID} onChange={noop} value="" />);
+
+      const root = screen.getByTestId(ROOT_TEST_ID);
+      const textarea = screen.getByRole('textbox');
+
+      fireEvent.focus(textarea);
+      expect(root).toHaveClass('border-default');
+      expect(root).not.toHaveClass('border-muted');
+
+      fireEvent.blur(textarea);
+      expect(root).toHaveClass('border-muted');
+      expect(root).not.toHaveClass('border-default');
+    });
+
+    it('keeps error border when focused and isError is true', () => {
+      render(
+        <TextArea
+          data-testid={ROOT_TEST_ID}
+          isError
... diff truncated: showing 800 of 1331 lines

You can send follow-ups to the cloud agent here.

Comment thread packages/design-system-react/src/components/TextArea/TextArea.tsx Outdated
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

| 'readOnly'
| 'value'
> &
Omit<TextAreaPropsShared, 'inputElement' | 'textVariant'> & {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This React surface intentionally does not mirror the mobile escape hatch, because inputElement is not used by the web implementation and will be removed from RN in a follow-up. The migration docs should make that boundary explicit so extension consumers do not assume parity with mobile.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview


TextArea renders a controlled, multiline native `textarea` with design system styling. Use TextField when you need a single-line field or optional leading and trailing accessories.

```tsx

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs use the real controlled example here so the displayed code matches the public React API instead of a wrapper helper. That keeps Storybook's Show code aligned with what consumers actually need to import and use.


Use the `rows` prop to set the initial visible height of the TextArea, in lines. The component has a minimum height, so small `rows` values may not visibly change the rendered height.

TextArea renders at full container width. Use layout styles or `className` for width changes instead of the native `cols` attribute.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rows is the only sizing knob that matters for this React API because the component is rendered at full width, so cols would not change the layout in practice. The migration docs should call out that extension consumers should map layout width to container styling and rows to height.

value: '',
placeholder: 'Enter multiple lines...',
},
render: function Render(args) {

@georgewrmarshall georgewrmarshall Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The controlled story mirrors the exported API directly so Storybook's Show code reflects real usage instead of a helper abstraction. Duplicating the state here is intentional because the docs should teach the shipped component, not a wrapper pattern.

Image

TextArea,
'https://www.figma.com/design/1D6tnzXqWgnUC3spaAOELN/%F0%9F%A6%8A-MMDS-Components?node-id=12302%3A528',
{
props: {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the React-facing Figma properties are mapped here, which keeps the design file aligned with the web API boundary. Leaving inputElement and textVariant out makes it clear that mobile parity is being handled separately.

* TextAreaResize - resize
* Controls the resize behavior of the textarea element.
*/
export const TextAreaResize = {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resize stays local to the React package because RN does not have an equivalent native resize axis to share. Keeping it in constants for now keeps the web API explicit without implying a cross-platform contract we cannot actually support.

/>
),
},
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figma code connect aligned and working as expected

figma.code.connect720.mov

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview


**Prop forwarding convention:**

- When a component forwards the remaining props to its rendered element, name the destructured catch-all `...props` and spread `{...props}` onto the element.

@georgewrmarshall georgewrmarshall Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was noticing a few violations of this, including in this PR. Its a small nit but helps with consistency

When a component forwards the remaining element props, use ...props instead of ...rest so the public API and implementation naming stay consistent. That convention makes it easier to scan for pass-through props across the codebase.


### Platform-specific const objects

If a const object only exists for one platform's behavior or styling, keep it in that platform package near the component instead of moving it to `@metamask/design-system-shared` just to satisfy the barrel export.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Platform-specific const objects should stay local when only one platform needs them. Re-exporting them from the barrel is fine, but moving them into shared would imply a cross-platform contract that does not actually exist.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall marked this pull request as ready for review June 1, 2026 19:49
@georgewrmarshall georgewrmarshall requested a review from a team as a code owner June 1, 2026 19:49
@georgewrmarshall georgewrmarshall enabled auto-merge (squash) June 1, 2026 19:49
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

📖 Storybook Preview

@georgewrmarshall georgewrmarshall merged commit 67aa77b into main Jun 2, 2026
36 checks passed
@georgewrmarshall georgewrmarshall deleted the textarea branch June 2, 2026 23:27
@georgewrmarshall georgewrmarshall mentioned this pull request Jun 4, 2026
18 tasks
georgewrmarshall added a commit that referenced this pull request Jun 4, 2026
## Release 43.0.0

This release drops Node.js 18 support across the release line, adds
several new components, and includes a small set of breaking API changes
that are documented in the migration guides.

### 📦 Package Versions

- `@metamask/design-system-shared`: **0.21.0**
- `@metamask/design-system-react`: **0.25.0**
- `@metamask/design-system-react-native`: **0.28.0**
- `@metamask/design-tokens`: **8.5.0**
- `@metamask/design-system-tailwind-preset`: **0.9.0**
- `@metamask/design-system-twrnc-preset`: **0.5.0**

### 🔄 Shared Type Updates (0.21.0)

#### Added

- Added `ContentPropsShared` and `ContentVerticalAlignment` for React
Native list-style rows and related layout patterns
([#1192](#1192))

#### Changed

- **BREAKING:** Dropped Node.js 18 support for the release line;
consumers must run Node 20 or newer
([#1206](#1206))
- **BREAKING:** Updated `TextAreaPropsShared` to remove `inputElement`
so React Native `TextArea` can render the root `TextInput` directly
([#1205](#1205))

### 🌐 React Web Updates (0.25.0)

#### Added

- Added `Popover` for anchored overlays such as menus, tooltips, and
dialogs
([#1153](#1153))
- Added `TextArea` for controlled multiline text entry
([#1036](#1036))
- Added `TextFieldSearch` for controlled search-field flows on top of
`TextField`
([#1171](#1171))
- Added `FormTextField` for labeled form controls built from `Label`,
`TextField`, and `HelpText`
([#1197](#1197))

#### Changed

- **BREAKING:** Dropped Node.js 18 support for the release line;
consumers must run Node 20 or newer
([#1206](#1206))
- Updated avatar fallback handling so `AvatarToken`, `AvatarNetwork`,
and `AvatarFavicon` resolve consistently when the requested image is
unavailable
([#1212](#1212))

### 📱 React Native Updates (0.28.0)

#### Added

- Added `Content` for composing scrollable and padded content sections
on React Native screens; it is closely related to the upcoming
`ListItem` work
([#1192](#1192))

#### Changed

- **BREAKING:** Dropped Node.js 18 support for the release line;
consumers must run Node 20 or newer
([#1206](#1206))
- Added default padding and `isInteractive` support to `SectionHeader`
so section rows match the new mobile layout patterns
([#1210](#1210))
- **BREAKING:** Flattened `TextArea` so it renders the root `TextInput`
directly; pass `TextInput` props on `TextArea`, use the component `ref`
for the input, and stop relying on `inputProps` or `inputElement`
([#1205](#1205))
- Updated avatar fallback handling so `AvatarToken`, `AvatarNetwork`,
and `AvatarFavicon` resolve consistently when the requested image is
unavailable
([#1212](#1212))

### ⚠️ Breaking Changes

#### Node.js 18 support removed

**What Changed:**

- The release line now requires Node 20 or newer.
- This applies across the monorepo, including the shared package, web
package, React Native package, tokens, and both preset packages.

**Impact:**

- Consumers still on Node 18 must upgrade their runtime before
installing or developing against this release line.
- Node 18 is end-of-life, so this change aligns the repo with the
supported app runtimes.

#### React Native `TextArea` flattening

**What Changed:**

- `TextArea` now renders the root `TextInput` directly.
- `inputProps` and `inputElement` are removed.
- `inputRef` is replaced by the component `ref`.

**Migration:**

```tsx
// Before (0.27.0)
<TextArea
  inputProps={{ placeholder: 'Message' }}
  inputElement={<CustomInput />}
/>

// After (0.28.0)
<TextArea placeholder="Message" ref={inputRef} />
```

**Impact:**

- Affects React Native consumers using `TextArea`.
- Call sites that depended on the wrapper/input split need to be
updated.

See migration guides for complete instructions:

- [React Migration
Guide](./packages/design-system-react/MIGRATION.md#from-version-0220-to-0230)
- [React Native Migration
Guide](./packages/design-system-react-native/MIGRATION.md#from-version-0270-to-0280)

### ✅ Checklist

- [x] Changelogs updated with human-readable descriptions
- [x] Changelog validation passed (`yarn changelog:validate`)
- [x] Version bumps follow semantic versioning
- [x] design-system-shared: minor (0.20.0 → 0.21.0) - shared type
additions and breaking TextArea/shared runtime baseline
- [x] design-system-react: minor (0.24.0 → 0.25.0) - new components and
release-line update
- [x] design-system-react-native: minor (0.27.0 → 0.28.0) - new
component, SectionHeader update, and breaking TextArea change
- [x] Breaking changes documented with migration guidance
- [x] Migration guides updated with before/after examples (if breaking
changes)
- [x] PR references included in changelog entries

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs)
- [x] I've reviewed the [Release
Workflow](./.cursor/rules/release-workflow.md) cursor rule
- [ ] All tests pass (`yarn build && yarn test && yarn lint`)
- [x] Changelog validation passes (`yarn changelog:validate`)

## **Pre-merge reviewer checklist**

- [x] I've reviewed the [Reviewing Release
PRs](./docs/reviewing-release-prs.md) guide
- [x] Package versions follow semantic versioning
- [x] Changelog entries are consumer-facing (not commit message
regurgitation)
- [x] Breaking changes are documented in MIGRATION.md with examples
- [x] All unreleased changes are accounted for in changelogs

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Breaking React Native TextArea and Node 18 removal affect consumer
upgrades; most diff is release metadata with coordinated peer dependency
bumps.
> 
> **Overview**
> **Release 43.0.0** bumps the monorepo root to **43.0.0** and publishes
coordinated semver bumps across design-system packages, with
**yarn.lock** peer ranges updated for
`@metamask/design-system-tailwind-preset` **^0.9.0** and
`@metamask/design-system-twrnc-preset` **^0.5.0**.
> 
> Across the release line, changelogs record **Node.js 18 dropped**
(Node **20+** required). **@metamask/design-system-react** **0.25.0**
documents new **`Popover`**, **`TextArea`**, **`TextFieldSearch`**, and
**`FormTextField`**, plus avatar fallback fixes.
**@metamask/design-system-react-native** **0.28.0** adds **`Content`**,
updates **`SectionHeader`** (default padding, **`isInteractive`**), and
includes a **breaking** **`TextArea`** flattening (`inputProps` /
`inputElement` / `inputRef` removed; props and **`ref`** target the root
**`TextInput`**). **@metamask/design-system-shared** **0.21.0** adds
**`ContentPropsShared`** / **`ContentVerticalAlignment`** and removes
**`inputElement`** from shared **`TextArea`** props.
> 
> Migration guide edits in this diff: React Native **0.27.0 → 0.28.0**
**`TextArea`** guidance; React version heading **0.22.0 → 0.23.0** for
**`BannerBase`** (changelog-driven **0.25.0** items are not new
migration sections here).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
23b0cda. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants