Expo Router Form components
Template and 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.
https://github.com/user-attachments/assets/e4f007a3-9316-4f3a-a1fd-352d3af60d11
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 @react-native-community/datetimepicker
You can also just bootstrap a project with this repo:
bunx create-expo -t https://github.com/EvanBacon/expo-router-forms-components
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.
Tabs
The custom tabs adds blurry backgrounds and haptics on iOS. You can also use the shortcut systemImage to set the icon.
import ThemeProvider from "@/components/ui/ThemeProvider";
import Tabs from "@/components/ui/Tabs";
export default function Layout() {
return (
<ThemeProvider>
<Tabs>
<Tabs.Screen name="(index)" systemImage="house.fill" title="Home" />
<Tabs.Screen name="(info)" systemImage="brain.fill" title="Info" />
</Tabs>
</ThemeProvider>
);
}
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";