Skip to main content
This guide covers all available components in NearYou, their APIs, and usage examples.

Component Architecture

NearYou follows a component-based architecture with:
  • Themed Components - Automatically adapt to light/dark mode
  • Layout Components - Handle scrolling and structure
  • Interactive Components - Buttons, links, and inputs
  • UI Components - Icons, collapsibles, and utilities

Themed Components

Themed components automatically respond to color scheme changes.

ThemedText

Location: src/components/themed-text.tsx A text component with predefined typography styles and automatic theme colors.
import { ThemedText } from '@/components/themed-text';

<ThemedText type="title">Welcome to NearYou</ThemedText>
<ThemedText type="subtitle">Getting Started</ThemedText>
<ThemedText type="defaultSemiBold">Important info</ThemedText>
<ThemedText type="link">Learn more</ThemedText>
<ThemedText>Regular body text</ThemedText>

ThemedView

Location: src/components/themed-view.tsx A view container with automatic background color theming.
import { ThemedView } from '@/components/themed-view';

<ThemedView style={styles.container}>
  <ThemedText>Content</ThemedText>
</ThemedView>

Layout Components

ParallaxScrollView

Location: src/components/parallax-scroll-view.tsx A scroll view with an animated parallax header that scales and translates on scroll.
import ParallaxScrollView from '@/components/parallax-scroll-view';
import { Image } from 'expo-image';

<ParallaxScrollView
  headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
  headerImage={
    <Image
      source={require('@assets/images/header.png')}
      style={styles.headerImage}
    />
  }
>
  <ThemedText type="title">Content below header</ThemedText>
  {/* More content */}
</ParallaxScrollView>

Interactive Components

HapticTab

Location: src/components/haptic-tab.tsx Provides haptic feedback when pressing tab buttons (iOS only).
import { Tabs } from 'expo-router';
import { HapticTab } from '@/components/haptic-tab';

<Tabs
  screenOptions={{
    tabBarButton: HapticTab,
  }}
>
  {/* Tab screens */}
</Tabs>
Haptic feedback only works on iOS devices. On Android and web, the component functions as a normal tab button without haptics.
Location: src/components/external-link.tsx Opens URLs in an in-app browser on native platforms, and in a new tab on web.
import { ExternalLink } from '@/components/external-link';

<ExternalLink href="https://expo.dev">
  <ThemedText type="link">Visit Expo</ThemedText>
</ExternalLink>

HelloWave

Location: src/components/hello-wave.tsx An animated waving hand emoji.
import { HelloWave } from '@/components/hello-wave';

<ThemedView style={styles.titleContainer}>
  <ThemedText type="title">Welcome!</ThemedText>
  <HelloWave />
</ThemedView>
Animation details:
  • Duration: 300ms per wave
  • Iterations: 4 times
  • Rotation: 0° to 25° and back

UI Components

IconSymbol

Location: src/components/ui/icon-symbol.tsx (Android/Web), src/components/ui/icon-symbol.ios.tsx (iOS) Cross-platform icon component using SF Symbols on iOS and Material Icons on Android/Web.
import { IconSymbol } from '@/components/ui/icon-symbol';

<IconSymbol
  name="house.fill"
  size={24}
  color="#0a7ea4"
/>

// With dynamic color
const textColor = useThemeColor({}, 'text');
<IconSymbol
  name="paperplane.fill"
  size={28}
  color={textColor}
/>
Finding icons:

Collapsible

Location: src/components/ui/collapsible.tsx Expandable/collapsible content section with animated chevron.
import { Collapsible } from '@/components/ui/collapsible';

<Collapsible title="Advanced Settings">
  <ThemedText>Hidden content that expands on tap</ThemedText>
  <ThemedText>You can put any components here</ThemedText>
</Collapsible>

Creating Custom Components

Using Themed Components

Build on top of themed components for consistency:
components/custom-button.tsx
import { TouchableOpacity, StyleSheet } from 'react-native';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { useThemeColor } from '@/hooks/use-theme-color';

type ButtonProps = {
  title: string;
  onPress: () => void;
  variant?: 'primary' | 'secondary';
};

export function CustomButton({ title, onPress, variant = 'primary' }: ButtonProps) {
  const tintColor = useThemeColor({}, 'tint');
  const backgroundColor = variant === 'primary' ? tintColor : 'transparent';
  const textColor = variant === 'primary' ? '#fff' : tintColor;

  return (
    <TouchableOpacity onPress={onPress}>
      <ThemedView
        style={[
          styles.button,
          { backgroundColor },
          variant === 'secondary' && styles.outlined
        ]}
      >
        <ThemedText
          style={[styles.text, { color: textColor }]}
          type="defaultSemiBold"
        >
          {title}
        </ThemedText>
      </ThemedView>
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  button: {
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
    alignItems: 'center',
  },
  outlined: {
    borderWidth: 2,
    borderColor: 'currentColor',
  },
  text: {
    fontSize: 16,
  },
});

