Input
Text input with variants and states.
Web preview — components render natively on iOS & Android
import { Input } from "@/components/ui/input";
export function MyScreen() {
return (
<Input placeholder="Enter your email..." />
);
}Installation#
npx @aniui/cli add inputUsage#
app/index.tsx
import { Input } from "@/components/ui/input";
export function MyScreen() {
return (
<Input placeholder="Enter your email..." />
);
}Password Toggle#
Show/hide password with a trailing eye icon.
Web preview — components render natively on iOS & Android
import { Input } from "@/components/ui/input";
import { Pressable } from "react-native";
import { Eye, EyeOff } from "lucide-react-native";
const [visible, setVisible] = useState(false);
<Input
secureTextEntry={!visible}
trailingIcon={
<Pressable onPress={() => setVisible(!visible)}>
{visible ? <EyeOff size={18} color="#71717a" /> : <Eye size={18} color="#71717a" />}
</Pressable>
}
placeholder="Password"
/>Clear Button#
Add a pressable clear icon to reset the input value.
Web preview — components render natively on iOS & Android
import { Input } from "@/components/ui/input";
import { Pressable } from "react-native";
import { XCircle } from "lucide-react-native";
<Input
trailingIcon={
<Pressable onPress={() => setValue("")}>
<XCircle size={18} color="#71717a" />
</Pressable>
}
placeholder="Type something..."
value={value}
/>Leading Icon#
Add an icon before the input text.
Web preview — components render natively on iOS & Android
import { Input } from "@/components/ui/input";
import { Search } from "lucide-react-native";
<Input
leadingIcon={<Search size={18} color="#71717a" />}
placeholder="Search..."
/>Variants#
Web preview — components render natively on iOS & Android
<Input variant="default" placeholder="Default input" />
<Input variant="ghost" placeholder="Ghost input" />Sizes#
Web preview — components render natively on iOS & Android
<Input size="sm" placeholder="Small" />
<Input size="md" placeholder="Medium" />
<Input size="lg" placeholder="Large" />Refs#
The ref is forwarded to the underlying React Native TextInput, so you can call focus(), blur(), and clear() imperatively. Same applies to Textarea, PasswordInput, SearchBar, MaskedInput, PhoneInput, and NumberInput.
app/index.tsx
import { useRef } from "react";
import { TextInput } from "react-native";
import { Input } from "@/components/ui/input";
export function MyScreen() {
const inputRef = useRef<TextInput>(null);
return (
<Input
ref={inputRef}
placeholder="Enter your email..."
onSubmitEditing={() => inputRef.current?.blur()}
/>
);
}
// Anywhere — e.g. after a button press:
// inputRef.current?.focus();
// inputRef.current?.blur();
// inputRef.current?.clear();Props#
PropTypeDefault
variant"default" | "ghost""default"size"sm" | "md" | "lg""md"leadingIconReact.ReactNode—trailingIconReact.ReactNode—classNamestring—Also accepts all TextInput props from React Native, and forwards ref to the underlying TextInput.
Accessibility#
accessibilityRoleis set on the underlyingTextInput.- Placeholder text color uses the theme variable for consistent contrast.
Source#
components/ui/input.tsx
import React from "react";
import { View, TextInput, useColorScheme } from "react-native";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
// Padding lives on the wrapping View, never on the raw TextInput — a TextInput
// doesn't honor `px-*` reliably, so keeping padding on the View gives a
// consistent inset with or without icons, under both NativeWind and Uniwind.
const inputVariants = cva(
"flex-row items-center rounded-md border py-2",
{
variants: {
variant: {
default: "border-input bg-background",
ghost: "border-transparent bg-transparent",
},
size: {
sm: "min-h-9 px-3",
md: "min-h-12 px-4",
lg: "min-h-14 px-5",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
}
);
const fontSizes = { sm: 14, md: 16, lg: 18 } as const;
export interface InputProps
extends React.ComponentPropsWithoutRef<typeof TextInput>,
VariantProps<typeof inputVariants> {
className?: string;
leadingIcon?: React.ReactNode;
trailingIcon?: React.ReactNode;
}
export const Input = React.forwardRef<
React.ElementRef<typeof TextInput>,
InputProps
>(function Input(
{ variant, size, className, leadingIcon, trailingIcon, style, ...props },
ref
) {
const dark = useColorScheme() === "dark";
const caret = dark ? "#fafafa" : "#18181b";
const resolvedSize = size ?? "md";
return (
<View className={cn(inputVariants({ variant, size }), className)}>
{leadingIcon && <View className="me-2">{leadingIcon}</View>}
<TextInput
ref={ref}
// font-size is set inline (no lineHeight) so the cursor stays centered
// on iOS while the size still matches the variant on every platform.
// self-stretch makes the TextInput fill the row height so taps on the
// wrapper's vertical padding still focus the field.
className="flex-1 self-stretch p-0 text-foreground placeholder:text-muted-foreground"
style={[{ fontSize: fontSizes[resolvedSize] }, style]}
textAlignVertical="center"
placeholderTextColor={dark ? "#a1a1aa" : "#71717a"}
keyboardAppearance={dark ? "dark" : "light"}
selectionColor={caret}
cursorColor={caret}
{...props}
/>
{trailingIcon && <View className="ms-2">{trailingIcon}</View>}
</View>
);
});