A textarea component for user input.

Importing

The component can be imported via:

import { TextArea } from '@customerio/pluma-components/react';

Usage

<TextArea value="Foo" ariaLabel="React demo input" />

Events

The PlumaTextArea component accepts the following functions, corresponding to native events on the input element:

  • onChange
  • onInput
  • onFocus
  • onBlur

Note: The onChange function is not the idiomatic React onChange callback (which acts more like the native input event). To keep the component API consistent between frameworks, we expose event handlers that correspond to the browser native event. This means that TextAreas onChange only fires after a blur, for example (rather than while typing).

import { TextArea } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [value, setValue] = useState('Foo');

	return <TextArea
		ariaLabel="Event demo input"
		value={value}
		onChange={(e) => {
			console.log('onChange fired');
			setValue(e.target.value);
		}}
		onInput={(e) => {
			console.log('onInput fired');
			setValue(e.target.value);
		}}
		onFocus={() => console.log('onFocus fired')}
		onBlur={() => console.log('onBlur fired')}
	/>
}

Labels and descriptions

A PlumaTextArea component should always be associated with a label. The component accepts a label string prop, which will render the text above the input:

<TextArea
	label="First name"
/>

Alternatively, the ariaLabelledby prop can be used to associate the input with a label element elsewhere in the DOM. It is also recommended to add a for attribute on the custom label, along with an id attribute on the input, to ensure the label is properly associated with the input.

<Box
	as="label"
	id="my_label_id"
	htmlFor="my_input"
>
	First name
</Box>
<TextArea
	id="my_input"
	ariaLabelledby="my_label_id"
/>

Finally, the ariaLabel prop can be used if no other element is suitable for a label.

Additionally, a hint text can be added below the input with the description prop:

<TextArea
	label="Username"
	description="Your username can be changed at any time"
/>

Errors

The PlumaTextArea component accepts an isInvalid prop, which will render the input in an error state:

<TextArea
	label="Comment"
	isInvalid={true}
/>

It is also highly recommended to provide an error message when the input is invalid. This will render below the input:

<TextArea
	label="Comment"
	isInvalid={true}
	error="Please enter a valid comment"
/>

Form submission behavior

By default, pressing Enter in a textarea creates a new line. However, with the shouldSubmitOnEnter prop set to true, pressing Enter will submit the form instead of creating a new line. Users can still add a new line by pressing Shift+Enter.

<Form onSubmit={(e) => {
	  e.preventDefault();
	  alert('Form submitted!');
	}}
>
	<FormLayout>
		<TextArea
			label="Comment (press Enter to submit)"
			shouldSubmitOnEnter={true}
		/>
		<ButtonGroup>
			<Button type="submit">Submit</Button>
		</ButtonGroup>
  	</FormLayout>
</Form>

Password manager autofill

By default, PlumaTextArea disables password manager autofill to prevent unwanted interference. This behavior can be controlled with the shouldAllowAutofill prop.

When to use:

  • Keep shouldAllowAutofill={false} (default) for general text areas where password managers often interfere inappropriately
  • Set shouldAllowAutofill={true} only if you specifically want password managers to function in this textarea (rare for textareas)

When shouldAllowAutofill is false, the component adds attributes to disable common password managers (1Password, LastPass, Bitwarden, Dashlane) and sets autocomplete="off".

import { TextArea } from '@customerio/pluma-components/react';

export default function Example() {
  return <>
    <TextArea
      label="API Response"
      description="Password managers disabled (default)"
    />

    <TextArea
      mt="200"
      label="Notes"
      shouldAllowAutofill={true}
      description="Password managers enabled"
    />
  </>
}

Auto-growing text area

The PlumaTextArea component supports auto-growing height based on content with the minRows and maxRows props.

<TextArea
	label="Auto-growing (2-6 rows)"
	minRows={2}
	maxRows={6}
	placeholder="Type multiple lines to see auto-grow in action..."
/>
  • minRows sets the minimum number of visible rows when empty
  • maxRows sets the maximum number of rows before scrolling
  • When both are set, the textarea automatically grows and shrinks based on content
  • When auto-growing is enabled, manual resizing is disabled

Resizing and width

