Skip to content

RodolfoSilva/expo-router-forms-components

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Common components for Expo apps

Components that I use in Expo Router apps that are generally optimized for iOS, dark mode, and servers. Main part is the forms which look like Apple's settings app. These should be replaced with proper SwiftUI/Jetpack Compose in the future, but it's still useful to have JS versions for platforms that don't have native support.

For best results, just copy the files to another project. Here are the other deps:

bunx expo install expo-haptics expo-symbols expo-blur expo-web-browser @bacons/apple-colors vaul @react-native-segmented-control/segmented-control

Stack

Use the correct stack header settings for peak iOS defaults:

import Stack from "@/components/ui/Stack";
import ThemeProvider from "@/components/ui/ThemeProvider";

export default function Layout() {
  return (
    <ThemeProvider>
      <Stack
        screenOptions={{
          title: "🥓 Bacon",
        }}
      />
    </ThemeProvider>
  );
}

Use headerLargeTitle: true to get the large header title.

Use <Form.Link headerRight> to add a link to the right side of the header with correct hit size and padding for Forms. The default color will be system blue.

<Stack
  screenOptions={{
    title: "🥓 Bacon",
    headerRight: () => (
      <Form.Link headerRight href="/info">
        Info
      </Form.Link>
    ),
  }}
/>

This stack uses vaul on web to make modal look like a native modal.

Bottom sheet

Works on web too!

You can open routes as a bottom sheet on iOS:

<Stack.Screen name="info" sheet />

This sets custom options for React Native Screens:

{
  presentation: "formSheet",
  gestureDirection: "vertical",
  animation: "slide_from_bottom",
  sheetGrabberVisible: true,
  sheetInitialDetentIndex: 0,
  sheetAllowedDetents: [0.5, 1.0],
}
  • Use sheetAllowedDetents to change the snap points of the sheet.
  • Change the corder radius with sheetCornerRadius: 48.

Forms

Start lists with a <Form.List> and add sections with <Form.Section>. Setting navigationTitle="Settings" will update the title of the stack header.

<Form.List navigationTitle="Settings">
  <Form.Section title="Developer">
    <Form.Link target="_blank" href="https://evanbacon.dev">
      Evan Bacon
    </Form.Link>
    <Form.Link href="https://evanbacon.dev">Evan Bacon in browser</Form.Link>
  </Form.Section>
</Form.List>
Internals

Form list is a wrapper around a scroll view with some extra styles and helpers.

<BodyScrollView
  contentContainerStyle={{
    padding: 16,
    gap: 24,
  }}
>
  <Form.Section title="Developer">
    <Form.Link target="_blank" href="https://evanbacon.dev">
      Evan Bacon
    </Form.Link>
    <Form.Link href="https://evanbacon.dev">Evan Bacon in browser</Form.Link>
  </Form.Section>
</BodyScrollView>

Form Sections

All top-level children will become items.

Add title and footer to a section. These can be strings or React nodes.

import * as AC from "@bacons/apple-colors";

<Form.Section
  title="Header"
  footer={
    <Text>
      Help improve Search by allowing Apple to store the searches you enter into
      Safari, Siri, and Spotlight in a way that is not linked to you.{"\n\n"}
      Searches include lookups of general knowledge, and requests to do things like
      play music and get directions.{"\n"}
      <Link style={{ color: AC.link }} href="/two">
        About Search & Privacy...
      </Link>
    </Text>
  }
>
  <Text>Default</Text>
</Form.Section>;

Form Items

  • Form.Text has extra types for hint and custom styles to have adaptive colors for dark mode. The font size is also larger to match the Apple defaults.
  • Adds the systemImage prop to append an SF Symbol icon before the text. The color of this icon will adopt the color of the text style.
<Form.Text>Hey</Form.Text>

Add a hint to the right-side of the form item:

<Form.Text hint="right">Left</Form.Text>

