@codyswann/lisa 2.111.0 → 2.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.mcp.json +3 -3
- package/plugins/lisa-expo/THIRD-PARTY-NOTICES.md +57 -0
- package/plugins/lisa-expo/skills/add-app-clip/SKILL.md +280 -0
- package/plugins/lisa-expo/skills/add-app-clip/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/add-app-clip/references/native-module.md +96 -0
- package/plugins/lisa-expo/skills/building-native-ui/SKILL.md +321 -0
- package/plugins/lisa-expo/skills/building-native-ui/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/animations.md +220 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/controls.md +272 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/form-sheet.md +253 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/gradients.md +106 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/icons.md +213 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/media.md +198 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/route-structure.md +229 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/search.md +248 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/storage.md +121 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/tabs.md +433 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/visual-effects.md +197 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/webgpu-three.md +605 -0
- package/plugins/lisa-expo/skills/building-native-ui/references/zoom-transitions.md +158 -0
- package/plugins/lisa-expo/skills/eas-update-insights/SKILL.md +228 -0
- package/plugins/lisa-expo/skills/eas-update-insights/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
- package/plugins/lisa-expo/skills/eas-update-insights/references/update-insights-schema.md +69 -0
- package/plugins/lisa-expo/skills/expo-api-routes/SKILL.md +369 -0
- package/plugins/lisa-expo/skills/expo-api-routes/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-brownfield/SKILL.md +54 -0
- package/plugins/lisa-expo/skills/expo-brownfield/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
- package/plugins/lisa-expo/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
- package/plugins/lisa-expo/skills/expo-brownfield/references/comparison.md +63 -0
- package/plugins/lisa-expo/skills/expo-brownfield/references/troubleshooting.md +88 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/SKILL.md +92 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/scripts/package.json +11 -0
- package/plugins/lisa-expo/skills/expo-cicd-workflows/scripts/validate.js +85 -0
- package/plugins/lisa-expo/skills/expo-deployment/SKILL.md +190 -0
- package/plugins/lisa-expo/skills/expo-deployment/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/app-store-metadata.md +479 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/ios-app-store.md +355 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/play-store.md +246 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/testflight.md +58 -0
- package/plugins/lisa-expo/skills/expo-deployment/references/workflows.md +200 -0
- package/plugins/lisa-expo/skills/expo-dev-client/SKILL.md +164 -0
- package/plugins/lisa-expo/skills/expo-dev-client/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-module/SKILL.md +141 -0
- package/plugins/lisa-expo/skills/expo-module/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-module/references/config-plugin.md +90 -0
- package/plugins/lisa-expo/skills/expo-module/references/create-expo-module.md +206 -0
- package/plugins/lisa-expo/skills/expo-module/references/lifecycle.md +127 -0
- package/plugins/lisa-expo/skills/expo-module/references/module-config.md +48 -0
- package/plugins/lisa-expo/skills/expo-module/references/native-module.md +286 -0
- package/plugins/lisa-expo/skills/expo-module/references/native-view.md +171 -0
- package/plugins/lisa-expo/skills/expo-tailwind-setup/SKILL.md +480 -0
- package/plugins/lisa-expo/skills/expo-tailwind-setup/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
- package/plugins/lisa-expo/skills/expo-ui-jetpack-compose/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/expo-ui-swift-ui/SKILL.md +39 -0
- package/plugins/lisa-expo/skills/expo-ui-swift-ui/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/native-data-fetching/SKILL.md +507 -0
- package/plugins/lisa-expo/skills/native-data-fetching/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
- package/plugins/lisa-expo/skills/upgrading-expo/SKILL.md +134 -0
- package/plugins/lisa-expo/skills/upgrading-expo/agents/openai.yaml +4 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/native-tabs.md +124 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/new-architecture.md +79 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/react-19.md +79 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/react-compiler.md +59 -0
- package/plugins/lisa-expo/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
- package/plugins/lisa-expo/skills/use-dom/SKILL.md +417 -0
- package/plugins/lisa-expo/skills/use-dom/agents/openai.yaml +4 -0
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/src/expo/.mcp.json +3 -3
- package/plugins/src/expo/THIRD-PARTY-NOTICES.md +57 -0
- package/plugins/src/expo/skills/add-app-clip/SKILL.md +280 -0
- package/plugins/src/expo/skills/add-app-clip/references/native-module.md +96 -0
- package/plugins/src/expo/skills/building-native-ui/SKILL.md +321 -0
- package/plugins/src/expo/skills/building-native-ui/references/animations.md +220 -0
- package/plugins/src/expo/skills/building-native-ui/references/controls.md +272 -0
- package/plugins/src/expo/skills/building-native-ui/references/form-sheet.md +253 -0
- package/plugins/src/expo/skills/building-native-ui/references/gradients.md +106 -0
- package/plugins/src/expo/skills/building-native-ui/references/icons.md +213 -0
- package/plugins/src/expo/skills/building-native-ui/references/media.md +198 -0
- package/plugins/src/expo/skills/building-native-ui/references/route-structure.md +229 -0
- package/plugins/src/expo/skills/building-native-ui/references/search.md +248 -0
- package/plugins/src/expo/skills/building-native-ui/references/storage.md +121 -0
- package/plugins/src/expo/skills/building-native-ui/references/tabs.md +433 -0
- package/plugins/src/expo/skills/building-native-ui/references/toolbar-and-headers.md +284 -0
- package/plugins/src/expo/skills/building-native-ui/references/visual-effects.md +197 -0
- package/plugins/src/expo/skills/building-native-ui/references/webgpu-three.md +605 -0
- package/plugins/src/expo/skills/building-native-ui/references/zoom-transitions.md +158 -0
- package/plugins/src/expo/skills/eas-update-insights/SKILL.md +228 -0
- package/plugins/src/expo/skills/eas-update-insights/references/channel-insights-schema.md +47 -0
- package/plugins/src/expo/skills/eas-update-insights/references/update-insights-schema.md +69 -0
- package/plugins/src/expo/skills/expo-api-routes/SKILL.md +369 -0
- package/plugins/src/expo/skills/expo-brownfield/SKILL.md +54 -0
- package/plugins/src/expo/skills/expo-brownfield/references/brownfield-integrated.md +526 -0
- package/plugins/src/expo/skills/expo-brownfield/references/brownfield-isolated.md +402 -0
- package/plugins/src/expo/skills/expo-brownfield/references/comparison.md +63 -0
- package/plugins/src/expo/skills/expo-brownfield/references/troubleshooting.md +88 -0
- package/plugins/src/expo/skills/expo-cicd-workflows/SKILL.md +92 -0
- package/plugins/src/expo/skills/expo-cicd-workflows/scripts/fetch.js +113 -0
- package/plugins/src/expo/skills/expo-cicd-workflows/scripts/package.json +11 -0
- package/plugins/src/expo/skills/expo-cicd-workflows/scripts/validate.js +85 -0
- package/plugins/src/expo/skills/expo-deployment/SKILL.md +190 -0
- package/plugins/src/expo/skills/expo-deployment/references/app-store-metadata.md +479 -0
- package/plugins/src/expo/skills/expo-deployment/references/ios-app-store.md +355 -0
- package/plugins/src/expo/skills/expo-deployment/references/play-store.md +246 -0
- package/plugins/src/expo/skills/expo-deployment/references/testflight.md +58 -0
- package/plugins/src/expo/skills/expo-deployment/references/workflows.md +200 -0
- package/plugins/src/expo/skills/expo-dev-client/SKILL.md +164 -0
- package/plugins/src/expo/skills/expo-module/SKILL.md +141 -0
- package/plugins/src/expo/skills/expo-module/references/config-plugin.md +90 -0
- package/plugins/src/expo/skills/expo-module/references/create-expo-module.md +206 -0
- package/plugins/src/expo/skills/expo-module/references/lifecycle.md +127 -0
- package/plugins/src/expo/skills/expo-module/references/module-config.md +48 -0
- package/plugins/src/expo/skills/expo-module/references/native-module.md +286 -0
- package/plugins/src/expo/skills/expo-module/references/native-view.md +171 -0
- package/plugins/src/expo/skills/expo-tailwind-setup/SKILL.md +480 -0
- package/plugins/src/expo/skills/expo-ui-jetpack-compose/SKILL.md +40 -0
- package/plugins/src/expo/skills/expo-ui-swift-ui/SKILL.md +39 -0
- package/plugins/src/expo/skills/native-data-fetching/SKILL.md +507 -0
- package/plugins/src/expo/skills/native-data-fetching/references/expo-router-loaders.md +344 -0
- package/plugins/src/expo/skills/upgrading-expo/SKILL.md +134 -0
- package/plugins/src/expo/skills/upgrading-expo/references/expo-av-to-audio.md +132 -0
- package/plugins/src/expo/skills/upgrading-expo/references/expo-av-to-video.md +160 -0
- package/plugins/src/expo/skills/upgrading-expo/references/native-tabs.md +124 -0
- package/plugins/src/expo/skills/upgrading-expo/references/new-architecture.md +79 -0
- package/plugins/src/expo/skills/upgrading-expo/references/react-19.md +79 -0
- package/plugins/src/expo/skills/upgrading-expo/references/react-compiler.md +59 -0
- package/plugins/src/expo/skills/upgrading-expo/references/react-navigation-to-expo-router.md +61 -0
- package/plugins/src/expo/skills/use-dom/SKILL.md +417 -0
- package/scripts/generate-codex-plugin-artifacts.mjs +7 -2
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# Native Tabs
|
|
2
|
+
|
|
3
|
+
Always prefer NativeTabs from 'expo-router/unstable-native-tabs' for the best iOS experience.
|
|
4
|
+
|
|
5
|
+
**SDK 54+. SDK 55 recommended.**
|
|
6
|
+
|
|
7
|
+
## SDK Compatibility
|
|
8
|
+
|
|
9
|
+
| Aspect | SDK 54 | SDK 55+ |
|
|
10
|
+
| ------------- | ------------------------------------------------------- | ----------------------------------------------------------- |
|
|
11
|
+
| Import | `import { NativeTabs, Icon, Label, Badge, VectorIcon }` | `import { NativeTabs }` only |
|
|
12
|
+
| Icon | `<Icon sf="house.fill" />` | `<NativeTabs.Trigger.Icon sf="house.fill" />` |
|
|
13
|
+
| Label | `<Label>Home</Label>` | `<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>` |
|
|
14
|
+
| Badge | `<Badge>9+</Badge>` | `<NativeTabs.Trigger.Badge>9+</NativeTabs.Trigger.Badge>` |
|
|
15
|
+
| Android icons | `drawable` prop | `md` prop (Material Symbols) |
|
|
16
|
+
|
|
17
|
+
All examples below use SDK 55 syntax. For SDK 54, replace `NativeTabs.Trigger.Icon/Label/Badge` with standalone `Icon`, `Label`, `Badge` imports.
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { NativeTabs } from "expo-router/unstable-native-tabs";
|
|
23
|
+
|
|
24
|
+
export default function TabLayout() {
|
|
25
|
+
return (
|
|
26
|
+
<NativeTabs minimizeBehavior="onScrollDown">
|
|
27
|
+
<NativeTabs.Trigger name="index">
|
|
28
|
+
<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
|
|
29
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
30
|
+
<NativeTabs.Trigger.Badge>9+</NativeTabs.Trigger.Badge>
|
|
31
|
+
</NativeTabs.Trigger>
|
|
32
|
+
<NativeTabs.Trigger name="settings">
|
|
33
|
+
<NativeTabs.Trigger.Icon sf="gear" md="settings" />
|
|
34
|
+
<NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
|
|
35
|
+
</NativeTabs.Trigger>
|
|
36
|
+
<NativeTabs.Trigger name="(search)" role="search">
|
|
37
|
+
<NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
|
|
38
|
+
</NativeTabs.Trigger>
|
|
39
|
+
</NativeTabs>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Rules
|
|
45
|
+
|
|
46
|
+
- You must include a trigger for each tab
|
|
47
|
+
- The `NativeTabs.Trigger` 'name' must match the route name, including parentheses (e.g. `<NativeTabs.Trigger name="(search)">`)
|
|
48
|
+
- Prefer search tab to be last in the list so it can combine with the search bar
|
|
49
|
+
- Use the 'role' prop for common tab types
|
|
50
|
+
- Tabs must be static — no dynamic addition/removal at runtime (remounts navigator, loses state)
|
|
51
|
+
|
|
52
|
+
## Platform Features
|
|
53
|
+
|
|
54
|
+
Native Tabs use platform-specific tab bar implementations:
|
|
55
|
+
|
|
56
|
+
- **iOS 26+**: Liquid glass effects with system-native appearance
|
|
57
|
+
- **Android**: Material 3 bottom navigation
|
|
58
|
+
- Better performance and native feel
|
|
59
|
+
|
|
60
|
+
## Icon Component
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// SF Symbol (iOS) + Material Symbol (Android)
|
|
64
|
+
<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
|
|
65
|
+
|
|
66
|
+
// State variants
|
|
67
|
+
<NativeTabs.Trigger.Icon sf={{ default: "house", selected: "house.fill" }} md="home" />
|
|
68
|
+
|
|
69
|
+
// Custom image
|
|
70
|
+
<NativeTabs.Trigger.Icon src={require('./icon.png')} />
|
|
71
|
+
|
|
72
|
+
// Xcode asset catalog — iOS only (SDK 55+)
|
|
73
|
+
<NativeTabs.Trigger.Icon xcasset="home-icon" />
|
|
74
|
+
<NativeTabs.Trigger.Icon xcasset={{ default: "home-outline", selected: "home-filled" }} />
|
|
75
|
+
|
|
76
|
+
// Rendering mode — iOS only (SDK 55+)
|
|
77
|
+
<NativeTabs.Trigger.Icon src={require('./icon.png')} renderingMode="template" />
|
|
78
|
+
<NativeTabs.Trigger.Icon src={require('./gradient.png')} renderingMode="original" />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`renderingMode`: `"template"` applies tint color (single-color icons), `"original"` preserves source colors (gradients). Android always uses original.
|
|
82
|
+
|
|
83
|
+
## Label & Badge
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Label
|
|
87
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
88
|
+
<NativeTabs.Trigger.Label hidden>Home</NativeTabs.Trigger.Label> {/* icon-only tab */}
|
|
89
|
+
|
|
90
|
+
// Badge
|
|
91
|
+
<NativeTabs.Trigger.Badge>9+</NativeTabs.Trigger.Badge>
|
|
92
|
+
<NativeTabs.Trigger.Badge /> {/* dot indicator */}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## iOS 26 Features
|
|
96
|
+
|
|
97
|
+
### Liquid Glass Tab Bar
|
|
98
|
+
|
|
99
|
+
The tab bar automatically adopts liquid glass appearance on iOS 26+.
|
|
100
|
+
|
|
101
|
+
### Minimize on Scroll
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<NativeTabs minimizeBehavior="onScrollDown">
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Search Tab
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<NativeTabs.Trigger name="(search)" role="search">
|
|
111
|
+
<NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
|
|
112
|
+
</NativeTabs.Trigger>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Note**: Place search tab last for best UX.
|
|
116
|
+
|
|
117
|
+
### Role Prop
|
|
118
|
+
|
|
119
|
+
Use semantic roles for special tab types:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
<NativeTabs.Trigger name="search" role="search" />
|
|
123
|
+
<NativeTabs.Trigger name="favorites" role="favorites" />
|
|
124
|
+
<NativeTabs.Trigger name="more" role="more" />
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Available roles: `search` | `more` | `favorites` | `bookmarks` | `contacts` | `downloads` | `featured` | `history` | `mostRecent` | `mostViewed` | `recents` | `topRated`
|
|
128
|
+
|
|
129
|
+
## Customization
|
|
130
|
+
|
|
131
|
+
### Tint Color
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
<NativeTabs tintColor="#007AFF">
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Dynamic Colors (iOS)
|
|
138
|
+
|
|
139
|
+
Use DynamicColorIOS for colors that adapt to liquid glass:
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import { DynamicColorIOS, Platform } from 'react-native';
|
|
143
|
+
|
|
144
|
+
const adaptiveBlue = Platform.select({
|
|
145
|
+
ios: DynamicColorIOS({ light: '#007AFF', dark: '#0A84FF' }),
|
|
146
|
+
default: '#007AFF',
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
<NativeTabs tintColor={adaptiveBlue}>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Conditional Tabs
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<NativeTabs.Trigger name="admin" hidden={!isAdmin}>
|
|
156
|
+
<NativeTabs.Trigger.Label>Admin</NativeTabs.Trigger.Label>
|
|
157
|
+
<NativeTabs.Trigger.Icon sf="shield.fill" md="shield" />
|
|
158
|
+
</NativeTabs.Trigger>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Don't hide the tabs when they are visible - toggling visibility remounts the navigator; Do it only during the initial render.**
|
|
162
|
+
|
|
163
|
+
**Note**: Hidden tabs cannot be navigated to!
|
|
164
|
+
|
|
165
|
+
## Behavior Options
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<NativeTabs.Trigger
|
|
169
|
+
name="home"
|
|
170
|
+
disablePopToTop // Don't pop stack when tapping active tab
|
|
171
|
+
disableScrollToTop // Don't scroll to top when tapping active tab
|
|
172
|
+
disableAutomaticContentInsets // Opt out of automatic safe area insets (SDK 55+)
|
|
173
|
+
>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Hidden Tab Bar (SDK 55+)
|
|
177
|
+
|
|
178
|
+
Use `hidden` prop on `NativeTabs` to hide the entire tab bar dynamically:
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
<NativeTabs hidden={isTabBarHidden}>{/* triggers */}</NativeTabs>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Bottom Accessory (SDK 55+)
|
|
185
|
+
|
|
186
|
+
`NativeTabs.BottomAccessory` renders content above the tab bar (iOS 26+). Uses `usePlacement()` to adapt between `'regular'` and `'inline'` layouts.
|
|
187
|
+
|
|
188
|
+
**Important**: Two instances render simultaneously — store state outside the component (props, context, or external store).
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { NativeTabs } from "expo-router/unstable-native-tabs";
|
|
192
|
+
import { useState } from "react";
|
|
193
|
+
import { Pressable, Text, View } from "react-native";
|
|
194
|
+
|
|
195
|
+
function MiniPlayer({
|
|
196
|
+
isPlaying,
|
|
197
|
+
onToggle,
|
|
198
|
+
}: {
|
|
199
|
+
isPlaying: boolean;
|
|
200
|
+
onToggle: () => void;
|
|
201
|
+
}) {
|
|
202
|
+
const placement = NativeTabs.BottomAccessory.usePlacement();
|
|
203
|
+
if (placement === "inline") {
|
|
204
|
+
return (
|
|
205
|
+
<Pressable onPress={onToggle}>
|
|
206
|
+
<SymbolView name={isPlaying ? "pause.fill" : "play.fill"} />
|
|
207
|
+
</Pressable>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return <View>{/* full player UI */}</View>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default function TabLayout() {
|
|
214
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
215
|
+
return (
|
|
216
|
+
<NativeTabs>
|
|
217
|
+
<NativeTabs.BottomAccessory>
|
|
218
|
+
<MiniPlayer
|
|
219
|
+
isPlaying={isPlaying}
|
|
220
|
+
onToggle={() => setIsPlaying(!isPlaying)}
|
|
221
|
+
/>
|
|
222
|
+
</NativeTabs.BottomAccessory>
|
|
223
|
+
<NativeTabs.Trigger name="index">
|
|
224
|
+
<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
|
|
225
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
226
|
+
</NativeTabs.Trigger>
|
|
227
|
+
</NativeTabs>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Safe Area Handling (SDK 55+)
|
|
233
|
+
|
|
234
|
+
SDK 55 handles safe areas automatically:
|
|
235
|
+
|
|
236
|
+
- **Android**: Content wrapped in SafeAreaView (bottom inset)
|
|
237
|
+
- **iOS**: First ScrollView gets automatic `contentInsetAdjustmentBehavior`
|
|
238
|
+
|
|
239
|
+
To opt out per-tab, use `disableAutomaticContentInsets` and manage manually:
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
<NativeTabs.Trigger name="index" disableAutomaticContentInsets>
|
|
243
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
244
|
+
</NativeTabs.Trigger>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
// In the screen
|
|
249
|
+
import { SafeAreaView } from "react-native-screens/experimental";
|
|
250
|
+
|
|
251
|
+
export default function HomeScreen() {
|
|
252
|
+
return (
|
|
253
|
+
<SafeAreaView edges={{ bottom: true }} style={{ flex: 1 }}>
|
|
254
|
+
{/* content */}
|
|
255
|
+
</SafeAreaView>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Using Vector Icons
|
|
261
|
+
|
|
262
|
+
If you must use @expo/vector-icons instead of SF Symbols:
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
import { NativeTabs } from "expo-router/unstable-native-tabs";
|
|
266
|
+
import Ionicons from "@expo/vector-icons/Ionicons";
|
|
267
|
+
|
|
268
|
+
<NativeTabs.Trigger name="home">
|
|
269
|
+
<NativeTabs.Trigger.VectorIcon vector={Ionicons} name="home" />
|
|
270
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
271
|
+
</NativeTabs.Trigger>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Prefer SF Symbols + `md` prop over vector icons for native feel.**
|
|
275
|
+
|
|
276
|
+
If you are using SDK 55 and later **use the md prop to specify Material Symbols used on Android**.
|
|
277
|
+
|
|
278
|
+
## Structure with Stacks
|
|
279
|
+
|
|
280
|
+
Native tabs don't render headers. Nest Stacks inside each tab for navigation headers:
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
// app/(tabs)/_layout.tsx
|
|
284
|
+
import { NativeTabs } from "expo-router/unstable-native-tabs";
|
|
285
|
+
|
|
286
|
+
export default function TabLayout() {
|
|
287
|
+
return (
|
|
288
|
+
<NativeTabs>
|
|
289
|
+
<NativeTabs.Trigger name="(home)">
|
|
290
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
291
|
+
<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
|
|
292
|
+
</NativeTabs.Trigger>
|
|
293
|
+
</NativeTabs>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// app/(tabs)/(home)/_layout.tsx
|
|
298
|
+
import Stack from "expo-router/stack";
|
|
299
|
+
|
|
300
|
+
export default function HomeStack() {
|
|
301
|
+
return (
|
|
302
|
+
<Stack>
|
|
303
|
+
<Stack.Screen
|
|
304
|
+
name="index"
|
|
305
|
+
options={{ title: "Home", headerLargeTitle: true }}
|
|
306
|
+
/>
|
|
307
|
+
<Stack.Screen name="details" options={{ title: "Details" }} />
|
|
308
|
+
</Stack>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Custom Web Layout
|
|
314
|
+
|
|
315
|
+
Use platform-specific files for separate native and web tab layouts:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
app/
|
|
319
|
+
_layout.tsx # NativeTabs for iOS/Android
|
|
320
|
+
_layout.web.tsx # Headless tabs for web (expo-router/ui)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Or extract to a component: `components/app-tabs.tsx` + `components/app-tabs.web.tsx`.
|
|
324
|
+
|
|
325
|
+
## Migration from JS Tabs
|
|
326
|
+
|
|
327
|
+
### Before (JS Tabs)
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
import { Tabs } from "expo-router";
|
|
331
|
+
|
|
332
|
+
<Tabs>
|
|
333
|
+
<Tabs.Screen
|
|
334
|
+
name="index"
|
|
335
|
+
options={{
|
|
336
|
+
title: "Home",
|
|
337
|
+
tabBarIcon: ({ color }) => <IconSymbol name="house.fill" color={color} />,
|
|
338
|
+
tabBarBadge: 3,
|
|
339
|
+
}}
|
|
340
|
+
/>
|
|
341
|
+
</Tabs>;
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### After (Native Tabs)
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
import { NativeTabs } from "expo-router/unstable-native-tabs";
|
|
348
|
+
|
|
349
|
+
<NativeTabs>
|
|
350
|
+
<NativeTabs.Trigger name="index">
|
|
351
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
352
|
+
<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
|
|
353
|
+
<NativeTabs.Trigger.Badge>3</NativeTabs.Trigger.Badge>
|
|
354
|
+
</NativeTabs.Trigger>
|
|
355
|
+
</NativeTabs>;
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Key Differences
|
|
359
|
+
|
|
360
|
+
| JS Tabs | Native Tabs |
|
|
361
|
+
| -------------------------- | ---------------------------- |
|
|
362
|
+
| `<Tabs.Screen>` | `<NativeTabs.Trigger>` |
|
|
363
|
+
| `options={{ title }}` | `<NativeTabs.Trigger.Label>` |
|
|
364
|
+
| `options={{ tabBarIcon }}` | `<NativeTabs.Trigger.Icon>` |
|
|
365
|
+
| `tabBarBadge` option | `<NativeTabs.Trigger.Badge>` |
|
|
366
|
+
| Props-based API | Component-based API |
|
|
367
|
+
| Headers built-in | Nest `<Stack>` for headers |
|
|
368
|
+
|
|
369
|
+
## Limitations
|
|
370
|
+
|
|
371
|
+
- **Android**: Maximum 5 tabs (Material Design constraint)
|
|
372
|
+
- **Nesting**: Native tabs cannot nest inside other native tabs
|
|
373
|
+
- **Tab bar height**: Cannot be measured programmatically
|
|
374
|
+
- **FlatList transparency**: Use `disableTransparentOnScrollEdge` to fix issues
|
|
375
|
+
- **Dynamic tabs**: Tabs must be static; changes remount navigator and lose state
|
|
376
|
+
|
|
377
|
+
## Keyboard Handling (Android)
|
|
378
|
+
|
|
379
|
+
Configure in app.json:
|
|
380
|
+
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"expo": {
|
|
384
|
+
"android": {
|
|
385
|
+
"softwareKeyboardLayoutMode": "resize"
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Common Issues
|
|
392
|
+
|
|
393
|
+
1. **Icons not showing on Android**: Add `md` prop (SDK 55) or use VectorIcon
|
|
394
|
+
2. **Headers missing**: Nest a Stack inside each tab group
|
|
395
|
+
3. **Trigger name mismatch**: `name` must match exact route name including parentheses
|
|
396
|
+
4. **Badge not visible**: Badge must be a child of Trigger, not a prop
|
|
397
|
+
5. **Tab bar transparent on iOS 18 and earlier**: If the screen uses a `ScrollView` or `FlatList`, make sure it is the first opaque child of the screen component. If it needs to be wrapped in another `View`, ensure the wrapper uses `collapsable={false}`. If the screen does not use a `ScrollView` or `FlatList`, set `disableTransparentOnScrollEdge` to `true` in the `NativeTabs.Trigger` options, to make the tab bar opaque.
|
|
398
|
+
6. **Scroll to top not working**: Ensure `disableScrollToTop` is not set on the active tab's Trigger and `ScrollView` is the first child of the screen component.
|
|
399
|
+
7. **Header buttons flicker when navigating between tabs**: Make sure the app is wrapped in a `ThemeProvider`
|
|
400
|
+
|
|
401
|
+
```tsx
|
|
402
|
+
import {
|
|
403
|
+
ThemeProvider,
|
|
404
|
+
DarkTheme,
|
|
405
|
+
DefaultTheme,
|
|
406
|
+
} from "@react-navigation/native";
|
|
407
|
+
import { useColorScheme } from "react-native";
|
|
408
|
+
import { Stack } from "expo-router";
|
|
409
|
+
|
|
410
|
+
export default function Layout() {
|
|
411
|
+
const colorScheme = useColorScheme();
|
|
412
|
+
return (
|
|
413
|
+
<ThemeProvider theme={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
|
|
414
|
+
<Stack />
|
|
415
|
+
</ThemeProvider>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
If the app only uses a light or dark theme, you can directly pass `DarkTheme` or `DefaultTheme` to `ThemeProvider` without checking the color scheme.
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
import { ThemeProvider, DarkTheme } from "@react-navigation/native";
|
|
424
|
+
import { Stack } from "expo-router";
|
|
425
|
+
|
|
426
|
+
export default function Layout() {
|
|
427
|
+
return (
|
|
428
|
+
<ThemeProvider theme={DarkTheme}>
|
|
429
|
+
<Stack />
|
|
430
|
+
</ThemeProvider>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
```
|