Components

A collection of accessible, styled primitives built with React Native and Tailwind CSS.

Accordion

A collapsible component that displays a list of sections.

1import { Accordion, Text } from '@/components/ui';
2import { useState } from 'react';
3import { View } from 'react-native';
4
5const SECTIONS = [
6 { title: 'Section 1', content: 'Content for section 1' },
7 { title: 'Section 2', content: 'Content for section 2' },
8];
9
10export default function AccordionExample() {
11 const [activeSections, setActiveSections] = useState<number[]>([]);
12
13 return (
14 <Accordion
15 sections={SECTIONS}
16 activeSections={activeSections}
17 onChange={setActiveSections}
18 renderHeader={(section, index, isActive) => (
19 <View className={`p-4 ${isActive ? 'bg-neutral-100 dark:bg-neutral-800' : 'bg-white dark:bg-black'}`}>
20 <Text className="font-bold">{section.title}</Text>
21 </View>
22 )}
23 renderContent={(section) => (
24 <View className="p-4 bg-neutral-50 dark:bg-neutral-900">
25 <Text>{section.content}</Text>
26 </View>
27 )}
28 />
29 );
30}

Avatar

Displays an image or a fallback with initials for a user.

1import { Avatar } from '@/components/ui';
2import { View } from 'react-native';
3
4export default function AvatarExample() {
5 return (
6 <View className="flex-row gap-4 items-center">
7 {/* With Image */}
8 <Avatar
9 source={{ uri: "https://github.com/shadcn.png" }}
10 size="lg"
11 />
12
13 {/* With Initials */}
14 <Avatar
15 label="Jane Doe"
16 size="md"
17 className="bg-blue-500"
18 />
19
20 {/* Different Shapes */}
21 <Avatar
22 label="JD"
23 size="md"
24 shape="square"
25 className="bg-green-500"
26 />
27 </View>
28 );
29}

Bottom Sheet

A modal sheet that slides up from the bottom of the screen.

1import { BottomSheet, Button, Text, useBottomSheet } from '@/components/ui';
2import { View } from 'react-native';
3
4export default function BottomSheetExample() {
5 const { ref, present, dismiss } = useBottomSheet();
6
7 return (
8 <>
9 <Button onPress={present} label="Open Sheet" />
10
11 <BottomSheet
12 ref={ref}
13 title="Options"
14 detents={["auto", 0.6, 1]}
15 >
16 <View className="p-4 gap-4">
17 <Text>Here is some content inside the sheet.</Text>
18 <Button variant="outline" onPress={dismiss} label="Close" />
19 </View>
20 </BottomSheet>
21 </>
22 );
23}

Button

Customizable button with variants, sizes, and loading states.

1import { Button } from '@/components/ui';
2import { View } from 'react-native';
3
4export default function ButtonExample() {
5 return (
6 <View className="gap-4">
7 <Button label="Default Button" onPress={() => {}} />
8
9 <Button variant="secondary" label="Secondary" onPress={() => {}} />
10
11 <Button variant="outline" label="Outline" onPress={() => {}} />
12
13 <Button variant="destructive" label="Destructive" onPress={() => {}} />
14
15 <Button variant="ghost" label="Ghost" onPress={() => {}} />
16
17 <Button loading label="Loading" onPress={() => {}} />
18
19 <Button disabled label="Disabled" onPress={() => {}} />
20 </View>
21 );
22}

Checkbox

A control that allows the user to toggle between checked and unchecked states.

1import { Checkbox } from '@/components/ui';
2import { useState } from 'react';
3
4export default function CheckboxExample() {
5 const [checked, setChecked] = useState(false);
6
7 return (
8 <Checkbox
9 checked={checked}
10 onChange={setChecked}
11 label="Accept terms and conditions"
12 accessibilityLabel="Terms checkbox"
13 />
14 );
15}

Container

Layout wrappers for consistent padding and safe area handling.

1import { Container, Text } from '@/components/ui';
2
3export default function ContainerExample() {
4 return (
5 // Container.Insets handles safe area insets
6 <Container.Insets>
7 {/* Container.Page provides standard page background and flex */}
8 <Container.Page>
9 <Text>Page Content</Text>
10
11 {/* Container.Box provides standard padding */}
12 <Container.Box>
13 <Text>Boxed Content</Text>
14 </Container.Box>
15 </Container.Page>
16 </Container.Insets>
17 );
18}

Icon

A wrapper for displaying icons from image sources or SVGs.

1import { Icon } from '@/components/ui';
2
3export default function IconExample() {
4 return (
5 <Icon
6 icon={require("@/assets/icon.png")}
7 size={32}
8 className="text-blue-500"
9 />
10 );
11}

Image

