Magic Native UI

Textarea

Multi-line text field sharing the input's designs and states.

Installation

Terminal
npx magic-native-ui@latest add textarea

Usage

The example running in the preview above. Imports use the default aliases from components.json.

textarea-demo.tsx
import { View } from 'react-native'

import { Label } from '@/components/ui/label'
import { Text } from '@/components/ui/text'
import { Textarea } from '@/components/ui/textarea'

export function TextareaDemo() {
  return (
    <View className="w-full gap-5">
      <View className="gap-1.5">
        <Label nativeID="bio">Bio</Label>
        <Textarea placeholder="Tell us about yourself" aria-labelledby="bio" />
      </View>

      <Textarea variant="filled" size="sm" placeholder="Filled, two lines high" />

      <View className="gap-1.5">
        <Textarea invalid defaultValue="Too short." size="sm" />
        <Text className="text-xs text-destructive">Use at least 40 characters.</Text>
      </View>
    </View>
  )
}

Component source

What the CLI copies into your project. Once it is there, the code is yours to edit.

registry/ui/textarea.tsx
import { TextInput } from 'react-native'
import { cva, type VariantProps } from 'class-variance-authority'

import { cn } from '@/registry/lib/utils'

const textareaVariants = cva('w-full px-3 py-2 text-base text-foreground', {
  variants: {
    variant: {
      default: 'rounded-md border border-input bg-background focus:border-ring',
      filled: 'rounded-md border border-transparent bg-muted focus:border-ring',
      ghost: 'rounded-md border border-transparent bg-transparent focus:bg-muted',
    },
    // A minimum rather than a fixed height, so `className` can still grow it.
    size: {
      sm: 'min-h-16',
      default: 'min-h-24',
      lg: 'min-h-36',
    },
    invalid: {
      true: 'border-destructive focus:border-destructive',
      false: '',
    },
  },
  defaultVariants: {
    variant: 'default',
    size: 'default',
    invalid: false,
  },
})

type TextareaProps = Omit<React.ComponentProps<typeof TextInput>, 'size'> &
  VariantProps<typeof textareaVariants>

function Textarea({
  className,
  variant,
  size,
  invalid,
  editable,
  readOnly,
  ...props
}: TextareaProps) {
  // `TextInput` has no `disabled` prop, so the `disabled:` variant never fires
  // on it — the dimming is applied from the props that do turn a field off.
  const off = editable === false || readOnly === true

  return (
    <TextInput
      multiline
      editable={editable}
      readOnly={readOnly}
      aria-invalid={invalid ?? undefined}
      // Android centres multiline text vertically without this.
      textAlignVertical="top"
      className={cn(textareaVariants({ variant, size, invalid }), off && 'opacity-50', className)}
      // Colour props are resolved from `accent-*` utilities, not `text-*`.
      placeholderTextColorClassName="accent-muted-foreground"
      {...props}
    />
  )
}

export { Textarea, textareaVariants }
export type { TextareaProps }