Version 2025-07 is the last API version to support React-based UI components. Later versions use web components, native UI elements with built-in accessibility, better performance, and consistent styling with Shopify's design system. Check out the migration guide to upgrade your extension.
TextArea
The TextArea component provides a multi-line text input for collecting longer-form content from merchants, such as descriptions, notes, or feedback. It supports configurable visible rows, length constraints, and autocomplete hints.
For single-line input, use TextField.
Supported targets
- admin.
abandoned-checkout-details. action. render - admin.
abandoned-checkout-details. block. render - admin.
catalog-details. action. render - admin.
catalog-details. block. render - admin.
collection-details. action. render - admin.
collection-details. block. render - admin.
collection-index. action. render - admin.
company-details. action. render - admin.
company-details. block. render - admin.
company-location-details. block. render - admin.
customer-details. action. render - admin.
customer-details. block. render - admin.
customer-index. action. render - admin.
customer-index. selection-action. render - admin.
customer-segment-details. action. render - admin.
discount-details. action. render - admin.
discount-details. function-settings. render - admin.
discount-index. action. render - admin.
draft-order-details. action. render - admin.
draft-order-details. block. render - admin.
draft-order-index. action. render - admin.
draft-order-index. selection-action. render - admin.
gift-card-details. action. render - admin.
gift-card-details. block. render - admin.
order-details. action. render - admin.
order-details. block. render - admin.
order-details. print-action. render - admin.
order-fulfilled-card. action. render - admin.
order-index. action. render - admin.
order-index. selection-action. render - admin.
order-index. selection-print-action. render - admin.
product-details. action. render - admin.
product-details. block. render - admin.
product-details. configuration. render - admin.
product-details. print-action. render - admin.
product-details. reorder. render - admin.
product-index. action. render - admin.
product-index. selection-action. render - admin.
product-index. selection-print-action. render - admin.
product-purchase-option. action. render - admin.
product-variant-details. action. render - admin.
product-variant-details. block. render - admin.
product-variant-details. configuration. render - admin.
product-variant-purchase-option. action. render - admin.
settings. order-routing-rule. render - admin.
settings. validation. render
Supported targets
- admin.
abandoned-checkout-details. action. render - admin.
abandoned-checkout-details. block. render - admin.
catalog-details. action. render - admin.
catalog-details. block. render - admin.
collection-details. action. render - admin.
collection-details. block. render - admin.
collection-index. action. render - admin.
company-details. action. render - admin.
company-details. block. render - admin.
company-location-details. block. render - admin.
customer-details. action. render - admin.
customer-details. block. render - admin.
customer-index. action. render - admin.
customer-index. selection-action. render - admin.
customer-segment-details. action. render - admin.
discount-details. action. render - admin.
discount-details. function-settings. render - admin.
discount-index. action. render - admin.
draft-order-details. action. render - admin.
draft-order-details. block. render - admin.
draft-order-index. action. render - admin.
draft-order-index. selection-action. render - admin.
gift-card-details. action. render - admin.
gift-card-details. block. render - admin.
order-details. action. render - admin.
order-details. block. render - admin.
order-details. print-action. render - admin.
order-fulfilled-card. action. render - admin.
order-index. action. render - admin.
order-index. selection-action. render - admin.
order-index. selection-print-action. render - admin.
product-details. action. render - admin.
product-details. block. render - admin.
product-details. configuration. render - admin.
product-details. print-action. render - admin.
product-details. reorder. render - admin.
product-index. action. render - admin.
product-index. selection-action. render - admin.
product-index. selection-print-action. render - admin.
product-purchase-option. action. render - admin.
product-variant-details. action. render - admin.
product-variant-details. block. render - admin.
product-variant-details. configuration. render - admin.
product-variant-purchase-option. action. render - admin.
settings. order-routing-rule. render - admin.
settings. validation. render
Anchor to PropertiesProperties
Props for the TextArea component, a multi-line text input for entering longer-form content such as descriptions, comments, or notes. It extends standard input props with min/max length constraints and autocomplete support.
- Anchor to labellabellabelstringstringrequiredrequired
The text content to display as the field's label. This label is always required for accessibility as it tells users what information the field expects. The label is rendered visually above the field.
- Anchor to autocompleteautocompleteautocomplete| AutocompleteField | `${AutocompleteSection} ${AutocompleteField}` | `${AutocompleteGroup} ${AutocompleteField}` | `${AutocompleteSection} ${AutocompleteGroup} ${AutocompleteField}` | boolean| AutocompleteField | `${AutocompleteSection} ${AutocompleteField}` | `${AutocompleteGroup} ${AutocompleteField}` | `${AutocompleteSection} ${AutocompleteGroup} ${AutocompleteField}` | boolean
A hint to the browser about the expected content of the field, used to offer autofill suggestions.
true: The field supports autofill, but no specific content type is specified.false: The field contains sensitive or ephemeral data that should not be autofilled, such as one-time codes.- An
token (such as'email'or'street-address'): Tells the browser exactly what data to suggest for this field.
Learn more about the supported autocomplete values.
- Anchor to defaultValuedefaultValuedefaultValuestring | string[]string | string[]
The initial value of the field when it isn't controlled by state. Use this instead of
valuewhen you don't need to manage the field's state yourself. The component tracks its own value internally and reports changes through.- Anchor to disableddisableddisabledbooleanbooleanDefault: falseDefault: false
Whether the field is disabled. When
true, the field can't be edited by the user, won't receive focus, and won't be submitted with the form. Use this for fields that aren't relevant in the current context.- Anchor to errorerrorerrorstringstring
An error message to display below the field. When set, the field receives a specific stylistic treatment (typically a red border) to communicate problems that have to be resolved immediately. The string value is displayed as the error message.
Pass
undefinedor omit this prop to clear the error state.- Anchor to idididstringstring
A unique identifier for the field.
- Anchor to maxLengthmaxLengthmaxLengthnumbernumber
The maximum number of characters the user can enter. If the current value exceeds this limit, then the field will be in an error state. This doesn't prevent the user from typing beyond the limit. Use the
errorprop to communicate the constraint.- Anchor to minLengthminLengthminLengthnumbernumber
The minimum number of characters required for a valid input. If the current value is shorter than this limit, then the field will be in a validation error state. This doesn't prevent the user from submitting a shorter value. Use the
errorprop to communicate the constraint.- Anchor to namenamenamestringstring
An identifier for the field that is unique within the nearest containing Form component.
- Anchor to onBluronBluronBlur() => void() => void
A callback fired when the field loses focus. This is useful for triggering validation after the user finishes interacting with the field, or for tracking which fields have been "touched" in a form.
- Anchor to onChangeonChangeonChange(value: string) => void(value: string) => void
A callback that fires when the user finishes editing the field, typically on blur. Only fires if the value changed. Update your state in this callback and pass the new value back through the
valueprop.This doesn't fire on every keystroke. Use
for real-time responses like clearing validation errors as the user types. Don't useto controlvaluebecause that can cause issues on lower-powered devices due to asynchronous rendering.- Anchor to onFocusonFocusonFocus() => void() => void
A callback fired when the field receives focus. This is useful for clearing errors, showing helper text, or tracking user interaction with form fields.
- Anchor to onInputonInputonInput(value: string) => void(value: string) => void
A callback that fires on every change the user makes in the field, including each keystroke. The callback receives the current value.
Use
for immediate responses like clearing validation errors as the user types. Don't use it to control the field'svalueprop. Usefor that instead.- Anchor to placeholderplaceholderplaceholderstringstring
A short hint displayed inside the field when it's empty. Use placeholder text to show an example of the expected value (such as "100" or "Search by name"). Don't use placeholder text as a substitute for the
labelas it disappears after the user starts typing.- Anchor to readOnlyreadOnlyreadOnlybooleanbooleanDefault: falseDefault: false
Whether the field is read-only. Unlike
disabled, a read-only field can still receive focus and its value is included when the form is submitted. Use this when the value should be visible and selectable but not editable, such as a computed total.- Anchor to requiredrequiredrequiredbooleanboolean
Whether the field needs a value. This requirement adds semantic value to the field, but it won't cause an error to appear automatically. If you want to present an error when this field is empty, you can do so with the
errorprop.- Anchor to rowsrowsrowsnumbernumberDefault: 2Default: 2
The number of visible text lines to display. This determines the initial height of the text area. Users can still enter more text than the visible lines allow.
- Anchor to valuevaluevalueTT
The current value for the field. If omitted, then the field will be empty. You should update this value in response to the
callback.
AutocompleteSection
A section prefix that scopes autofill data to a specific area of the page. Use this when the same page contains multiple groups of fields that share the same autocomplete tokens, such as two separate shipping address forms. The value must follow the pattern `section-${name}`, for example `"section-shipping-1"`.
`section-${string}`AutocompleteGroup
The contact information group the autocomplete data should be sourced from. - `shipping`: Autofill with the user's shipping address information. - `billing`: Autofill with the user's billing address information.
'shipping' | 'billing'Anchor to ExamplesExamples
Anchor to Collect internal product notesCollect internal product notes
Write internal product notes and save them from an action modal. This example uses TextArea with a rows prop to set the input height, and a Button that saves the notes.
Collect internal product notes
 that saves the notes.](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/templated-apis-screenshots/admin-extensions/2025-07/textarea-default-CJ8VS6Bc.png)