An optimized image component using expo-image with styling support.

1import { Image } from '@/components/ui';
2
3export default function ImageExample() {
4 return (
5 <Image
6 source={{ uri: "https://picsum.photos/200" }}
7 className="w-40 h-40 rounded-xl"
8 contentFit="cover"
9 transition={1000}
10 />
11 );
12}

Safe Fast Image

A wrapper around Image that handles loading errors by showing a fallback image. It also supports blurhash placeholders and safe source validation.

1import { SafeFastImage } from '@/components/ui';
2
3export default function SafeFastImageExample() {
4 return (
5 <SafeFastImage
6 source={{ uri: "https://invalid-url.com/image.png" }}
7 className="w-40 h-40 rounded-xl"
8 contentFit="cover"
9 placeholder="L6PZfSi_.AyE_3t7t7R**0o#DgR4" // blurhash
10 transition={1000}
11 // If the source fails or is invalid, a default fallback icon is shown
12 />
13 );
14}

Input

Text input component with labels, error states, and icons. Supports integration with React Hook Form and Zod validation.

Basic Usage

1import { Input } from '@/components/ui';
2import { useState } from 'react';
3
4export default function InputExample() {
5 const [value, setValue] = useState("");
6
7 return (
8 <Input
9 label="Email Address"
10 placeholder="hello@example.com"
11 value={value}
12 onChangeText={setValue}
13 error={value.length > 0 && !value.includes('@') ? "Invalid email" : undefined}
14 />
15 );
16}

Variants

1import { Input, Icon } from '@/components/ui';
2import { Mail, Lock } from 'lucide-react-native';
3
4// ... inside component
5<Input
6 label="With Left Icon"
7 leftIcon={<Icon icon={Mail} size={20} className="text-neutral-500" />}
8 placeholder="Email"
9/>
10
11<Input
12 label="With Right Icon"
13 rightIcon={<Icon icon={Lock} size={20} className="text-neutral-500" />}
14 secureTextEntry
15 placeholder="Password"
16/>
17
18<Input
19 label="Disabled Input"
20 value="Cannot change me"
21 editable={false}
22 disabled
23/>
24
25<Input
26 label="Multiline Area"
27 multiline
28 numberOfLines={4}
29 className="h-32"
30 textAlignVertical="top"
31/>

Form Integration (Zod + React Hook Form)

Use ControlledInput for seamless integration with react-hook-form.

1import { ControlledInput, Button, VStack } from '@/components/ui';
2import { useForm } from 'react-hook-form';
3import { z } from 'zod';
4import { zodResolver } from '@hookform/resolvers/zod';
5
6const schema = z.object({
7 email: z.string().email({ message: "Invalid email address" }),
8 password: z.string().min(6, { message: "Password must be at least 6 characters" }),
9});
10
11type FormData = z.infer<typeof schema>;
12
13export default function FormExample() {
14 const { control, handleSubmit } = useForm<FormData>({
15 resolver: zodResolver(schema),
16 defaultValues: {
17 email: '',
18 password: '',
19 },
20 });
21
22 const onSubmit = (data: FormData) => {
23 console.log("Form Submitted:", data);
24 };
25
26 return (
27 <VStack className="gap-4">
28 <ControlledInput
29 control={control}
30 name="email"
31 label="Email"
32 placeholder="hello@example.com"
33 autoCapitalize="none"
34 keyboardType="email-address"
35 />
36
37 <ControlledInput
38 control={control}
39 name="password"
40 label="Password"
41 secureTextEntry
42 placeholder="••••••"
43 />
44
45 <Button onPress={handleSubmit(onSubmit)} label="Login" />
46 </VStack>
47 );
48}

Input View

A scrollable container that automatically handles keyboard avoidance using react-native-avoid-softinput. Perfect for forms.

1import { InputView, Input, Button, Text } from '@/components/ui';
2
3export default function InputViewExample() {
4 return (
5 // InputView handles keyboard avoidance and scrolling automatically
6 <InputView contentContainerClassName="p-4 gap-4">
7 <Text className="text-2xl font-bold mb-4">Registration</Text>
8
9 <Input label="First Name" placeholder="John" />
10 <Input label="Last Name" placeholder="Doe" />
11 <Input label="Email" placeholder="john@example.com" />
12 <Input label="Phone" placeholder="+1 234 567 8900" />
13 <Input label="Address" placeholder="123 Main St" multiline numberOfLines={3} />
14
15 <Button label="Submit" className="mt-4" onPress={() => {}} />
16 </InputView>
17 );
18}

Progress Bar

Visual indicator of progress, available in linear and circular forms.

