Layouts
UI that stays while the screen under it changes: a top bar, a tab bar, a shell. Layouts nest by folder, and a screen can opt out of them.
+layout.wh
// src/routes/+layout.wh
<Column class="w-full h-full safe">
<TopBar />
<slot />
</Column> A +layout.wh wraps every screen in its folder and below. <slot /> is where the screen goes. Only a layout takes the page below it: <slot /> in a
screen is a build error, since the router passes a screen no children. A +layout.load.wh beside it loads data for the whole chain; see $data.
See it happen
<Column class="w-full h-full safe"> <Text class="text-lg font-bold">My App</Text> <slot /> </Column>
The layout wraps whatever screen the router mounts at <slot />. class="safe" keeps the first row out from under the status bar.
Nesting
Layouts nest by directory, outermost first:
src/routes/
├── +layout.wh # wraps everything
├── +screen.wh # /
└── settings/
├── +layout.wh # wraps /settings and below, inside the root layout
├── +screen.wh # /settings
└── profile/+screen.wh # /settings/profile /settings/profile renders inside the settings layout, which renders
inside the root layout.
Opting out
An @ in the file name says which layouts to leave behind:
| File | Gets |
|---|---|
+screen@.wh | no layouts |
+screen@root.wh | only the root layout |
+layout@.wh | no layouts above it: a layout that starts a new chain |
A login screen that should not carry the app's tab bar is +screen@.wh.
Route groups
src/routes/
├── (auth)/
│ ├── +layout.wh # a layout for the group; no path segment
│ ├── login/+screen.wh # /login
│ └── account/+screen.wh # /account
└── about/+screen.wh # /about A folder in parentheses groups screens under a shared layout and adds nothing to the URL. (auth)/account/+screen.wh is /account. The group's layout is named
after it, here AuthLayout. Groups are how two screens at the same depth get
different layouts.
Safe area
Android draws under the status bar. A root layout wants class="safe"; without it
the first row sits under the clock. bleed says the overlap is deliberate. The
linter asks for one or the other on a root layout.
<Column class="w-full h-full safe">…</Column> // content stays clear of the system bars
<Box class="w-full h-full bleed">…</Box> // the overlap is intended One edge, or the keyboard, at a time, with safe-top, safe-bottom and safe-ime. See Layout.
The shell: src/main.wh
Layouts wrap screens. src/main.wh wraps the router itself, one level up. It is
optional; its body wraps every screen, and the router mounts at <slot />. <App>'s props choose the theme, and bound to a store they switch it while
the app runs:
import $lib.stores.Settings
<App theme={Settings.mode}>
<Column class="w-full h-full">
<OfflineBanner />
<slot />
</Column>
</App> Absent, the shell is <App><slot /></App> and [theme] mode in whitehall.toml is the scheme.
A body without <slot /> is a build error.
+screen@.wh.See Also
- Project Structure — the file table
- $navigate — moving between screens
- $screen — parameters, state and the current path
- Layout — Column, Row, Box and safe-area classes
- Theming