Developing Mobile Apps
telagod/code-abyss
Mobile development knowledge reference covering iOS (SwiftUI), Android (Jetpack Compose), React Native, and Flutter.
Mobile UI patterns - React Native, iOS/Android, touch targets
$ npx skills add alinaqi/maggy --skill ui-mobile -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install alinaqi/maggy ui-mobile --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/ui-mobile .claude/skills/ui-mobile && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "ui-mobile" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/ui-mobile into .claude/skills/ui-mobile/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ui-mobile", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/alinaqi/maggy/tree/main/skills/ui-mobileType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add alinaqi/maggy --skill ui-mobile -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install alinaqi/maggy ui-mobile --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/ui-mobile .agents/skills/ui-mobile && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "ui-mobile" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/ui-mobile into .agents/skills/ui-mobile/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ui-mobile", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add alinaqi/maggy --skill ui-mobile -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install alinaqi/maggy ui-mobile --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/ui-mobile .cursor/skills/ui-mobile && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "ui-mobile" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/ui-mobile into .cursor/skills/ui-mobile/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ui-mobile", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/alinaqi/maggy.git --path skills/ui-mobile--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add alinaqi/maggy --skill ui-mobile -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install alinaqi/maggy ui-mobile --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/ui-mobile .gemini/skills/ui-mobile && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "ui-mobile" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/ui-mobile into .gemini/skills/ui-mobile/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ui-mobile", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install alinaqi/maggy ui-mobileInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add alinaqi/maggy --skill ui-mobile -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/ui-mobile .github/skills/ui-mobile && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "ui-mobile" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/ui-mobile into .github/skills/ui-mobile/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ui-mobile", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add alinaqi/maggy --skill ui-mobile -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install alinaqi/maggy ui-mobile --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/ui-mobile .opencode/skills/ui-mobile && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "ui-mobile" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/ui-mobile into .opencode/skills/ui-mobile/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ui-mobile", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
ui-mobileMobile UI patterns - React Native, iOS/Android, touch targets
UI Mobile is an agent skill from alinaqi/maggy. Mobile UI patterns - React Native, iOS/Android, touch targets
Its SKILL.md is about 4.9k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Mobile, covering Mobile UI design and Cross-platform mobile apps. It works with React Native, Android and iOS. The repository describes itself as: What started as an opinionated Claude Code setup kit is now an autonomous AI engineering command center. The licence is MIT.
5 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 72a456e. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are typescript).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
UI Mobile loads about 4.9k tokens when it runs. Until then it costs about 18 tokens; SKILL.md has 139 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from alinaqi/maggy at commit 72a456e, republished under its MIT licence (© alinaqi). 139 words, ~4,902 tokens.
.claude/skills/ui-mobile/SKILL.md (or your agent's skills folder).These rules are NON-NEGOTIABLE. Every UI element must pass these checks.
// MINIMUM 44x44 points for ALL interactive elements
const MINIMUM_TOUCH_SIZE = 44;
// EVERY button, link, icon button must meet this
const styles = StyleSheet.create({
button: {
minHeight: MINIMUM_TOUCH_SIZE,
minWidth: MINIMUM_TOUCH_SIZE,
paddingVertical: 12,
paddingHorizontal: 16,
},
iconButton: {
width: MINIMUM_TOUCH_SIZE,
height: MINIMUM_TOUCH_SIZE,
justifyContent: 'center',
alignItems: 'center',
},
});
// NEVER DO THIS:
style={{ height: 30 }} // ✗ TOO SMALL
style={{ padding: 4 }} // ✗ RESULTS IN TINY TARGET// WCAG 2.1 AA: 4.5:1 for text, 3:1 for large text/UI
// SAFE COMBINATIONS:
const colors = {
// Light mode
textPrimary: '#000000', // on white = 21:1 ✓
textSecondary: '#374151', // gray-700 on white = 9.2:1 ✓
// Dark mode
textPrimaryDark: '#FFFFFF', // on gray-900 = 16:1 ✓
textSecondaryDark: '#E5E7EB', // gray-200 on gray-900 = 11:1 ✓
};
// FORBIDDEN - FAILS CONTRAST:
// ✗ '#9CA3AF' (gray-400) on white = 2.6:1
// ✗ '#6B7280' (gray-500) on '#111827' = 4.0:1
// ✗ Any text below 4.5:1 ratio// ALL BUTTONS MUST HAVE visible boundaries
// PRIMARY: Solid background with contrasting text
<Pressable style={styles.primaryButton}>
<Text style={{ color: '#FFFFFF' }}>Submit</Text>
</Pressable>
const styles = StyleSheet.create({
primaryButton: {
backgroundColor: '#1F2937', // gray-800
paddingVertical: 16,
paddingHorizontal: 24,
borderRadius: 12,
minHeight: 44,
},
});
// SECONDARY: Visible background
<Pressable style={styles.secondaryButton}>
<Text style={{ color: '#1F2937' }}>Cancel</Text>
</Pressable>
const styles = StyleSheet.create({
secondaryButton: {
backgroundColor: '#F3F4F6', // gray-100
minHeight: 44,
},
});
// GHOST: MUST have visible border
<Pressable style={styles.ghostButton}>
<Text style={{ color: '#374151' }}>Skip</Text>
</Pressable>
const styles = StyleSheet.create({
ghostButton: {
borderWidth: 1,
borderColor: '#D1D5DB', // gray-300
minHeight: 44,
},
});
// NEVER CREATE invisible buttons:
// ✗ backgroundColor: 'transparent' without border
// ✗ Text color matching background// EVERY interactive element needs accessibility props
// Buttons
<Pressable
accessible={true}
accessibilityRole="button"
accessibilityLabel="Submit form"
accessibilityHint="Double tap to submit your information"
>
<Text>Submit</Text>
</Pressable>
// Icon buttons (NO visible text = MUST have label)
<Pressable
accessible={true}
accessibilityRole="button"
accessibilityLabel="Close menu"
>
<CloseIcon />
</Pressable>
// Images
<Image
accessible={true}
accessibilityRole="image"
accessibilityLabel="User profile photo"
source={...}
/>// EVERY Pressable needs visible pressed state
<Pressable
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed,
]}
>
{children}
</Pressable>
const styles = StyleSheet.create({
button: {
backgroundColor: '#1F2937',
},
buttonPressed: {
opacity: 0.7,
// OR
backgroundColor: '#374151',
},
});Mobile UI is about touch, speed, and focus. No hover states, smaller screens, thumb-friendly targets. Design for one-handed use and interruption recovery.
import { Platform } from 'react-native';
// Platform-specific values
const styles = StyleSheet.create({
shadow: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
},
android: {
elevation: 4,
},
}),
// iOS uses SF Pro, Android uses Roboto
text: {
fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto',
},
});iOS (Human Interface Guidelines)
─────────────────────────────────
- Flat design with subtle depth
- SF Symbols for icons
- Large titles (34pt)
- Rounded corners (10-14pt)
- Blue as default tint
Android (Material Design 3)
─────────────────────────────────
- Material You dynamic color
- Outlined/filled icons
- Medium titles (22pt)
- Rounded corners (12-28pt)
- Primary color from theme// React Native spacing - consistent scale
const spacing = {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
'2xl': 48,
} as const;
// Usage
const styles = StyleSheet.create({
container: {
padding: spacing.md,
gap: spacing.sm,
},
});import { useSafeAreaInsets } from 'react-native-safe-area-context';
const Screen = ({ children }) => {
const insets = useSafeAreaInsets();
return (
<View style={{
flex: 1,
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: Math.max(insets.left, 16),
paddingRight: Math.max(insets.right, 16),
}}>
{children}
</View>
);
};const typography = {
// Large titles (iOS style)
largeTitle: {
fontSize: 34,
fontWeight: '700' as const,
letterSpacing: 0.37,
},
// Section headers
title: {
fontSize: 22,
fontWeight: '700' as const,
letterSpacing: 0.35,
},
// Card titles
headline: {
fontSize: 17,
fontWeight: '600' as const,
letterSpacing: -0.41,
},
// Body text
body: {
fontSize: 17,
fontWeight: '400' as const,
letterSpacing: -0.41,
lineHeight: 22,
},
// Secondary text
callout: {
fontSize: 16,
fontWeight: '400' as const,
letterSpacing: -0.32,
},
// Small labels
caption: {
fontSize: 12,
fontWeight: '400' as const,
letterSpacing: 0,
},
};// Use semantic names, not literal colors
const colors = {
// Backgrounds
background: '#FFFFFF',
backgroundSecondary: '#F2F2F7',
backgroundTertiary: '#FFFFFF',
// Surfaces
surface: '#FFFFFF',
surfaceElevated: '#FFFFFF',
// Text
label: '#000000',
labelSecondary: '#3C3C43', // 60% opacity
labelTertiary: '#3C3C43', // 30% opacity
// Actions
primary: '#007AFF',
destructive: '#FF3B30',
success: '#34C759',
warning: '#FF9500',
// Separators
separator: '#3C3C43', // 29% opacity
opaqueSeparator: '#C6C6C8',
};
// Dark mode variants
const darkColors = {
background: '#000000',
backgroundSecondary: '#1C1C1E',
label: '#FFFFFF',
labelSecondary: '#EBEBF5', // 60% opacity
separator: '#545458',
};import { useColorScheme } from 'react-native';
const useColors = () => {
const scheme = useColorScheme();
return scheme === 'dark' ? darkColors : colors;
};
// Usage
const MyComponent = () => {
const colors = useColors();
return (
<View style={{ backgroundColor: colors.background }}>
<Text style={{ color: colors.label }}>Hello</Text>
</View>
);
};// CRITICAL: Minimum 44pt touch targets
const touchable = {
minHeight: 44,
minWidth: 44,
};
// Button with proper sizing
const styles = StyleSheet.create({
button: {
minHeight: 44,
paddingHorizontal: 16,
paddingVertical: 12,
justifyContent: 'center',
alignItems: 'center',
},
// Icon button (square)
iconButton: {
width: 44,
height: 44,
justifyContent: 'center',
alignItems: 'center',
},
// List row
listRow: {
minHeight: 44,
paddingVertical: 12,
paddingHorizontal: 16,
},
});import { Pressable } from 'react-native';
// iOS-style opacity feedback
const Button = ({ children, onPress }) => (
<Pressable
onPress={onPress}
style={({ pressed }) => [
styles.button,
pressed && { opacity: 0.7 },
]}
>
{children}
</Pressable>
);
// Android-style ripple
const AndroidButton = ({ children, onPress }) => (
<Pressable
onPress={onPress}
android_ripple={{
color: 'rgba(0, 0, 0, 0.1)',
borderless: false,
}}
style={styles.button}
>
{children}
</Pressable>
);const Card = ({ children, style }) => (
<View style={[styles.card, style]}>
{children}
</View>
);
const styles = StyleSheet.create({
card: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 16,
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 8,
},
android: {
elevation: 2,
},
}),
},
});// Primary button
const PrimaryButton = ({ title, onPress, disabled }) => (
<Pressable
onPress={onPress}
disabled={disabled}
style={({ pressed }) => [
styles.primaryButton,
pressed && styles.primaryButtonPressed,
disabled && styles.buttonDisabled,
]}
>
<Text style={styles.primaryButtonText}>{title}</Text>
</Pressable>
);
const styles = StyleSheet.create({
primaryButton: {
backgroundColor: '#007AFF',
borderRadius: 12,
paddingVertical: 16,
paddingHorizontal: 24,
alignItems: 'center',
},
primaryButtonPressed: {
backgroundColor: '#0056B3',
},
primaryButtonText: {
color: '#FFFFFF',
fontSize: 17,
fontWeight: '600',
},
buttonDisabled: {
opacity: 0.5,
},
});
// Secondary button
const SecondaryButton = ({ title, onPress }) => (
<Pressable
onPress={onPress}
style={({ pressed }) => [
styles.secondaryButton,
pressed && { opacity: 0.7 },
]}
>
<Text style={styles.secondaryButtonText}>{title}</Text>
</Pressable>
);const TextField = ({ label, value, onChangeText, error }) => {
const [focused, setFocused] = useState(false);
return (
<View style={styles.textFieldContainer}>
{label && (
<Text style={styles.textFieldLabel}>{label}</Text>
)}
<TextInput
value={value}
onChangeText={onChangeText}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
style={[
styles.textField,
focused && styles.textFieldFocused,
error && styles.textFieldError,
]}
placeholderTextColor="#8E8E93"
/>
{error && (
<Text style={styles.errorText}>{error}</Text>
)}
</View>
);
};
const styles = StyleSheet.create({
textFieldContainer: {
gap: 8,
},
textFieldLabel: {
fontSize: 15,
fontWeight: '500',
color: '#3C3C43',
},
textField: {
backgroundColor: '#F2F2F7',
borderRadius: 10,
paddingHorizontal: 16,
paddingVertical: 14,
fontSize: 17,
color: '#000000',
borderWidth: 2,
borderColor: 'transparent',
},
textFieldFocused: {
borderColor: '#007AFF',
backgroundColor: '#FFFFFF',
},
textFieldError: {
borderColor: '#FF3B30',
},
errorText: {
fontSize: 13,
color: '#FF3B30',
},
});// Grouped list (iOS Settings style)
const GroupedList = ({ sections }) => (
<ScrollView style={styles.groupedList}>
{sections.map((section, i) => (
<View key={i} style={styles.section}>
{section.title && (
<Text style={styles.sectionHeader}>{section.title}</Text>
)}
<View style={styles.sectionContent}>
{section.items.map((item, j) => (
<React.Fragment key={j}>
{j > 0 && <View style={styles.separator} />}
<Pressable
style={({ pressed }) => [
styles.listRow,
pressed && { backgroundColor: '#E5E5EA' },
]}
onPress={item.onPress}
>
<Text style={styles.listRowText}>{item.title}</Text>
<ChevronRight color="#C7C7CC" />
</Pressable>
</React.Fragment>
))}
</View>
</View>
))}
</ScrollView>
);
const styles = StyleSheet.create({
groupedList: {
flex: 1,
backgroundColor: '#F2F2F7',
},
section: {
marginTop: 35,
},
sectionHeader: {
fontSize: 13,
fontWeight: '400',
color: '#6D6D72',
textTransform: 'uppercase',
marginLeft: 16,
marginBottom: 8,
},
sectionContent: {
backgroundColor: '#FFFFFF',
borderRadius: 10,
marginHorizontal: 16,
overflow: 'hidden',
},
listRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 12,
paddingHorizontal: 16,
minHeight: 44,
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: '#C6C6C8',
marginLeft: 16,
},
});// Proper bottom tab sizing
const tabBarStyle = {
height: Platform.OS === 'ios' ? 83 : 65, // Account for home indicator
paddingBottom: Platform.OS === 'ios' ? 34 : 10,
paddingTop: 10,
backgroundColor: '#F8F8F8',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: '#C6C6C8',
};
// Tab item
const TabItem = ({ icon, label, active }) => (
<View style={styles.tabItem}>
<Icon name={icon} color={active ? '#007AFF' : '#8E8E93'} size={24} />
<Text style={[
styles.tabLabel,
{ color: active ? '#007AFF' : '#8E8E93' }
]}>
{label}
</Text>
</View>
);// Large title header (iOS)
const LargeTitleHeader = ({ title, rightAction }) => {
const insets = useSafeAreaInsets();
return (
<View style={[styles.header, { paddingTop: insets.top }]}>
<View style={styles.headerContent}>
<Text style={styles.largeTitle}>{title}</Text>
{rightAction}
</View>
</View>
);
};
const styles = StyleSheet.create({
header: {
backgroundColor: '#F8F8F8',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#C6C6C8',
},
headerContent: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingBottom: 8,
},
largeTitle: {
fontSize: 34,
fontWeight: '700',
letterSpacing: 0.37,
},
});import { Animated } from 'react-native';
// Always use native driver when possible
const fadeIn = (value: Animated.Value) => {
Animated.timing(value, {
toValue: 1,
duration: 200,
useNativeDriver: true, // CRITICAL for performance
}).start();
};
// Spring for natural feel
const bounce = (value: Animated.Value) => {
Animated.spring(value, {
toValue: 1,
damping: 15,
stiffness: 150,
useNativeDriver: true,
}).start();
};import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
const AnimatedCard = ({ children }) => {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const onPressIn = () => {
scale.value = withSpring(0.95);
};
const onPressOut = () => {
scale.value = withSpring(1);
};
return (
<Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
<Animated.View style={[styles.card, animatedStyle]}>
{children}
</Animated.View>
</Pressable>
);
};const SkeletonLoader = ({ width, height, borderRadius = 4 }) => {
const opacity = useSharedValue(0.3);
useEffect(() => {
opacity.value = withRepeat(
withSequence(
withTiming(1, { duration: 500 }),
withTiming(0.3, { duration: 500 })
),
-1,
false
);
}, []);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));
return (
<Animated.View
style={[
{ width, height, borderRadius, backgroundColor: '#E5E5EA' },
animatedStyle,
]}
/>
);
};import { ActivityIndicator } from 'react-native';
// Use platform-native indicator
<ActivityIndicator size="large" color="#007AFF" />
// Button with loading state
const LoadingButton = ({ loading, title, onPress }) => (
<Pressable
onPress={onPress}
disabled={loading}
style={styles.button}
>
{loading ? (
<ActivityIndicator color="#FFFFFF" />
) : (
<Text style={styles.buttonText}>{title}</Text>
)}
</Pressable>
);// Accessible button
<Pressable
onPress={onPress}
accessible={true}
accessibilityRole="button"
accessibilityLabel="Submit form"
accessibilityHint="Double tap to submit your information"
>
<Text>Submit</Text>
</Pressable>
// Accessible image
<Image
source={icon}
accessible={true}
accessibilityRole="image"
accessibilityLabel="User profile picture"
/>
// Group related elements
<View
accessible={true}
accessibilityRole="summary"
accessibilityLabel={`${name}, ${role}, ${status}`}
>
<Text>{name}</Text>
<Text>{role}</Text>
<Text>{status}</Text>
</View>import { PixelRatio } from 'react-native';
// Scale fonts with system settings
const fontScale = PixelRatio.getFontScale();
const scaledFontSize = (size: number) => size * fontScale;
// Or use allowFontScaling
<Text allowFontScaling={true} style={{ fontSize: 17 }}>
This text scales with system settings
</Text>✗ Touch targets smaller than 44pt
✗ Text smaller than 12pt
✗ Hover states (no hover on mobile)
✗ Fixed heights that break with large text
✗ Ignoring safe areas
✗ Heavy shadows on Android (use elevation)
✗ White text on light backgrounds without checking contrast
✗ Non-native animations (JS-driven transforms)
✗ Ignoring platform conventions (iOS vs Android)
✗ Inline styles everywhere (use StyleSheet.create)// ✗ Hardcoded dimensions that break accessibility
style={{ height: 40 }} // Text might be larger
// ✓ Minimum height with padding
style={{ minHeight: 44, paddingVertical: 12 }}
// ✗ Shadow on Android
shadowColor: '#000' // Won't work
// ✓ Platform-specific
...Platform.select({
ios: { shadowColor: '#000', ... },
android: { elevation: 4 },
})
// ✗ Fixed status bar height
paddingTop: 44
// ✓ Use safe area
paddingTop: insets.topTouch targets: 44pt minimum
Font sizes: 12pt min, 17pt body, 34pt large title
Border radius: 10-14pt (iOS), 12-28pt (Android)
Spacing: 4/8/16/24/32 grid
Animations: 200-300ms, native driver
Shadow: iOS shadowOpacity 0.08-0.15, Android elevation 2-8□ All touch targets 44pt+
□ Consistent spacing (4pt grid)
□ Platform-appropriate styling
□ Safe area handling
□ Native animations (60fps)
□ Proper loading states
□ Dark mode support
□ Accessibility labels
□ Haptic feedback on actions
□ Pull-to-refresh where appropriate© alinaqi, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/ui-mobile of alinaqi/maggy.
Open the folder on GitHubat commit 72a456e
UI Mobile next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| UI Mobile this skillalinaqi/maggy | 707 | — | ~4.9k | Automated safety check: Pass | MIT | |
| Developing Mobile Appstelagod/code-abyss | 244 | — | ~258 | Automated safety check: Pass | MIT | |
| Mobile AccessibilityCommunity-Access/accessibility-agents | 423 | — | ~1.1k | Automated safety check: Pass | MIT | |
| Kb Mobile AccessibilityCommunity-Access/accessibility-agents | 423 | — | ~3.8k | Automated safety check: Pass | MIT | |
| Mobile Apps Createforcedotcom/sf-skills | 1.1k | — | ~1.4k | Automated safety check: Pass | Apache-2.0 | |
| Mobile Designirahardianto/awesome-agv | 156 | — | ~1.2k | Automated safety check: Pass | MIT |
telagod/code-abyss
Mobile development knowledge reference covering iOS (SwiftUI), Android (Jetpack Compose), React Native, and Flutter.
Community-Access/accessibility-agents
React Native, Expo, iOS and Android: labels, roles and touch targets.
Community-Access/accessibility-agents
Reference data, not a reviewer. An agent skill from Community-Access/accessibility-agents.
forcedotcom/sf-skills
The entry point for building any Salesforce native mobile app on iOS or Android.
irahardianto/awesome-agv
Generates distinctive, production-grade mobile interfaces for Flutter and React Native.
wix/react-native-navigation
Navigate and work with the react-native-navigation (RNN) codebase.
alinaqi/maggy
AI Engine Optimization - semantic triples, page templates, content clusters for AI citations
alinaqi/maggy
Claude Code Agent Teams - default team-based development with strict TDD pipeline enforcement
alinaqi/maggy
Latest AI models reference - Claude, OpenAI, Gemini, Eleven Labs, Replicate
alinaqi/maggy
Android Java development with MVVM, ViewBinding, and Espresso testing
alinaqi/maggy
Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing
alinaqi/maggy
AI-driven testing agent that auto-discovers, generates, executes, evaluates, and fixes tests for any project type
Works with
Categories
Mobile UI patterns - React Native, iOS/Android, touch targets. UI Mobile is an agent skill from alinaqi/maggy.
UI Mobile fits situations like: tasks that involve Mobile UI design; tasks that involve Cross-platform mobile apps.
Run `npx skills add alinaqi/maggy --skill ui-mobile -a claude-code`. Or copy the skill folder (skills/ui-mobile in alinaqi/maggy) into .claude/skills/ui-mobile in your project. Claude Code loads it when a task matches its description.
Run `npx skills add alinaqi/maggy --skill ui-mobile -a codex`. Or copy the skill folder (skills/ui-mobile in alinaqi/maggy) into .agents/skills/ui-mobile in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add alinaqi/maggy --skill ui-mobile -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/ui-mobile, .gemini/skills/ui-mobile, .github/skills/ui-mobile and .opencode/skills/ui-mobile in your project.
SKILL.md names no scripts, command-line tools or credentials: UI Mobile is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
UI Mobile is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 4.9k tokens (SKILL.md is roughly 20k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with UI Mobile: Developing Mobile Apps (telagod/code-abyss, 244 stars), Mobile Accessibility (Community-Access/accessibility-agents, 423 stars), Kb Mobile Accessibility (Community-Access/accessibility-agents, 423 stars) and Mobile Apps Create (forcedotcom/sf-skills, 1.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
alinaqi (a GitHub user) maintains it in alinaqi/maggy, which has 707 GitHub stars. The repository holds 71 skills in this directory. The repository was last updated on September 24, 2026.
Source: alinaqi/maggy on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.