Layout files can be placed in any folder inside the ./app directory. They serve as the frame for the routes contained within that folder, and they can nest inside of each other if you place them in sub-directories.
_layout.tsx
Wraps all files in this directory and below
index.tsx
Matches "/"
blog
_layout.tsx
A custom sub-layout for all /blog pages
index.tsx
Matches "/blog"
[slug].tsx
Matches a single sub-path of "/blog", like "/blog/hello"
Layouts must render one of the following to show the matched pages that exist in their directory:
As of now, layouts don't support loaders. If you need to access the current route params, you can use the useParams hook.
Root Layout
The root layout on web controls the entire page, from <html> on down. You can optionally return html, body, and head elements in your app/_layout.tsx and they will "just work" - on web, it will be output, and on native it won't so as to avoid rendering errors:
The root _layout.tsx can use a special hook that allows it to insert tags into your <head /> tag after server rendering finishes. This is an edge-case that is mostly only useful for CSS-in-JS libraries. You can use it like so:
import{ Slot, useServerHeadInsertion }from'one'
exportdefaultfunctionLayout(){
useServerHeadInsertion(()=>{
// this will run after the entire page renders
// return a single React.ReactElement that will be spliced into your <head />
return<style>{renderCSS()}</style>
})
return(
<html>
<Slot/>
</html>
)
}
Groups and layouts
If you'd like to logically group together some pages without creating a sub-route, you can use a group folder by naming it like so:
_layout.tsx
Wraps all files in this directory and below
index.tsx
Matches "/"
(blog)
_layout.tsx
A custom sub-layout for all /blog pages
blog.tsx
Matches "/blog"
[slug].tsx
Matches a single sub-path of "/blog", like "/blog/hello"
Groups let you add extra layouts. If done right, this gives you a lot of control over how your app feels.
Nested layouts example
A common pattern that apps have is something like Twitter/X, where you have bottom tabs for your "top level" views, but then on some of the bottom tab sections, you want to have a Stack that remembers its state inside just that tab.
This pattern can be incredibly verbose to link together with React Navigation, and takes a bit of tinkering to figure out with One. Since it is common and a useful example, lets walk through how you'd build on using One's file system routing.
Here's our file structure:
_layout.tsx
Where our Tabs layout is defined
notifications.tsx
Matches "/notifications"
profile.tsx
Matches "/profile"
(feed)
_layout.tsx
Where our Stack layout is defined
index.tsx
Matches "/"
post-[id].tsx
Matches routes like "/post-123"
The top level Layout will define our tabs:
app/_layout.tsx
import{ Bell, Home, User }from'@tamagui/lucide-icons'
import{ Home }from'~/features/icons'
exportfunctionRootLayout(){
return(
<TabsscreenOptions={{
headerShown:false,}}>
<Tabs.Screenname="(feed)"options={{
title:'Feed',tabBarIcon:({ color })=><Homesize={20}color={color}/>,}}/>
<Tabs.Screenname="notifications"options={{
title:'Notifications',tabBarIcon:({ color })=><Bellsize={20}color={color}/>,}}/>
<Tabs.Screenname="profile"options={{
title:'Profile',tabBarIcon:({ color })=><Usersize={20}color={color}/>,}}/>
</Tabs>
)
}
This will set us up with three bottom tabs: Feed, Notifications, and Profile. The Notifications and Profile tabs for now will just show their content directly, but inside of the Feed tab, we want to show a stack.
One thing we're showing here is that the layout is diverging between web and native. On web, we are showing a Slot, while on Native we show a Stack. This is because browsers feel better without stacks - the native back/forward button serves as our stack controller.
On Native we are defining the configuration for each sub-screen with the Stack.Screen component. The Stack component is a React Navigation native stack navigator and nothing more, it accepts all the props you'd expect.
You'll notice we are matching the name to the file names names of the sub-routes, without the .tsx extension.
This will get you a nice Stack-inside-Tabs pattern that is common on native apps, all with just two layouts and a few routes.
Limitations
As of today layouts don't support loaders. This is on our radar, please chime in if this is helpful for you.
Also note that useParams won't work in layouts, as they are never nested inside a route. Instead, you can use useActiveParams.