CDS Mobile Package Guidelines
SkillDev toolsUSE THIS when asked to work on a new or existing (MOBILE) CDS React component in packages/mobile
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the CDS Mobile Package Guidelines skill
What this skill tells your AI
The instructions your AI receives, as published by coinbase/cds in .claude/skills/dev.cds-mobile/SKILL.md and read by ahel’s review.
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:
import type { MyComponentBaseProps } from '../category/MyComponent';
export type ComponentConfig = {
MyComponent?: ConfigResolver<MyComponentBaseProps>;
};
- Adopt
useComponentConfigin 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} />;
});
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:
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]} />;
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:
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}>
Animation
React Native Reanimated
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.
Gesture Handling
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>;
Layout Measurement
Use onLayout callback instead of ResizeObserver:
const [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:
<View
accessible
accessibilityRole="adjustable"
accessibilityLabel="Product carousel"
accessibilityLiveRegion="polite"
>
<Pressable
accessibilityState={{ selected: isActive, disabled }}
accessibilityActions={[{ name: 'activate' }]}
onAccessibilityAction={handleAccessibilityAction}
/>
</View>
Screen Reader Content
// 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
Signals
- GitHub stars
- 504
- Forks
- 105
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
dev-cds-mobile- Source
- github.com/coinbase/cds