Scaffold
Screen structure for safe areas, top chrome, scrollable content and a fixed footer.
Scaffold anatomy
Scaffold fills the screen, then arranges its regions vertically.
- Scaffold.AppBarHolds navigation, the screen title and contextual actions.
- Scaffold.ContentTakes the available height and owns scrolling by default.
- Scaffold.FooterKeeps the main action visible below the scrolling content.
- safeAreaEdgesProtect content around the status bar and home indicator.
Installation
npx axiom add scaffoldpnpm dlx axiom add scaffoldyarn dlx axiom add scaffoldbun x axiom add scaffoldAlso copies: app-bar and its dependencies. Installs react-native-keyboard-controller and react-native-safe-area-context. Scaffold brings no tokens of its own: it uses the screen background and the bar’s.
Your app needs KeyboardProvider at its root for Scaffold.KeyboardAvoiding to work.
Usage
import { AppBar } from "@/components/ui/app-bar";
import { Scaffold } from "@/components/ui/scaffold";
<Scaffold>
<Scaffold.AppBar>
<AppBar.Row>
<AppBar.Center inset>
<AppBar.Title>Settings</AppBar.Title>
</AppBar.Center>
</AppBar.Row>
</Scaffold.AppBar>
<Scaffold.Content>{/* screen content */}</Scaffold.Content>
<Scaffold.Footer>
<Button fullWidth>Save</Button>
</Scaffold.Footer>
</Scaffold>;Props
Scaffold
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Screen regions, usually AppBar, Content and an optional Footer. |
safeAreaEdges | Edge[] | ['top', 'bottom'] | Safe-area edges owned by the screen. Child regions avoid applying the same inset twice. |
background | 'default' | 'subtle' | 'default' | Uses the standard screen background or the grouped-list background. |
statusBarStyle | 'auto' | 'light' | 'dark' | 'auto' | Status bar content style. auto follows the resolved color scheme. |
style | StyleProp<ViewStyle> | — | Extra styles for the screen container. |
Scaffold.KeyboardAvoiding
A KeyboardAvoidingView from react-native-keyboard-controller that fills the screen. Wrap the Content and the Footer of a screen that has a field: the footer rises above the keyboard and the content keeps its room. It takes every KeyboardAvoidingView prop; behavior defaults to 'padding'. A screen without a field leaves it out and listens to nothing.
Scaffold.AppBar
An AppBar without its own top inset, since the root already owns it. It takes every AppBar prop except safeArea, and the same parts as children.
Scaffold.Content
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Main screen content. |
scrollable | boolean | true | Uses a ScrollView. Set it to false for lists, maps or another child that owns scrolling. |
contentContainerStyle | StyleProp<ViewStyle> | — | Styles the inner scroll container, including padding and gaps. |
keyboardShouldPersistTaps | ScrollViewProps['keyboardShouldPersistTaps'] | 'handled' | Controls whether tapping content dismisses the keyboard. |
...ScrollViewProps | — | — | Remaining scroll props when scrollable is true. |
Scaffold.Footer
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Fixed actions shown after the content region. |
bordered | boolean | false | Draws a hairline above the footer when scrolling content passes behind it. |
safeArea | boolean | false | Adds the bottom safe-area inset to the footer. Leave it off while the root owns the bottom edge, which it does by default — otherwise the inset lands twice. |
style | StyleProp<ViewStyle> | — | Extra footer styles. |
Use cases
Form with a fixed action
Let the fields scroll while the main action stays reachable. The footer moves above the keyboard instead of covering the focused field.
<Scaffold>
<Scaffold.AppBar>
<AppBar.Row>
<CloseButton />
<AppBar.Center>
<AppBar.Title>New address</AppBar.Title>
</AppBar.Center>
</AppBar.Row>
</Scaffold.AppBar>
<Scaffold.KeyboardAvoiding>
<Scaffold.Content contentContainerStyle={styles.form}>
<AddressFields />
</Scaffold.Content>
<Scaffold.Footer>
<Button size="lg" fullWidth onPress={save}>
Save address
</Button>
</Scaffold.Footer>
</Scaffold.KeyboardAvoiding>
</Scaffold>Grouped settings screen
Use the subtle background with inset lists. A FlatList owns scrolling here, so Scaffold.Content renders a plain view.
<Scaffold background="subtle">
<Scaffold.AppBar>
<AppBar.Row>
<BackButton />
<AppBar.Center>
<AppBar.Title>Settings</AppBar.Title>
</AppBar.Center>
</AppBar.Row>
</Scaffold.AppBar>
<Scaffold.Content scrollable={false}>
<FlatList data={sections} renderItem={renderSection} />
</Scaffold.Content>
</Scaffold>Full-bleed map
Turn off the content scroller when the child needs every available pixel and handles gestures itself. Screen chrome remains in its own regions.
<Scaffold>
<Scaffold.AppBar>
<AppBar.Row>
<BackButton />
<AppBar.Center>
<AppBar.Title>Choose a place</AppBar.Title>
</AppBar.Center>
</AppBar.Row>
</Scaffold.AppBar>
<Scaffold.Content scrollable={false}>
<MapView style={StyleSheet.absoluteFill} />
</Scaffold.Content>
<Scaffold.Footer>
<PlaceSummary />
<Button fullWidth>Use this place</Button>
</Scaffold.Footer>
</Scaffold>A base, not a fixed layout
Leave out any region the screen does not need. Scaffold coordinates safe areas and available space, but it does not force every screen to have a bar or footer.