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, but we are working to land that shortly, which would enable multiple loaders per-route.
If you need to access the current route params, you can use the useParams hook.
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.