Add a custom press handler to the form item:

<Form.Text
  onPress={() => {
    console.log("Pressed");
  }}
>
  Press me
</Form.Text>

Form Link

Open with in-app browser using target="_blank" (only works when the href is an external URL):

<Form.Link target="_blank" href="https://evanbacon.dev">
  Evan Bacon
</Form.Link>

Add a hint to the right-side of the form item:

<Form.Link hint="123" href="/foo">
  Evan Bacon
</Form.Link>

Alternatively, use an HStack-type system instead of the hint hack:

<Form.HStack>
  <Form.Text>Foo</Form.Text>
  <View style={{ flex: 1 }} />
  <Form.Text style={Form.FormFont.secondary}>Bar</Form.Text>
</Form.HStack>

Add a quick icon before the text:

<Form.Link href="/two" systemImage="person.fill.badge.plus">
  Evan Bacon
</Form.Link>

Customize the color, size, etc:

<Form.Link
  href="/two"
  systemImage={{
    name: "person.fill.badge.plus",
    color: AC.systemBlue,
    size: 128,
  }}
>
  Evan Bacon
</Form.Link>

Form Description and Item

Add a list item with an image and text + description combo:

<Form.HStack style={{ gap: 16 }}>
  <Image
    source={{ uri: "https://github.com/evanbacon.png" }}
    style={{
      aspectRatio: 1,
      height: 48,
      borderRadius: 999,
    }}
  />
  <View style={{ gap: 4 }}>
    <Form.Text>Evan's iPhone</Form.Text>
    <Form.Text style={Form.FormFont.caption}>This iPhone 16 Pro Max</Form.Text>
  </View>

  {/* Spacer */}
  <View style={{ flex: 1 }} />

  <IconSymbol color={AC.systemBlue} name="person.fill.badge.plus" size={24} />
</Form.HStack>

Create a linkable version like this:

<Form.Link href="/two">
  <View style={{ gap: 4 }}>
    <Form.Text>Evan's iPhone</Form.Text>
    <Form.Text style={Form.FormFont.caption}>This iPhone 16 Pro Max</Form.Text>
  </View>
</Form.Link>

Colors

Be sure to use @bacons/apple-colors for high-quality P3 colors.

Icons

Use the IconSymbol component to use Apple's SF Symbols.

Status Bar

Avoid using <StatusBar> on iOS as the system has built-in support for changing the color better than most custom solutions. Enable OS-changing with:

{
  "expo": {
    "userInterfaceStyle": "automatic",
    "ios": {
      "infoPlist": {
        "UIViewControllerBasedStatusBarAppearance": true,
      }
    }
  }
}

This won't work as expected in Expo Go. Use a dev client to understand the behavior better.

Segments

npx expo install @react-native-segmented-control/segmented-control

For tabbed content that doesn't belong in the router, use the Segment component:

import {
  Segments,
  SegmentsList,
  SegmentsContent,
  SegmentsTrigger,
} from "@/components/ui/Segments";

export default function Page() {
  return (
    <Segments defaultValue="account">
      <SegmentsList>
        <SegmentsTrigger value="account">Account</SegmentsTrigger>
        <SegmentsTrigger value="password">Password</SegmentsTrigger>
      </SegmentsList>

      <SegmentsContent value="account">
        <Text>Account Section</Text>
      </SegmentsContent>
      <SegmentsContent value="password">
        <Text>Password Section</Text>
      </SegmentsContent>
    </Segments>
  );
}

This can be used with React Server Components as the API is entirely declarative.

Toggle

Add a toggle switch item using hint and Switch from React Native:

<Form.Text hint={<Switch />}>Label</Form.Text>

You can also build the item manually for more customization:

<Form.HStack>
  <Form.Text>Label</Form.Text>
  <View style={{ flex: 1 }} />
  <Switch />
</Form.HStack>

About

some common components I use for building Expo apps

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 100.0%