TextInput

A theme-aware text input primitive. Supports controlled and uncontrolled usage, multiline (textarea), error and disabled states, and all standard form attributes.

Architecture: TextInputRoot is a styled <div> that owns the visible border and focus ring. The native <input> (or <textarea>) sits inside it with a transparent background, so the root border appears as the field border. This makes it easy to add prefix/suffix adornments later.

Examples

Default

<TextInput resize={false} placeholder="Enter a value..." />

States

Default
Disabled
Disabled + value
Error
Error + value
<Flex flexDirection="column" gap={2} maxWidth="360px">
  {(
    [
      { label: 'Default', props: { placeholder: 'Default' } },
      { label: 'Disabled', props: { placeholder: 'Disabled', disabled: true } },
      {
        label: 'Disabled + value',
        props: { value: 'Cannot edit', disabled: true, onChange: () => {} },
      },
      { label: 'Error', props: { placeholder: 'Invalid field', error: true } },
      {
        label: 'Error + value',
        props: { value: 'bad@', error: true, onChange: () => {} },
      },
    ] as const
  ).map(({ label, props }) => (
    <Flex key={label} flexDirection="row" alignItems="center" gap={3}>
      <Typography variant="caption" color="secondary" width="8rem" flexShrink={0} m={0}>
        {label}
      </Typography>
      <TextInput color="primary" fullWidth {...props} />
    </Flex>
  ))}
</Flex>

Variants

NormalDisabledError
outlined
underline
text
<Flex flexDirection="column" gap={3} maxWidth="560px">
  <Flex flexDirection="row" gap={2} mb={1}>
    {(['Normal', 'Disabled', 'Error'] as const).map((h) => (
      <Typography key={h} variant="caption" color="secondary" flex={1} m={0}>
        {h}
      </Typography>
    ))}
  </Flex>
  {(['outlined', 'underline', 'text'] as const).map((variant) => (
    <Flex key={variant} flexDirection="column" gap={1}>
      <Typography variant="caption" color="secondary" m={0}>
        {variant}
      </Typography>
      <Flex flexDirection="row" gap={2} alignItems="center">
        <TextInput variant={variant} color="primary" placeholder="Normal" fullWidth />
        <TextInput
          variant={variant}
          color="primary"
          placeholder="Disabled"
          disabled
          fullWidth
        />
        <TextInput variant={variant} color="primary" placeholder="Error" error fullWidth />
      </Flex>
    </Flex>
  ))}
</Flex>

Colors

Default (click)DisabledError
primary
secondary
success
error
info
warning
<Flex flexDirection="column" gap={3} maxWidth="560px">
  <Flex flexDirection="row" gap={2} mb={1}>
    {(['Default (click)', 'Disabled', 'Error'] as const).map((h) => (
      <Typography key={h} variant="caption" color="secondary" flex={1} m={0}>
        {h}
      </Typography>
    ))}
  </Flex>
  {(['primary', 'secondary', 'success', 'error', 'info', 'warning'] as const).map((color) => (
    <Flex key={color} flexDirection="column" gap={1}>
      <Typography variant="caption" color="secondary" m={0}>
        {color}
      </Typography>
      <Flex flexDirection="row" gap={2}>
        <TextInput variant="outlined" color={color} placeholder="Click to focus" fullWidth />
        <TextInput variant="outlined" color={color} placeholder="Disabled" disabled fullWidth />
        <TextInput variant="outlined" color={color} placeholder="Error" error fullWidth />
      </Flex>
    </Flex>
  ))}
</Flex>

Multiline - Auto-grow

Grows with content - no fixed height
<Flex flexDirection="column" gap={1} maxWidth="480px">
  <Typography variant="caption" color="secondary" m={0}>
    Grows with content - no fixed height
  </Typography>
  <TextInput
    multiline
    resize
    fullWidth
    placeholder="Start typing... the field expands as you write."
  />
</Flex>

Multiline - Fixed rows

Fixed to 4 rows - scrolls when content overflows
<Flex flexDirection="column" gap={1} maxWidth="480px">
  <Typography variant="caption" color="secondary" m={0}>
    Fixed to 4 rows - scrolls when content overflows
  </Typography>
  <TextInput rows={4} fullWidth placeholder="Write something..." />
</Flex>

Full Width

const args: Partial<TextInputProps> = {}

<Flex width="100%" maxWidth="480px">
  <TextInput {...args} fullWidth placeholder="Full-width input..." />
</Flex>

Types

text
email
password
number
search
url
<Flex flexDirection="column" gap={2} maxWidth="360px">
  {(
    [
      { type: 'text', placeholder: 'Text' },
      { type: 'email', placeholder: '[email protected]' },
      { type: 'password', placeholder: 'Password' },
      { type: 'number', placeholder: '0' },
      { type: 'search', placeholder: 'Search...' },
      { type: 'url', placeholder: 'https://' },
    ] as const
  ).map(({ type, placeholder }) => (
    <Flex key={type} flexDirection="row" alignItems="center" gap={3}>
      <Typography variant="caption" color="secondary" width="5rem" flexShrink={0} m={0}>
        {type}
      </Typography>
      <TextInput type={type} placeholder={placeholder} fullWidth />
    </Flex>
  ))}
</Flex>

TextInput API reference