CDS Mobile Package Guidelines
Mobile-specific patterns for @coinbase/cds-mobile.
Component Config Adoption (Mobile)
Use this guidance when adding ComponentConfigProvider defaults for the specific component you are editing.
Required implementation pattern
- Register the component in
packages/mobile/src/core/componentConfig.tsusing its*BaseProps:
tsimport type { MyComponentBaseProps } from '../category/MyComponent'; export type ComponentConfig = { MyComponent?: ConfigResolver<MyComponentBaseProps>; };
- Adopt
useComponentConfigin the component and destructure from merged props:
tsximport { useComponentConfig } from '../hooks/useComponentConfig'; export const MyComponent = memo((_props: MyComponentProps) => { const mergedProps = useComponentConfig('MyComponent', _props); const { style, ...props } = mergedProps; return <Pressable style={style} {...props} />; });
Rules to preserve behavior
- Provider config supplies defaults only; local props must continue to win.
- Use
_propsas the input variable andmergedPropsas the configured output. - Type resolver entries with
*BaseProps(not full*Props). - Keep scope to prop-level theming defaults; do not alter component behavior or control flow.
- When practical during the same change, prefer arrow-function component declarations.
Styling with StyleSheet
Use StyleSheet.create for static styles and useTheme() for dynamic values:
tsximport { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; const styles = StyleSheet.create({ container: { position: 'relative', width: '100%' }, }); // Dynamic styles via theme hook const theme = useTheme(); const dynamicStyle = { backgroundColor: theme.color.bgPrimary, padding: theme.space[2], }; <View style={[styles.container, dynamicStyle, style]} />;
Inline styles
- Mobile components should all expose a
styleandstylesobject props for overriding default styles. - As styling is a concern of that specific component, the
styleandstylesprops should never be on the*BasePropstype. stylescan be used for granular overrides on child elements within the component.- Always merge styles into a react-native style array with
useMemoin the correct order (default styles =>styleprop =>styles[ELEMENT_NAME]prop).
Example:
tsxtype ComponentProps = ComponentBaseProps & { style?: StyleProp<ViewStyle>; styles?: { root?: StyleProp<ViewStyle>; label?: StyleProp<TextStyle>; }; }; const theme = useTheme(); const containerStyles = useMemo( () => [ { backgroundColor: theme.color.bgPrimary }, // default styles style, // from props styles.root, // from props ], [theme.color.bgPrimary, animatedStyles, style] ); // Apply to component <Box style={containerStyles}>
Animation
React Native Reanimated
tsximport Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; const opacity = useSharedValue(0); const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, transform: [{ translateY: withTiming(opacity.value * -8) }], })); <Animated.View style={animatedStyle} />;
We DO NOT use React-Spring anymore for animations on mobile.
Gesture Handling
Use react-native-gesture-handler:
tsximport { Gesture, GestureDetector } from 'react-native-gesture-handler'; const panGesture = useMemo( () => Gesture.Pan() .onStart(() => { /* ... */ }) .onUpdate(({ translationX }) => { /* ... */ }) .onEnd(({ translationX, velocityX }) => { /* ... */ }) .withTestId(testID) .runOnJS(true), [dependencies], ); <GestureDetector gesture={panGesture}> <Animated.View>{/* ... */}</Animated.View> </GestureDetector>;
Layout Measurement
Use onLayout callback instead of ResizeObserver:
tsxconst [size, onLayout] = useLayout(); <View onLayout={onLayout} /> // Or inline <View onLayout={(e) => setHeight(e.nativeEvent.layout.height)} />
Accessibility
- Use appropriate accessibilityLabel, accessibilityHint, and accessibilityRole, accessibilityState props
- Support screen readers (VoiceOver and TalkBack)
- Ensure touch targets meet minimum size requirements (44x44 points)
Example: Use React Native accessibility props:
tsx<View accessible accessibilityRole="adjustable" accessibilityLabel="Product carousel" accessibilityLiveRegion="polite" > <Pressable accessibilityState={{ selected: isActive, disabled }} accessibilityActions={[{ name: 'activate' }]} onAccessibilityAction={handleAccessibilityAction} /> </View>
Screen Reader Content
tsx// Hide visual content from screen readers <View accessibilityElementsHidden importantForAccessibility="no-hide-descendants"> {/* Animated/visual content */} </View> // Provide accessible alternative <Text importantForAccessibility="yes" accessibilityLiveRegion="polite" style={{ color: 'transparent', position: 'absolute' }} > {accessibleLabel} </Text>
Reference Components
- SlideButton: gesture handling, spring animations, accessibility actions
- RollingNumber: Reanimated, measurement patterns, screen reader content
- Select (alpha/): controlled/uncontrolled, Drawer integration
- Stepper: direction-based defaults, shared logic from cds-common
- Tour: animations, complexity
- DatePicker: complexity


