A styled text input field.

Importing

The component can be imported via:

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

Usage

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

Events

The PlumaTextField 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 TextFields onChange only fires after a blur, for example (rather than while typing).

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

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

  return <TextField
    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 PlumaTextField component should always be associated with a label. The component accepts a label string prop, which will render the text above the input:

<TextField
  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>
<TextField
  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:

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

Errors

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

<TextField
  label="Email address"
  isInvalid={true}
/>

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

<TextField
  label="Email address"
  isInvalid={true}
  error="Please enter a valid email address"
/>

Size

The PlumaTextField component accepts a size prop, which can be set to small to render a smaller input. By default, the input renders as medium.

<TextField
  label="Email address"
  size="small"
/>
<TextField
  mt="200"
  label="Email address"
/>

Icons

An icon can be rendered in the input with the icon prop. This prop accepts the name of an icon from the Pluma Icons library.

<TextField
  label="Search"
  icon="search"
/>

Clearable input

The PlumaTextField accepts the isClearable prop, which renders a clear button when the input has a value. Additionally, an onClear function is required as another prop. The PlumaTextField component doesn't manage state changes when the button is clicked: it is up to the consumer to handle resetting the value.

If isClearable is true and onClear is not provided, the clear button will not render.

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

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

  return <TextField
    ariaLabel="Clearable input"
    value={value}
    onChange={(e) => setValue(e.target.value)}
    onInput={(e) => setValue(e.target.value)}
    isClearable={true}
    onClear={() => setValue('')}
  />
}

Password visibility

When type is set to password on PlumaTextField, a button will be rendered to change the type of the backing input between text and password, which allows the user to view the password as plain text or make it obfuscated.

This feature is enabled by default—if you want to prevent a user from toggling the visibility of a password type input, use the shouldShowPasswordVisibilityToggle prop on PlumaTextField.

Note that type needs to be passed as a prop rather than an attribute (important for Ember usage), otherwise the button will never render.

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

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

  return <>
    <TextField
      ariaLabel="Password visibility input"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      onInput={(e) => setValue(e.target.value)}
      type='password'
    />

    <TextField
      ariaLabel="Plain text passwords disabled input"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      onInput={(e) => setValue(e.target.value)}
      type='password'
      shouldShowPasswordVisibilityToggle={false}
    />
  </>
}

Password manager autofill

By default, PlumaTextField disables password manager autofill to prevent unwanted interference in non-credential fields. This behavior can be controlled with the shouldAllowAutofill prop.

When to use:

  • Keep shouldAllowAutofill={false} (default) for general input fields where password managers often interfere inappropriately (search fields, names, API keys, etc.)
  • Set shouldAllowAutofill={true} for credential fields where you want password managers to function (email, username, password fields)

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

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

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

    <TextField
      mt="200"
      label="Email"
      type="email"
      shouldAllowAutofill={true}
      description="Password managers enabled"
    />
  </>
}

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 { TextField, Icon } from '@customerio/pluma-components/react';
import { useState } from 'react';

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

  return <TextField
    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 { TextField, Link } from '@customerio/pluma-components/react';
import { useState } from 'react';

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

  return <TextField
    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 { TextField, Link } from '@customerio/pluma-components/react';
import { useState } from 'react';

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

  return <TextField
    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.

Focuses automatically when the component is rendered. Should be used with care and only sparingly, as there are some accessibility concerns. More information here

Additional classes to be applied to the top-level container. This is necessary as an argument in Ember, where we apply splattributes on the "input" itself, which means we can't pass a custom class to the containing element the classic way.

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.

The name of an icon (from Pluma Icons) to be rendered on the left side of the input.

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 input should render in an "active" styled state.

Whether the input element is clearable.

Whether the form field is disabled.

Whether the form field is in an invalid state.

Whether the input should show a loading spinner.

The label text to show with the form field.

The name attribute is used for HTML forms

In addition to isClearable, this function is required to render the clear button. This will be called when the clear button is clicked.

Placeholder text to show inside the field.

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.

Whether or not the password visibility button will be shown. Defaults to true.

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.

Default:md

The size of the input element. medium and small are deprecated, use md and sm instead.

The type of input that should be rendered. Defaults to text.

Pluma internal property to set a component name used for logging. Only use if building a custom component that wraps this one and need to make it more obvious where errors, for example, come from.

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.

Additional classes to be applied to the div that wraps the input element.

Whether the component should render with "legacy" styles.

Additional classes to be applied to the label node.

A component to render on the left side within the input wrapper.

A component to render on the right side within the input wrapper.

Whether to apply the center-baseline variant.

Ember only: a modifier to apply on the input's wrapper element.

React only: a ref to the input's wrapper element.