OneKey UI Recipes
Bite-sized solutions for common UI issues.
Quick Reference
Recipe Guide Key Points
iOS Tab Bar Scroll Offset ios-tab-bar-scroll-offset.md Use useScrollContentTabBarOffset for paddingBottom on iOS tab pages
Smooth State Transitions start-view-transition.md Wrap heavy state updates in startViewTransition for fade on web
Horizontal Scroll in Collapsible Tab Headers collapsible-tab-horizontal-scroll.md Bidirectional Gesture.Pan()
- programmatic scrollTo via CollapsibleTabContext
Android Bottom Tab Touch Interception android-bottom-tab-touch-intercept.md Temporary — GestureDetector
- Gesture.Tap() in .android.tsx to bypass native tab bar touch stealing
Keyboard Avoidance for Input Fields keyboard-avoidance.md KeyboardAwareScrollView auto-scroll, Footer animated padding, useKeyboardHeight / useKeyboardEvent hooks
iOS Overlay Navigation Freeze ios-overlay-navigation-freeze.md Use resetAboveMainRoute() instead of sequential goBack() to close overlays before navigating
Critical Rules Summary
- iOS Tab Bar Scroll Content Offset
Use useScrollContentTabBarOffset to add dynamic paddingBottom to scroll containers inside tab pages. Returns tab bar height on iOS, undefined on other platforms.
import { useScrollContentTabBarOffset } from '@onekeyhq/components';
const tabBarHeight = useScrollContentTabBarOffset(); <ScrollView contentContainerStyle={{ paddingBottom: tabBarHeight }} />
- Smooth State Transitions with startViewTransition
Wrap heavy state updates in startViewTransition — fade on web/desktop via View Transition API, setTimeout fallback on native.
import { startViewTransition } from '@onekeyhq/components';
startViewTransition(() => { setIsReady(true); });
- Horizontal Scroll in Collapsible Tab Headers (Native)
When placing a horizontal scroller inside renderHeader of collapsible tabs, use Gesture.Pan() that handles both directions — horizontal drives translateX , vertical calls scrollTo on the focused tab's ScrollView via CollapsibleTabContext .
import { CollapsibleTabContext } from '@onekeyhq/components';
Do NOT import directly from react-native-collapsible-tab-view/src/Context . Always use the @onekeyhq/components re-export.
- Android Bottom Tab Touch Interception (Temporary Workaround)
Temporary fix — the root cause is react-native-bottom-tabs intercepting touches even when hidden. This workaround should be removed once the upstream issue is fixed.
On Android, react-native-bottom-tabs intercepts touches in the tab bar region even when the tab bar is GONE . Buttons near the bottom of the screen become unclickable. Fix by creating a .android.tsx variant that wraps buttons with GestureDetector
- Gesture.Tap() :
import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { runOnJS } from 'react-native-reanimated';
const tapGesture = useMemo( () => Gesture.Tap().onEnd(() => { 'worklet'; runOnJS(onPress)(); }), [onPress], );
<GestureDetector gesture={tapGesture}> <View> <Button>Label</Button> </View> </GestureDetector>
Use .android.tsx file extension so other platforms are unaffected.
- Keyboard Avoidance for Input Fields
Standard Page and Dialog components handle keyboard avoidance automatically. Only add manual handling for custom layouts.
-
Page inputs: Automatic — PageContainer wraps with KeyboardAwareScrollView (90px bottomOffset )
-
Page Footer: Automatic — animates paddingBottom via useReanimatedKeyboardAnimation
-
Dialog: Automatic — keyboard avoidance is handled at the Dialog level for all dialogs (including showFooter: false )
-
Custom layout: Use Keyboard.AwareScrollView with custom bottomOffset
import { Keyboard } from '@onekeyhq/components';
// Custom scrollable area with keyboard avoidance <Keyboard.AwareScrollView bottomOffset={150}> {/* inputs */} </Keyboard.AwareScrollView>
// Dismiss keyboard before navigation await Keyboard.dismissWithDelay();
Hooks for custom behavior:
import { useKeyboardHeight, useKeyboardEvent } from '@onekeyhq/components';
const height = useKeyboardHeight(); // 0 when hidden
useKeyboardEvent({ keyboardWillShow: (e) => { /* e.endCoordinates.height / }, keyboardWillHide: () => { / ... */ }, });
Use useKeyboardEventWithoutNavigation for components outside NavigationContainer (Dialog, Modal).
- iOS Overlay Navigation Freeze (resetAboveMainRoute )
On iOS with native UITabBarController , closing overlay routes (Modal, FullScreenPush) via sequential goBack() calls triggers an RNSScreenStack window-nil race condition. Popped pages' screen stacks lose their iOS window reference and enter a retry storm (50 retries × ~100ms), freezing navigation for ~5 seconds.
Symptom: After closing a modal, the app appears stuck on the home page. A touch on the screen "unsticks" navigation.
Root cause: goBack() triggers animated modal dismiss. Screen stacks inside detached tab views get window=NIL and retry indefinitely until the retry limit (50) is exhausted.
Fix: Use resetAboveMainRoute() to atomically remove all overlay routes via CommonActions.reset instead of sequential goBack() calls.
import { resetAboveMainRoute, rootNavigationRef } from '@onekeyhq/components';
// ❌ WRONG: Sequential goBack() causes iOS window-nil freeze const closeModalPages = async () => { rootNavigationRef.current?.goBack(); await timerUtils.wait(150); await closeModalPages(); // recursive — each call triggers native animation }; await closeModalPages(); await timerUtils.wait(250); rootNavigationRef.current?.navigate(targetRoute);
// ✅ CORRECT: Atomic reset, no orphaned screen stacks resetAboveMainRoute(); await timerUtils.wait(100); rootNavigationRef.current?.navigate(targetRoute);
Key file: packages/components/src/layouts/Navigation/Navigator/NavigationContainer.tsx
Reference: commit 2cabd040 (OK-50182) — same fix applied to scan QR code navigation.
Related Skills
-
/1k-cross-platform
-
Platform-specific development
-
/1k-performance
-
Performance optimization
-
/1k-coding-patterns
-
General coding patterns