+ )}
+ >
+ )}
+
+ );
+}
diff --git a/packages/react-components/src/components/TextArea/index.ts b/packages/react-components/src/components/TextArea/index.ts
new file mode 100644
index 00000000..f254d030
--- /dev/null
+++ b/packages/react-components/src/components/TextArea/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./TextArea";
+export type { TextAreaProps } from "./TextArea";
diff --git a/packages/react-components/src/components/index.ts b/packages/react-components/src/components/index.ts
index a2408fab..6a0d7ace 100644
--- a/packages/react-components/src/components/index.ts
+++ b/packages/react-components/src/components/index.ts
@@ -8,5 +8,6 @@ export { default as SvgBcLogo } from "./SvgBcLogo";
export { default as Tag } from "./Tag";
export { default as TagGroup } from "./TagGroup";
export { default as TagList } from "./TagList";
+export { default as TextArea } from "./TextArea";
export { default as TextField } from "./TextField";
export { default as Tooltip, TooltipTrigger } from "./Tooltip";
diff --git a/packages/react-components/src/pages/TextArea/TextArea.tsx b/packages/react-components/src/pages/TextArea/TextArea.tsx
new file mode 100644
index 00000000..e0845d1d
--- /dev/null
+++ b/packages/react-components/src/pages/TextArea/TextArea.tsx
@@ -0,0 +1,33 @@
+import { useState } from "react";
+
+import { TextArea } from "@/components";
+
+export default function TextAreaPage() {
+ const [text, setText] = useState("initial text state");
+
+ function handleTextChange(text: string) {
+ console.log("new text value: ", text);
+
+ setText(text);
+ }
+
+ return (
+ <>
+
TextArea
+
+
+ text: {text}{" "}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/packages/react-components/src/pages/TextArea/index.ts b/packages/react-components/src/pages/TextArea/index.ts
new file mode 100644
index 00000000..3bd4155a
--- /dev/null
+++ b/packages/react-components/src/pages/TextArea/index.ts
@@ -0,0 +1,3 @@
+import TextAreaPage from "./TextArea";
+
+export default TextAreaPage;
diff --git a/packages/react-components/src/pages/index.ts b/packages/react-components/src/pages/index.ts
index d55e10a1..3f1dac80 100644
--- a/packages/react-components/src/pages/index.ts
+++ b/packages/react-components/src/pages/index.ts
@@ -1,7 +1,8 @@
import ButtonPage from "./Button";
import SelectPage from "./Select";
import TagGroupPage from "./TagGroup";
+import TextAreaPage from "./TextArea";
import TextFieldPage from "./TextField";
import TooltipPage from "./Tooltip";
-export { ButtonPage, SelectPage, TagGroupPage, TextFieldPage, TooltipPage };
+export { ButtonPage, SelectPage, TagGroupPage, TextAreaPage, TextFieldPage, TooltipPage };
diff --git a/packages/react-components/src/stories/TextArea.mdx b/packages/react-components/src/stories/TextArea.mdx
new file mode 100644
index 00000000..deb47c7a
--- /dev/null
+++ b/packages/react-components/src/stories/TextArea.mdx
@@ -0,0 +1,74 @@
+{/* TextArea.mdx */}
+
+import {
+ Canvas,
+ Controls,
+ Meta,
+ Primary,
+ Source,
+ Story,
+ Subtitle,
+} from "@storybook/blocks";
+
+import * as TextAreaStories from "./TextArea.stories";
+
+
+
+# Text Area
+
+
+ The text area component enables a user to enter multiple lines of plain text
+ into an interface or form.
+
+
+
+## Usage and resources
+
+Learn more about working with the text area component:
+
+- [Usage and best practice guidance](https://www2.gov.bc.ca/gov/content/digital/design-system/components/text-area)
+- [View the text field component in Figma](#)
+
+This component is based on [React Aria TextField](https://react-spectrum.adobe.com/react-aria/TextField.html#multi-line).
+
+### Validation
+
+Default and custom data validation support is built in, using the [React Aria FieldError subcomponent](https://react-spectrum.adobe.com/react-aria/forms.html#validation).
+
+### Placeholder
+
+As a general rule, the `placeholder` attribute should not be used on text fields. While `placeholder` is technically supported, the use of 'ghost text' has [serious accessibility and usability issues](https://developer.mozilla.org/en-US/docs/Web/CSS/::placeholder#accessibility_concerns).
+
+## Controls
+
+
+
+
+## Configuration
+
+### Maximum length
+
+The `maxLength` prop can be used to limit the amount of text a user can input. When `maxLength` is passed, a character counter renders below the input field:
+
+
+
+### States
+
+Use `isRequired` to make a text area mandatory. A secondary label is shown above the input field:
+
+
+
+Use `isDisabled` to disable a text area. A disabled text area cannot be focused or interacted with:
+
+
+
+Use `isReadOnly` to lock a text area to its current value. A read-only text area can be focused and copied, but cannot be edited:
+
+
+
+The `isInvalid` prop should be set programmatically when an input is invalid. It renders an error icon in the input field, changes the border colour and displays an `errorMessage` (using the FieldError subcomponent):
+
+
diff --git a/packages/react-components/src/stories/TextArea.stories.tsx b/packages/react-components/src/stories/TextArea.stories.tsx
new file mode 100644
index 00000000..3389bf13
--- /dev/null
+++ b/packages/react-components/src/stories/TextArea.stories.tsx
@@ -0,0 +1,102 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { TextArea } from "../components";
+
+const meta = {
+ title: "Components/TextArea/TextArea",
+ component: TextArea,
+ parameters: {
+ layout: "centered",
+ },
+ argTypes: {
+ value: {
+ control: { type: "text" },
+ description: "Input value",
+ },
+ label: {
+ control: { type: "text" },
+ description: "Text that appears above the input field",
+ },
+ isRequired: {
+ control: "boolean",
+ description: "Is this input required?",
+ },
+ isDisabled: {
+ control: "boolean",
+ description: "Is the input disabled?",
+ },
+ isInvalid: {
+ control: "boolean",
+ description: "Is the user's input valid?",
+ },
+ isReadOnly: {
+ control: "boolean",
+ description: "Locks the field to its current value",
+ },
+ minLength: {
+ control: "number",
+ description: "Minimum character length for this input",
+ },
+ maxLength: {
+ control: "number",
+ description: "Maximum character length for this input",
+ },
+ errorMessage: {
+ control: { type: "text" },
+ description: "Error message text that appears when `isInvalid` is true",
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const TextAreaTemplate: Story = {
+ args: {
+ label: "Label",
+ description: "Optional description or helper text",
+ },
+};
+
+export const RequiredTextArea: Story = {
+ args: {
+ label: "Label",
+ description: "Optional description or helper text",
+ isRequired: true,
+ },
+};
+
+export const TextAreaWithMaxLength: Story = {
+ args: {
+ label: "Label",
+ description: "This text area has a maximum length, and displays a counter.",
+ maxLength: 500,
+ },
+};
+
+export const DisabledTextArea: Story = {
+ args: {
+ label: "Label",
+ description: "This text area is disabled",
+ isDisabled: true,
+ },
+};
+
+export const ReadOnlyTextArea: Story = {
+ args: {
+ label: "Label",
+ value: "This text area is set to read-only.",
+ description: "It is focusable, and the value can be copied but not edited.",
+ isReadOnly: true,
+ },
+};
+
+export const TextAreaError: Story = {
+ args: {
+ label: "Label",
+ description: "Optional description or helper text",
+ isRequired: true,
+ isInvalid: true,
+ errorMessage: "Error message",
+ },
+};