Expo @expo/ui SwiftUI Best Practices
Library reference for @expo/ui/swift-ui and @expo/ui/swift-ui/modifiers — the iOS surface of Expo's native UI bridge. Contains 53 rules across 8 categories, prioritised by cascade impact for agents building Expo apps that render to native SwiftUI views on iOS 26 and earlier.
When to Apply
Reference these guidelines when:
- Building a new screen with
@expo/ui/swift-ui — pick the right container (Form vs List vs ScrollView), wrap in Host correctly, apply modifiers
- Migrating from React Native primitives (View, Text, TouchableOpacity) to native SwiftUI components
- Targeting iOS 26 features — Liquid Glass material, GlassEffectContainer, new sheet detent behaviours
- Reviewing code that imports from
@expo/ui/swift-ui or @expo/ui/swift-ui/modifiers
- Debugging "the SwiftUI view doesn't render / is the wrong size / ignores styles" — usually a Host or modifier issue
- Composing presentation surfaces — Alert, ConfirmationDialog, BottomSheet, Popover — under HIG modality guidance
- Writing controlled inputs (TextField, Toggle, Picker, Slider) with
useNativeState and worklet writes
When NOT to Use This Skill
- Android Jetpack Compose — this skill covers iOS SwiftUI only. The
@expo/ui/jetpack-compose surface has its own conventions
- Universal (cross-platform) components —
@expo/ui exposes a small set; this skill scopes to the platform-specific iOS surface
- Navigation routing — for stack/tab routing, use
expo-router and expo-router/unstable-native-tabs; this skill covers UI composition only
- Pre-iOS-17 fallbacks — most rules assume iOS 17 minimum; Liquid Glass rules require iOS 26
Rule Categories by Priority
| Priority |
Category |
Impact |
Prefix |
| 1 |
Setup & Host Boundaries |
CRITICAL |
host- |
| 2 |
iOS 26 HIG Composition Rules |
CRITICAL |
hig- |
| 3 |
Modifiers System |
CRITICAL |
mod- |
| 4 |
Layout Components |
HIGH |
layout- |
| 5 |
Input & Controls |
HIGH |
input- |
| 6 |
Navigation & Overlays |
HIGH |
nav- |
| 7 |
Display & Feedback |
MEDIUM-HIGH |
display- |
| 8 |
State & Cross-Cutting Patterns |
MEDIUM |
state- |
Quick Reference
1. Setup & Host Boundaries (CRITICAL)
host-wrap-all-swiftui-roots — Wrap every SwiftUI subtree in a Host
host-match-contents — Size Host to its SwiftUI content with matchContents
host-viewport-size-for-form — Use useViewportSizeMeasurement for Form and List
host-color-scheme-explicit — Pass explicit colorScheme when overriding the system
host-ignore-safe-area — Use ignoreSafeArea only for full-bleed surfaces
2. iOS 26 HIG Composition Rules (CRITICAL)
hig-glass-effect-container — Group glass siblings inside GlassEffectContainer
hig-no-glass-on-glass — Avoid nesting glassEffect on glass surfaces
hig-no-stacked-modals — Resolve a sheet before presenting another
hig-popover-iphone-fallback — Don't use Popover on iPhone — use BottomSheet
hig-sheet-detents-partial — Include a partial detent for Liquid Glass appearance
hig-confirmation-dialog-destructive — ConfirmationDialog + destructive role
hig-tint-only-for-brand — Reserve tint for brand surfaces, not destructive
3. Modifiers System (CRITICAL)
mod-prop-not-style — Modifiers go through the modifiers prop, not RN style
mod-composition-order — Modifier order is meaningful — each wraps the previous
mod-import-from-modifiers-subpath — Import from @expo/ui/swift-ui/modifiers
mod-frame-vs-fixedsize — frame proposes a size, fixedSize opts out of flex
mod-padding-vs-frame — padding for inner space, frame for outer bounds
mod-presentation-on-sheet-content — Presentation modifiers attach to sheet content
mod-disabled-prop — Use disabled modifier, don't conditionally render
mod-animation-wraps-trigger — withAnimation wraps state-driven prop changes
4. Layout Components (HIGH)
layout-hstack-vs-vstack — Pick stack direction by content flow
layout-lazy-stack-for-long-lists — LazyVStack inside ScrollView for long lists
layout-form-for-settings — Form adopts iOS grouped chrome automatically
layout-section-with-header-footer — Use Section header/footer slots
layout-scrollview-axes — Set axes explicitly for horizontal/2D scroll
layout-grid-vs-stack — Grid for column-aligned content
5. Input & Controls (HIGH)
input-button-role-for-destructive — Set role='destructive' for delete buttons
input-button-systemimage — Use systemImage SF Symbol for button icons
input-textfield-observable-state — useNativeState for TextField, not React state
input-securefield-for-passwords — SecureField for passwords, not TextField
input-toggle-on-async — SyncToggle for instant flicks, Toggle for async
input-picker-style-via-modifier — pickerStyle modifier picks appearance
input-date-picker-range — Constrain selectable dates with range
input-stepper-bounded — Provide min and max on Stepper
6. Navigation & Overlays (HIGH)
nav-alert-for-critical-only — Alert for blocking notifications only
nav-context-menu-vs-swipe — ContextMenu or SwipeActions per row, not both
nav-bottom-sheet-via-group — Wrap BottomSheet content in Group
nav-share-link-system — ShareLink for the system share sheet
nav-tabview-style-modifier — tabViewStyle modifier picks appearance
nav-disclosure-group-collapsible — DisclosureGroup for collapsible sections
nav-link-not-button-for-urls — Link for URLs, Button for in-app actions
nav-menu-primary-action — onPrimaryAction disambiguates tap from long-press
7. Display & Feedback (MEDIUM-HIGH)
display-text-markdown — Enable markdownEnabled for inline rich text
display-image-system-name — Prefer systemName SF Symbols over uiImage
display-chart-data-points — ChartDataPoint arrays drive native axes
display-gauge-current-value-label — Provide currentValueLabel for accessibility
display-progress-indeterminate — Undefined value → spinner, 0 → frozen bar
display-label-icon-vs-title — systemImage for SF Symbols, icon slot for custom
8. State & Cross-Cutting Patterns (MEDIUM)
state-use-native-state-for-fields — useNativeState for every bridged input
state-worklet-writes — Update ObservableState from worklets
state-controlled-via-selection-prop — selection or defaultSelection, not both
state-platform-check-pre-26 — Guard iOS 26-only features with version check
state-textfield-ref-imperative — TextFieldRef for focus and selection
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions — Category structure and impact levels
- Rule template — Template for adding new rules
- Reference files:
references/{prefix}-{slug}.md
Each rule file contains:
- Brief explanation of why it matters in the SwiftUI bridge
- Incorrect code example anchored to a realistic domain
- Correct code example with a minimal diff from the incorrect one
- Where relevant: Alternative approach, When NOT to use, Warning callouts, authoritative reference URL
Gotchas
See gotchas.md — append entries as failure points surface during real use.
Related Skills
- For Android Jetpack Compose components, the parallel skill would target
@expo/ui/jetpack-compose
- For navigation routing (stack, tabs), use
expo-router directly
- For form validation libraries, see the
react-hook-form skill
1---2name: expo-ui3description: Library reference for @expo/ui SwiftUI components on iOS — covers Host boundaries, modifier composition, iOS 26 Liquid Glass and Human Interface Guidelines composition rules, layout/input/navigation/display catalogues, and ObservableState patterns. Use this skill whenever writing or reviewing React Native code that imports from @expo/ui/swift-ui or @expo/ui/swift-ui/modifiers — including new Expo apps adopting native SwiftUI views, migrations from React Native primitives to expo-ui, and code targeting iOS 26 features (Liquid Glass, GlassEffectContainer, sheet detents). Trigger even if the user does not explicitly mention "expo-ui" but is writing iOS-targeted Expo UI code that should bridge to SwiftUI.4---5
6# Expo @expo/ui SwiftUI Best Practices
7
8Library reference for `@expo/ui/swift-ui` and `@expo/ui/swift-ui/modifiers` — the iOS surface of Expo's native UI bridge. Contains 53 rules across 8 categories, prioritised by cascade impact for agents building Expo apps that render to native SwiftUI views on iOS 26 and earlier.
9
10## When to Apply
11
12Reference these guidelines when:
13
14- Building a new screen with `@expo/ui/swift-ui` — pick the right container (Form vs List vs ScrollView), wrap in Host correctly, apply modifiers
15- Migrating from React Native primitives (View, Text, TouchableOpacity) to native SwiftUI components
16- Targeting iOS 26 features — Liquid Glass material, GlassEffectContainer, new sheet detent behaviours
17- Reviewing code that imports from `@expo/ui/swift-ui` or `@expo/ui/swift-ui/modifiers`
18- Debugging "the SwiftUI view doesn't render / is the wrong size / ignores styles" — usually a Host or modifier issue
19- Composing presentation surfaces — Alert, ConfirmationDialog, BottomSheet, Popover — under HIG modality guidance
20- Writing controlled inputs (TextField, Toggle, Picker, Slider) with `useNativeState` and worklet writes
21
22## When NOT to Use This Skill
23
24- **Android Jetpack Compose** — this skill covers iOS SwiftUI only. The `@expo/ui/jetpack-compose` surface has its own conventions
25- **Universal (cross-platform) components** — `@expo/ui` exposes a small set; this skill scopes to the platform-specific iOS surface
26- **Navigation routing** — for stack/tab routing, use `expo-router` and `expo-router/unstable-native-tabs`; this skill covers UI composition only
27- **Pre-iOS-17 fallbacks** — most rules assume iOS 17 minimum; Liquid Glass rules require iOS 26
28
29## Rule Categories by Priority
30
31| Priority | Category | Impact | Prefix |
32|----------|----------|--------|--------|
33| 1 | Setup & Host Boundaries | CRITICAL | `host-` |
34| 2 | iOS 26 HIG Composition Rules | CRITICAL | `hig-` |
35| 3 | Modifiers System | CRITICAL | `mod-` |
36| 4 | Layout Components | HIGH | `layout-` |
37| 5 | Input & Controls | HIGH | `input-` |
38| 6 | Navigation & Overlays | HIGH | `nav-` |
39| 7 | Display & Feedback | MEDIUM-HIGH | `display-` |
40| 8 | State & Cross-Cutting Patterns | MEDIUM | `state-` |
41
42## Quick Reference
43
44### 1. Setup & Host Boundaries (CRITICAL)
45
46- [`host-wrap-all-swiftui-roots`](references/host-wrap-all-swiftui-roots.md) — Wrap every SwiftUI subtree in a Host
47- [`host-match-contents`](references/host-match-contents.md) — Size Host to its SwiftUI content with matchContents
48- [`host-viewport-size-for-form`](references/host-viewport-size-for-form.md) — Use useViewportSizeMeasurement for Form and List
49- [`host-color-scheme-explicit`](references/host-color-scheme-explicit.md) — Pass explicit colorScheme when overriding the system
50- [`host-ignore-safe-area`](references/host-ignore-safe-area.md) — Use ignoreSafeArea only for full-bleed surfaces
51
52### 2. iOS 26 HIG Composition Rules (CRITICAL)
53
54- [`hig-glass-effect-container`](references/hig-glass-effect-container.md) — Group glass siblings inside GlassEffectContainer
55- [`hig-no-glass-on-glass`](references/hig-no-glass-on-glass.md) — Avoid nesting glassEffect on glass surfaces
56- [`hig-no-stacked-modals`](references/hig-no-stacked-modals.md) — Resolve a sheet before presenting another
57- [`hig-popover-iphone-fallback`](references/hig-popover-iphone-fallback.md) — Don't use Popover on iPhone — use BottomSheet
58- [`hig-sheet-detents-partial`](references/hig-sheet-detents-partial.md) — Include a partial detent for Liquid Glass appearance
59- [`hig-confirmation-dialog-destructive`](references/hig-confirmation-dialog-destructive.md) — ConfirmationDialog + destructive role
60- [`hig-tint-only-for-brand`](references/hig-tint-only-for-brand.md) — Reserve tint for brand surfaces, not destructive
61
62### 3. Modifiers System (CRITICAL)
63
64- [`mod-prop-not-style`](references/mod-prop-not-style.md) — Modifiers go through the `modifiers` prop, not RN style
65- [`mod-composition-order`](references/mod-composition-order.md) — Modifier order is meaningful — each wraps the previous
66- [`mod-import-from-modifiers-subpath`](references/mod-import-from-modifiers-subpath.md) — Import from `@expo/ui/swift-ui/modifiers`
67- [`mod-frame-vs-fixedsize`](references/mod-frame-vs-fixedsize.md) — frame proposes a size, fixedSize opts out of flex
68- [`mod-padding-vs-frame`](references/mod-padding-vs-frame.md) — padding for inner space, frame for outer bounds
69- [`mod-presentation-on-sheet-content`](references/mod-presentation-on-sheet-content.md) — Presentation modifiers attach to sheet content
70- [`mod-disabled-prop`](references/mod-disabled-prop.md) — Use disabled modifier, don't conditionally render
71- [`mod-animation-wraps-trigger`](references/mod-animation-wraps-trigger.md) — withAnimation wraps state-driven prop changes
72
73### 4. Layout Components (HIGH)
74
75- [`layout-hstack-vs-vstack`](references/layout-hstack-vs-vstack.md) — Pick stack direction by content flow
76- [`layout-lazy-stack-for-long-lists`](references/layout-lazy-stack-for-long-lists.md) — LazyVStack inside ScrollView for long lists
77- [`layout-form-for-settings`](references/layout-form-for-settings.md) — Form adopts iOS grouped chrome automatically
78- [`layout-section-with-header-footer`](references/layout-section-with-header-footer.md) — Use Section header/footer slots
79- [`layout-scrollview-axes`](references/layout-scrollview-axes.md) — Set axes explicitly for horizontal/2D scroll
80- [`layout-grid-vs-stack`](references/layout-grid-vs-stack.md) — Grid for column-aligned content
81
82### 5. Input & Controls (HIGH)
83
84- [`input-button-role-for-destructive`](references/input-button-role-for-destructive.md) — Set role='destructive' for delete buttons
85- [`input-button-systemimage`](references/input-button-systemimage.md) — Use systemImage SF Symbol for button icons
86- [`input-textfield-observable-state`](references/input-textfield-observable-state.md) — useNativeState for TextField, not React state
87- [`input-securefield-for-passwords`](references/input-securefield-for-passwords.md) — SecureField for passwords, not TextField
88- [`input-toggle-on-async`](references/input-toggle-on-async.md) — SyncToggle for instant flicks, Toggle for async
89- [`input-picker-style-via-modifier`](references/input-picker-style-via-modifier.md) — pickerStyle modifier picks appearance
90- [`input-date-picker-range`](references/input-date-picker-range.md) — Constrain selectable dates with range
91- [`input-stepper-bounded`](references/input-stepper-bounded.md) — Provide min and max on Stepper
92
93### 6. Navigation & Overlays (HIGH)
94
95- [`nav-alert-for-critical-only`](references/nav-alert-for-critical-only.md) — Alert for blocking notifications only
96- [`nav-context-menu-vs-swipe`](references/nav-context-menu-vs-swipe.md) — ContextMenu or SwipeActions per row, not both
97- [`nav-bottom-sheet-via-group`](references/nav-bottom-sheet-via-group.md) — Wrap BottomSheet content in Group
98- [`nav-share-link-system`](references/nav-share-link-system.md) — ShareLink for the system share sheet
99- [`nav-tabview-style-modifier`](references/nav-tabview-style-modifier.md) — tabViewStyle modifier picks appearance
100- [`nav-disclosure-group-collapsible`](references/nav-disclosure-group-collapsible.md) — DisclosureGroup for collapsible sections
101- [`nav-link-not-button-for-urls`](references/nav-link-not-button-for-urls.md) — Link for URLs, Button for in-app actions
102- [`nav-menu-primary-action`](references/nav-menu-primary-action.md) — onPrimaryAction disambiguates tap from long-press
103
104### 7. Display & Feedback (MEDIUM-HIGH)
105
106- [`display-text-markdown`](references/display-text-markdown.md) — Enable markdownEnabled for inline rich text
107- [`display-image-system-name`](references/display-image-system-name.md) — Prefer systemName SF Symbols over uiImage
108- [`display-chart-data-points`](references/display-chart-data-points.md) — ChartDataPoint arrays drive native axes
109- [`display-gauge-current-value-label`](references/display-gauge-current-value-label.md) — Provide currentValueLabel for accessibility
110- [`display-progress-indeterminate`](references/display-progress-indeterminate.md) — Undefined value → spinner, 0 → frozen bar
111- [`display-label-icon-vs-title`](references/display-label-icon-vs-title.md) — systemImage for SF Symbols, icon slot for custom
112
113### 8. State & Cross-Cutting Patterns (MEDIUM)
114
115- [`state-use-native-state-for-fields`](references/state-use-native-state-for-fields.md) — useNativeState for every bridged input
116- [`state-worklet-writes`](references/state-worklet-writes.md) — Update ObservableState from worklets
117- [`state-controlled-via-selection-prop`](references/state-controlled-via-selection-prop.md) — selection or defaultSelection, not both
118- [`state-platform-check-pre-26`](references/state-platform-check-pre-26.md) — Guard iOS 26-only features with version check
119- [`state-textfield-ref-imperative`](references/state-textfield-ref-imperative.md) — TextFieldRef for focus and selection
120
121## How to Use
122
123Read individual reference files for detailed explanations and code examples:
124
125- [Section definitions](references/_sections.md) — Category structure and impact levels
126- [Rule template](assets/templates/_template.md) — Template for adding new rules
127- Reference files: `references/{prefix}-{slug}.md`
128
129Each rule file contains:
130- Brief explanation of why it matters in the SwiftUI bridge
131- Incorrect code example anchored to a realistic domain
132- Correct code example with a minimal diff from the incorrect one
133- Where relevant: Alternative approach, When NOT to use, Warning callouts, authoritative reference URL
134
135## Gotchas
136
137See [gotchas.md](gotchas.md) — append entries as failure points surface during real use.
138
139## Related Skills
140
141- For Android Jetpack Compose components, the parallel skill would target `@expo/ui/jetpack-compose`
142- For navigation routing (stack, tabs), use `expo-router` directly
143- For form validation libraries, see the `react-hook-form` skill