USE THIS when asked to work on a new or existing (MOBILE) CDS React component in packages/mobile
Mobile-specific patterns for @coinbase/cds-mobile.
Use this guidance when adding ComponentConfigProvider defaults for the specific component you are editing.
packages/mobile/src/core/componentConfig.ts using its *BaseProps:import type { MyComponentBaseProps } from '../category/MyComponent';
export type ComponentConfig = {
MyComponent?: ConfigResolver<MyComponentBaseProps>;
};useComponentConfig in the component and destructure from merged props:import { useComponentConfig } from '../hooks/useComponentConfig';
export const MyComponent = memo((_props: MyComponentProps) => {
const mergedProps = useComponentConfig('MyComponent', _props);
const { style, ...props } = mergedProps;
return <Pressable style={style} {...props} />;
});_props as the input variable and mergedProps as the configured output.*BaseProps (not full *Props).Use StyleSheet.create for static styles and useTheme() for dynamic values:
import { 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]} />;style and styles object props for overriding default styles.style and styles props should never be on the *BaseProps type.styles can be used for granular overrides on child elements within the component.useMemo in the correct order (default styles => style prop => styles[ELEMENT_NAME] prop).Example:
type 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}>import 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.
Use react-native-gesture-handler:
import { 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>;Use onLayout callback instead of ResizeObserver:
const [size, onLayout] = useLayout();
<View onLayout={onLayout} />
// Or inline
<View onLayout={(e) => setHeight(e.nativeEvent.layout.height)} />Example: Use React Native accessibility props:
<View
accessible
accessibilityRole="adjustable"
accessibilityLabel="Product carousel"
accessibilityLiveRegion="polite"
>
<Pressable
accessibilityState={{ selected: isActive, disabled }}
accessibilityActions={[{ name: 'activate' }]}
onAccessibilityAction={handleAccessibilityAction}
/>
</View>// 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>f79a780
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.