1import { ProgressBar, Button, VStack } from '@/components/ui';
2import { useRef } from 'react';
3
4export default function ProgressBarExample() {
5 const linearRef = useRef(null);
6 const circularRef = useRef(null);
7
8 const updateProgress = () => {
9 linearRef.current?.setProgress(75);
10 circularRef.current?.setProgress(75);
11 };
12
13 return (
14 <VStack className="gap-8">
15 <ProgressBar ref={linearRef} initialProgress={30} />
16
17 <ProgressBar.Circular
18 ref={circularRef}
19 initialProgress={30}
20 size={60}
21 />
22
23 <Button onPress={updateProgress} label="Set to 75%" />
24 </VStack>
25 );
26}

Radio

Single selection control for a list of options.

1import { Radio, VStack } from '@/components/ui';
2import { useState } from 'react';
3
4export default function RadioExample() {
5 const [selected, setSelected] = useState('option1');
6
7 return (
8 <VStack className="gap-4">
9 <Radio
10 checked={selected === 'option1'}
11 onChange={() => setSelected('option1')}
12 label="Option 1"
13 accessibilityLabel="Option 1"
14 />
15 <Radio
16 checked={selected === 'option2'}
17 onChange={() => setSelected('option2')}
18 label="Option 2"
19 accessibilityLabel="Option 2"
20 />
21 </VStack>
22 );
23}

Select

A dropdown-like selection component using a bottom sheet.

1import { Select } from '@/components/ui';
2import { useState } from 'react';
3
4const OPTIONS = [
5 { label: 'Option 1', value: '1' },
6 { label: 'Option 2', value: '2' },
7 { label: 'Option 3', value: '3' },
8];
9
10export default function SelectExample() {
11 const [value, setValue] = useState<string | number>();
12
13 return (
14 <Select
15 label="Choose an option"
16 options={OPTIONS}
17 value={value}
18 onSelect={setValue}
19 placeholder="Select..."
20 title="Options"
21 />
22 );
23}

Separator

Visually separates content with a line.

1import { Separator, Text, View } from '@/components/ui';
2
3export default function SeparatorExample() {
4 return (
5 <View>
6 <Text>Item 1</Text>
7 <Separator className="my-4" />
8 <Text>Item 2</Text>
9
10 <View className="flex-row h-5 items-center mt-4">
11 <Text>Link 1</Text>
12 <Separator orientation="vertical" className="mx-4" />
13 <Text>Link 2</Text>
14 </View>
15 </View>
16 );
17}

Stacks

Layout primitives for arranging components horizontally or vertically.

1import { HStack, VStack, Center, Circle, Text } from '@/components/ui';
2
3export default function StacksExample() {
4 return (
5 <VStack className="gap-4">
6 <HStack className="gap-2 bg-neutral-100 p-4">
7 <Text>Horizontal</Text>
8 <Text>Stack</Text>
9 </HStack>
10
11 <Center className="bg-neutral-200 p-8">
12 <Text>Centered Content</Text>
13 </Center>
14
15 <Circle className="size-20 bg-blue-500">
16 <Text className="text-white">Circle</Text>
17 </Circle>
18 </VStack>
19 );
20}

Switch

A control that allows the user to toggle between on and off states.

1import { Switch } from '@/components/ui';
2import { useState } from 'react';
3
4export default function SwitchExample() {
5 const [enabled, setEnabled] = useState(false);
6
7 return (
8 <Switch
9 checked={enabled}
10 onChange={setEnabled}
11 label="Enable notifications"
12 accessibilityLabel="Notifications switch"
13 />
14 );
15}

Text

Typography component that handles fonts, sizes, and localization.

1import { Text, VStack } from '@/components/ui';
2
3export default function TextExample() {
4 return (
5 <VStack className="gap-2">
6 <Text className="text-4xl font-bold">Heading 1</Text>
7 <Text className="text-2xl font-semibold">Heading 2</Text>
8 <Text className="text-base text-neutral-500">Body text with regular font.</Text>
9
10 {/* With i18n key */}
11 {/* <Text tx="welcome.message" /> */}
12 </VStack>
13 );
14}

View

An enhanced View component with animation capabilities using Moti.

1import { View, Text, Button } from '@/components/ui';
2import { useState } from 'react';
3
4export default function ViewExample() {
5 const [isVisible, setIsVisible] = useState(true);
6
7 return (
8 <>
9 <Button onPress={() => setIsVisible(!isVisible)} label="Toggle View" />
10
11 <View
12 animatePresence
13 isVisible={isVisible}
14 motionPreset="fadeUp"
15 className="p-4 bg-neutral-100 rounded-xl mt-4"
16 >
17 <Text>I animate in and out!</Text>
18 </View>
19 </>
20 );
21}

Last updated on 2/10/2026

Edit this page on GitHub