Collect internal product notes
React
import {useState} from 'react';
import {reactExtension, useApi, TextArea, Button, BlockStack} from '@shopify/ui-extensions-react/admin';
function App() {
const {data, close} = useApi('admin.product-details.action.render');
const productId = data.selected[0]?.id;
const [notes, setNotes] = useState('');
return (
<BlockStack>
<TextArea
label="Internal notes"
name="internalNotes"
rows={4}
value={notes}
onChange={setNotes}
/>
<Button
variant="primary"
onPress={async () => {
await fetch('/api/products/notes', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, notes}),
});
close();
}}
>
Save notes
</Button>
</BlockStack>
);
}
export default reactExtension(
'admin.product-details.action.render',
() => <App />,
);TS
import {extension, TextArea, Button, BlockStack} from '@shopify/ui-extensions/admin';
export default extension(
'admin.product-details.action.render',
(root, api) => {
const {data, close} = api;
const productId = data.selected[0]?.id;
let notes = '';
const stack = root.createComponent(BlockStack);
const field = root.createComponent(TextArea, {
label: 'Internal notes',
name: 'internalNotes',
rows: 4,
onChange: (value) => {
notes = value;
},
});
const saveButton = root.createComponent(
Button,
{
variant: 'primary',
onPress: async () => {
await fetch('/api/products/notes', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, notes}),
});
close();
},
},
'Save notes',
);
stack.appendChild(field);
stack.appendChild(saveButton);
root.appendChild(stack);
},
);Anchor to Validate description lengthValidate description length
Enforce a character limit using maxLength with a live counter and inline error feedback. This example tracks the description length on each keystroke and shows an error when the limit is exceeded, helping merchants keep descriptions within the allowed length.
Validate description length
React
import {useState} from 'react';
import {reactExtension, useApi, TextArea, Button, BlockStack, Text} from '@shopify/ui-extensions-react/admin';
function App() {
const {data, close} = useApi('admin.product-details.action.render');
const productId = data.selected[0]?.id;
const [description, setDescription] = useState('');
const [error, setError] = useState(undefined);
return (
<BlockStack>
<TextArea
label="Product description for external catalog"
name="catalogDescription"
rows={5}
maxLength={500}
value={description}
error={error}
onChange={(value) => {
setDescription(value);
setError(
value.length > 500
? 'Description cannot exceed 500 characters'
: undefined,
);
}}
/>
<Text>{description.length} / 500 characters</Text>
<Button
variant="primary"
onPress={async () => {
if (description.length <= 500) {
await fetch('/api/products/catalog-description', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, description}),
});
close();
}
}}
>
Save description
</Button>
</BlockStack>
);
}
export default reactExtension(
'admin.product-details.action.render',
() => <App />,
);TS
import {extension, TextArea, Button, BlockStack, Text} from '@shopify/ui-extensions/admin';
export default extension(
'admin.product-details.action.render',
(root, api) => {
const {data, close} = api;
const productId = data.selected[0]?.id;
let description = '';
const stack = root.createComponent(BlockStack);
const charCount = root.createComponent(Text, {}, '0 / 500 characters');
const field = root.createComponent(TextArea, {
label: 'Product description for external catalog',
name: 'catalogDescription',
rows: 5,
maxLength: 500,
onChange: (value) => {
description = value;
charCount.replaceChildren(`${value.length} / 500 characters`);
if (value.length > 500) {
field.updateProps({error: 'Description cannot exceed 500 characters'});
} else {
field.updateProps({error: undefined});
}
},
});
const saveButton = root.createComponent(
Button,
{
variant: 'primary',
onPress: async () => {
if (description.length <= 500) {
await fetch('/api/products/catalog-description', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, description}),
});
close();
}
},
},
'Save description',
);
stack.appendChild(field);
stack.appendChild(charCount);
stack.appendChild(saveButton);
root.appendChild(stack);
},
);Anchor to Display existing metafield contentDisplay existing metafield content
Pre-fill a text area with existing metafield data from the GraphQL Admin API and display it as read-only. This example queries the product's shipping instructions metafield and renders the value in a readOnly text area for reference.
Display existing metafield content
React
import {useState, useEffect} from 'react';
import {reactExtension, useApi, TextArea, BlockStack, Text} from '@shopify/ui-extensions-react/admin';
function App() {
const {data, query} = useApi('admin.product-details.block.render');
const productId = data.selected[0]?.id;
const [instructions, setInstructions] = useState('');
useEffect(() => {
query(
`query Product($id: ID!) {
product(id: $id) {
metafield(namespace: "custom", key: "shipping_instructions") { value }
}
}`,
{variables: {id: productId}},
).then((result) => {
setInstructions(result?.data?.product?.metafield?.value || '');
});
}, [productId, query]);
return (
<BlockStack>
<Text fontWeight="bold">Shipping instructions</Text>
<TextArea
label="Special handling instructions"
name="shippingInstructions"
rows={3}
value={instructions}
readOnly
/>
</BlockStack>
);
}
export default reactExtension(
'admin.product-details.block.render',
() => <App />,
);TS
import {extension, TextArea, BlockStack, Text} from '@shopify/ui-extensions/admin';
export default extension(
'admin.product-details.block.render',
async (root, api) => {
const {data, query} = api;
const productId = data.selected[0]?.id;
const stack = root.createComponent(BlockStack);
const heading = root.createComponent(
Text,
{fontWeight: 'bold'},
'Shipping instructions',
);
const result = await query(
`query Product($id: ID!) {
product(id: $id) {
metafield(namespace: "custom", key: "shipping_instructions") { value }
}
}`,
{variables: {id: productId}},
);
const existing = result?.data?.product?.metafield?.value || '';
const field = root.createComponent(TextArea, {
label: 'Special handling instructions',
name: 'shippingInstructions',
rows: 3,
value: existing,
readOnly: true,
});
stack.appendChild(heading);
stack.appendChild(field);
root.appendChild(stack);
},
);Anchor to Best practicesBest practices
- Write a clear label: The required
labelprop tells merchants what content to enter. Use specific, descriptive labels like "Description" or "Return instructions". - Use for multi-line content only: If the expected input is a single line (like a name or title), use TextField instead. TextArea should be reserved for content that benefits from multiple lines.
Anchor to LimitationsLimitations
- The
rowsprop sets the initial visible height but doesn't prevent the textarea from growing. Content that exceeds the visible rows will scroll. - TextArea doesn't support rich text formatting. All input is treated as plain text. For rich text editing, a custom solution is needed.
- The
minLengthandmaxLengthprops validate length but don't display a character counter. Implement a counter separately if merchants need to see remaining characters. - TextArea doesn't support the
suffixprop that TextField offers. There is no built-in way to add a decoration or unit indicator.