Component Best Practices

Use Composition

Build complex components from smaller themed components

Theme Integration

Use useThemeColor and useColorScheme for consistency

TypeScript Props

Define clear prop interfaces with TypeScript

Style Composition

Allow style overrides via props

Component Patterns

Compound Components

Create related components that work together:
components/card.tsx
import { PropsWithChildren } from 'react';
import { ThemedView } from '@/components/themed-view';
import { ThemedText } from '@/components/themed-text';
import { StyleSheet } from 'react-native';

function Card({ children }: PropsWithChildren) {
  return (
    <ThemedView
      lightColor="#ffffff"
      darkColor="#1f2937"
      style={styles.card}
    >
      {children}
    </ThemedView>
  );
}

function CardHeader({ children }: PropsWithChildren) {
  return (
    <ThemedView style={styles.header}>
      {children}
    </ThemedView>
  );
}

function CardTitle({ children }: PropsWithChildren) {
  return <ThemedText type="subtitle">{children}</ThemedText>;
}

function CardContent({ children }: PropsWithChildren) {
  return <ThemedView style={styles.content}>{children}</ThemedView>;
}

Card.Header = CardHeader;
Card.Title = CardTitle;
Card.Content = CardContent;

export { Card };

// Usage:
<Card>
  <Card.Header>
    <Card.Title>Title</Card.Title>
  </Card.Header>
  <Card.Content>
    <ThemedText>Content</ThemedText>
  </Card.Content>
</Card>

Render Props

Provide flexible rendering options:
components/list.tsx
type ListProps<T> = {
  data: T[];
  renderItem: (item: T, index: number) => ReactNode;
  emptyMessage?: string;
};

export function List<T>({ data, renderItem, emptyMessage }: ListProps<T>) {
  if (data.length === 0) {
    return (
      <ThemedView style={styles.empty}>
        <ThemedText>{emptyMessage ?? 'No items'}</ThemedText>
      </ThemedView>
    );
  }

  return (
    <ThemedView>
      {data.map((item, index) => (
        <ThemedView key={index}>
          {renderItem(item, index)}
        </ThemedView>
      ))}
    </ThemedView>
  );
}

// Usage:
<List
  data={users}
  renderItem={(user) => (
    <ThemedText>{user.name}</ThemedText>
  )}
  emptyMessage="No users found"
/>

Performance Optimization

Memoization

Use React.memo for expensive components:
import { memo } from 'react';

type ItemProps = {
  title: string;
  description: string;
  onPress: () => void;
};

const ListItem = memo(function ListItem({ title, description, onPress }: ItemProps) {
  return (
    <TouchableOpacity onPress={onPress}>
      <ThemedView style={styles.item}>
        <ThemedText type="defaultSemiBold">{title}</ThemedText>
        <ThemedText>{description}</ThemedText>
      </ThemedView>
    </TouchableOpacity>
  );
});

Virtualized Lists

For long lists, use FlatList or SectionList:
import { FlatList } from 'react-native';
import { ThemedView } from '@/components/themed-view';
import { useThemeColor } from '@/hooks/use-theme-color';

function UserList({ users }) {
  const backgroundColor = useThemeColor({}, 'background');

  return (
    <FlatList
      data={users}
      renderItem={({ item }) => <UserItem user={item} />}
      keyExtractor={(item) => item.id}
      contentContainerStyle={{ backgroundColor }}
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      windowSize={5}
    />
  );
}

Testing Components

__tests__/themed-text.test.tsx
import { render } from '@testing-library/react-native';
import { ThemedText } from '@/components/themed-text';

describe('ThemedText', () => {
  it('renders with default type', () => {
    const { getByText } = render(
      <ThemedText>Hello</ThemedText>
    );
    expect(getByText('Hello')).toBeTruthy();
  });

  it('applies title styles', () => {
    const { getByText } = render(
      <ThemedText type="title">Title</ThemedText>
    );
    const element = getByText('Title');
    expect(element.props.style).toMatchObject({
      fontSize: 32,
      fontWeight: 'bold',
    });
  });
});

Next Steps

Theme System

Customize colors and themes

Navigation

Connect components with routing

React Native Docs

Learn about built-in components

Build docs developers (and LLMs) love