Input
Single-line text field in four designs, three sizes, with invalid and disabled states.
Installation
Terminal
npx magic-native-ui@latest add inputUsage
The example running in the preview above. Imports use the default aliases from components.json.
input-demo.tsx
import { View } from 'react-native'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Text } from '@/components/ui/text'
export function InputDemo() {
return (
<View className="w-full gap-5">
<View className="gap-1.5">
<Label nativeID="email">Email</Label>
<Input
placeholder="you@example.com"
aria-labelledby="email"
keyboardType="email-address"
autoCapitalize="none"
/>
</View>
<View className="gap-2">
<Input variant="filled" placeholder="Filled" />
<Input variant="underline" placeholder="Underline" />
<Input variant="ghost" placeholder="Ghost" />
</View>
<View className="gap-2">
<Input size="sm" placeholder="Small" />
<Input size="lg" placeholder="Large" />
</View>
<View className="gap-1.5">
<Input invalid defaultValue="not-an-email" />
<Text className="text-xs text-destructive">Enter a valid email address.</Text>
</View>
<Input editable={false} placeholder="Disabled" />
</View>
)
}Component source
What the CLI copies into your project. Once it is there, the code is yours to edit.
registry/ui/input.tsx
import { TextInput } from 'react-native'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/registry/lib/utils'
const inputVariants = cva('w-full 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',
underline: 'rounded-none border-b border-input bg-transparent focus:border-ring',
ghost: 'rounded-md border border-transparent bg-transparent focus:bg-muted',
// For composition — `input-group` draws the border and background itself.
unstyled: '',
},
size: {
sm: 'h-9 px-2.5 py-1.5 text-sm',
default: 'h-10 px-3 py-2 text-base',
lg: 'h-12 px-4 py-3 text-lg',
},
invalid: {
true: 'border-destructive focus:border-destructive',
false: '',
},
},
compoundVariants: [
// A rule that sits under the text reads wrong with the field's inset.
{ variant: 'underline', class: 'px-0' },
{ variant: 'unstyled', class: 'px-0' },
],
defaultVariants: {
variant: 'default',
size: 'default',
invalid: false,
},
})
type InputProps = Omit<React.ComponentProps<typeof TextInput>, 'size'> &
VariantProps<typeof inputVariants>
function Input({ className, variant, size, invalid, editable, readOnly, ...props }: InputProps) {
// `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
editable={editable}
readOnly={readOnly}
aria-invalid={invalid ?? undefined}
className={cn(inputVariants({ variant, size, invalid }), off && 'opacity-50', className)}
// Colour props are resolved from `accent-*` utilities, not `text-*`.
placeholderTextColorClassName="accent-muted-foreground"
{...props}
/>
)
}
export { Input, inputVariants }
export type { InputProps }