Scaffold
The app's chrome, painted from the theme: the ground under every screen, the status bar over it, and the options the navigator is dressed with.
Overview
The app's chrome, painted from the theme: the ground under every screen, the status bar over it, and the options the navigator is dressed with.
First example
export default function RootLayout() {return (<XAUIProvider colorMode={colorMode}><Scaffold><Scaffold.StatusBar /><Scaffold.Navigator><Stack screenOptions={{ headerRight: () => <ThemeToggle /> }}><Stack.Screen name="index" options={{ title: 'Accueil' }} /></Stack></Scaffold.Navigator></Scaffold></XAUIProvider>)}
Anatomy
<Scaffold>
<Scaffold.Navigator />
<Scaffold.StatusBar />
</Scaffold>
Usage
export default function RootLayout() {
return (
<XAUIProvider colorMode={colorMode}>
<Scaffold>
<Scaffold.StatusBar />
<Scaffold.Navigator>
<Stack screenOptions={{ headerRight: () => <ThemeToggle /> }}>
<Stack.Screen name="index" options={{ title: 'Accueil' }} />
</Stack>
</Scaffold.Navigator>
</Scaffold>
</XAUIProvider>
)
}
It has to be under XAUIProvider, which is what resolves the theme it reads.
Root props
ScaffoldProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
| variant | ScaffoldVariant | undefined | No | — |
| color | string | undefined | No | A raw tint (`'#7c3aed'`), never a token (R7). Where it lands follows the variant, as it does everywhere else: the bar of a `primary` or a `secondary`, the title and the hairline of a `tertiary`, the title alone of a `ghost`. |
| asChild | boolean | undefined | No | — |
Slots
ScaffoldStatusBarProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
Variants, sizes and colour
Accessibility
Migration from legacy
Implementation notes
It depends on no navigator
@xaui/native has no dependency on expo-router, on @react-navigation/native or on
expo-status-bar — not a real one, not an optional peer, not a type import. This component
does not change that.
Scaffold.Navigator takes the app's own navigator as its child and clones it with five
style keys merged into its screenOptions. Those keys are React Navigation's spelling,
declared structurally here as ScaffoldScreenOptions, and the navigator is the thing that
knows what to do with them. So a Stack, a Tabs, a Drawer, Expo Router or React
Navigation on its own are all dressed by the same component, and the library imports none
of them.
Routing stays entirely the app's. Nothing here writes a route, wraps a screen or
touches what the navigator was configured with: initialRouteName, the Screen children,
a nested layout and the navigator's own type all pass through untouched.
A ladder, not four emphases
| variant | header fill | header edge | title ink |
|---|---|---|---|
primary | accent | — | accentForeground |
secondary | surface | — | surfaceForeground |
tertiary | background | border | foreground |
ghost | background | — | foreground |
It says how much the header separates from the page, from a bar you cannot miss down to
no bar at all. ghost is the default: a header painted in the page's own colour, which is
the arrangement almost every app now wants.
The page's ground is the theme's background under every variant. A scaffold that
repainted the page per variant would be a theme rather than a chrome — the variant is about
the bar.
There is no success / warning / danger: chrome reports nothing.
The tint follows the variant
color is a raw value (R7), and it lands where the variant put its tokens, as everywhere
else in the library:
primaryandsecondary— the bar, with contrasted ink derived in OKLab.tertiary— the title and the hairline, on the page's own ground.ghost— the title alone.
That is why the two flat variants name no fill in the recipe. A ghost button paints its
label in the tint because it declared no background to take it, and this is the same
declaration: <Scaffold color="#7c3aed"> is a brand title on the page's ground, not a
violet page.
`useScaffold`, for a chrome the slots do not reach
const { screenOptions, statusBar } = useScaffold()
Resolved values, never props to resolve again (R5). It is what an app spreads by hand onto
a navigator with keys of its own — a drawer's drawerStyle, a tab bar — or hands to an
Android navigation-bar module.
Outside a <Scaffold>, useAppearance is the same four values with no wiring: this
component is useAppearance with the wiring done.
What it deliberately does not do
- No safe-area inset. The navigator's header already owns the top one, and a scaffold
adding its own would fight
react-native-safe-area-contextfor the bottom. - No header of its own. A header belongs to whichever navigator the app chose, and a status bar belongs to the platform. This hands both what they ask for.
- No colour-mode toggle. Which control flips the mode, and where it sits, is the app's
— write it into the navigator's
headerRightand the merge lets it through.