By default, PlumaTextArea renders at full width and can be resized vertically (but not horizontally).

Note: In the code snippet examples above, the component is placed in a 300px wide container. Below is a full width example without the constraint:

<TextArea
	label="Full width"
/>

When rendered at full width, it cannot be resized horizontally (it always stretches to fill the container).

To allow horizontal resizing, isFullWidth must be set to false. Additionally, resize should be set to horizontal or both:

<TextArea
	label="Resizable"
  	isFullWidth={false}
  	resize="both"
/>

Customizing labels

If a string label is not sufficient, a component can be used to render the label.

In React, the label prop accepts any ReactNode.

import { TextArea, Icon } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [value, setValue] = useState('');

  return <TextArea
    label={<>Custom label <Icon name="campaigns" isInline={true} size="sm" /></>}
    value={value}
    onInput={(e) => {
      setValue(e.target.value);
    }}
  />
}

Customizing descriptions

If a string description is not sufficient, a component can be used to render the description.

In React, the description prop accepts any ReactNode.

import { TextArea, Link } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [value, setValue] = useState('');

  return <TextArea
    ariaLabel="Custom description input"
    description={<>Custom description, <Link href="https://docs.customer.io" isExternal={true} variant="secondary">see docs for details</Link></>}
    value={value}
    onInput={(e) => {
      setValue(e.target.value);
    }}
  />
}

Customizing errors

If a string error is not sufficient, a component can be used to render the error.

In React, the error prop accepts any ReactNode.

import { TextArea, Link } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
  const [value, setValue] = useState('');

  return <TextArea
    ariaLabel="Custom error input"
    error={<>Custom error, <Link href="https://docs.customer.io" isExternal={true} variant="secondary">see docs for help</Link></>}
    value={value}
    onInput={(e) => {
      setValue(e.target.value);
    }}
  />
}

API

Element (or elements) that describe the form field. Also used for error messages, as aria-errormessage isn't supported in all assistive technologies: https://a11ysupport.io/tech/aria/aria-errormessage_attribute

String value that labels the form field.

Element (or elements) that label the form field.

Visible width of the textarea.

A description for the field, providing additional context or hints.

An error message associated with the form field. Boolean: Ember-only. If the error named block is used, this prop can be set to true to indicate an error is present, which will make the error block render.

An optional id to be assigned to the input element. Also used to generate ids for labels and error messages. If one isn't provided, it will be generated.

Whether the form field is disabled.

Default:true

Whether the textarea should stretch to fill the width of its container. Set this to false if the textarea needs to be resized horizontally (via resize both or horizontal).

Whether the form field is in an invalid state.

The label text to show with the form field.

Maximum number of rows when auto-growing.

Minimum number of rows when auto-growing.

The name of the textarea.

The keydown event handler for the textarea element.

Placeholder text to show inside the field.

Default:vertical

Whether the textarea is resizable.

Number of visible text lines.

Whether to allow browser autofill and password managers. When false (default), adds attributes to disable password managers (1Password, LastPass, Bitwarden, Dashlane). Set to true for fields where autofill is desired (e.g., email, password, address fields).

Whether the generated description id should be included in the input's aria-describedby attribute. Disable this when a label`` tag surrounds the input as well as it's description text, for example in an OptionCard` component.

Whether the generated label id should be included in the input's aria-labelledby attribute. Disable this when the label tag surrounds the input as well as it's label text, for example in an OptionCard component.

Default:false

When true, pressing Enter key will submit the form instead of creating a new line.

By default, form elements will throw an error if no label or aria-label is provided. Disable this to suppress the error, for example when building a custom form element, and you want to handle labeling yourself.

Additional classes to be applied to the description element.

Additional classes to be applied to the error element.

Additional classes to be applied to the field node.

Additional classes to be applied to the footer element.

Ember-only: this is used internally to detect whether a label named block is present, and to ignore empty label props if so.

Additional classes to be applied to the input node.

Sets the size attribute on the input element. This controls the visible width of the input in characters.

Whether the component should render with "legacy" styles.

Additional classes to be applied to the label node.

Additional class to apply on the textarea element wrapper.

Whether to apply the center-baseline variant.