BetaLayout

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.

PropTypeRequiredDescription
childrenReactNodeNo
variantScaffoldVariant | undefinedNo
colorstring | undefinedNoA 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`.
asChildboolean | undefinedNo

Slots

ScaffoldNavigatorProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNodeNoThe app's navigator — exactly one element. See `Scaffold.Navigator`.

ScaffoldStatusBarProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

This slot adds nothing to the props of its React Native node.

Variants, sizes and colour

This component adds no visual axis of its own. The values it does take are in the generated types above and in the live demo.

Accessibility

The source defines no extra rule. Keep the React Native labels, roles, states and focus order your case needs, then check the result with VoiceOver and TalkBack.

Migration from legacy

This component declares no rule of its own. The migration guide covers variants, colours and slots.

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

variantheader fillheader edgetitle ink
primaryaccentaccentForeground
secondarysurfacesurfaceForeground
tertiarybackgroundborderforeground
ghostbackgroundforeground

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:

  • primary and secondary — 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-context for 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 headerRight and the merge lets it through.