Gluestack UI v4 - Validation & Anti-Patterns
This sub-skill focuses on validating implementations, identifying anti-patterns, and ensuring code quality for gluestack-ui v4.
Validation Checklist
When reviewing code, check for:
Component Usage
- • Component usage verified against official v4 docs at
https://v4.gluestack.io/ui/docs/components/${componentName}/ - • All React Native primitives replaced with Gluestack components
- • Components imported from local
@/components/ui/directory - • GluestackUIProvider wraps the app
Component Props vs className
- • Component props used instead of className when available:
- • VStack/HStack use
spaceprop instead ofgap-*className - • Button uses
variantandsizeprops instead of className - • Heading/Text use
sizeprop instead oftext-*className - • Heading/Text use
boldprop instead offont-boldclassName - • Other component props used where applicable
- • VStack/HStack use
Styling
- • CRITICAL: All colors use ONLY semantic tokens - NO exceptions:
- • No
typography-*tokens (usetext-foreground,text-muted-foreground) - • No
neutral-*tokens (use semantic equivalents) - • No
gray-*orslate-*tokens (use semantic equivalents) - • No numbered colors:
red-500,blue-600,green-400 - • No arbitrary values:
[#3b82f6],[#DC2626] - • No opacity utilities (use alpha values:
/70,/90)
- • No
- • All spacing values use the standard scale (no arbitrary values)
- • No inline styles where className can be used
- • Dark mode support using
dark:prefix (semantic tokens ensure compatibility) - • Variants defined using
tvawhen needed - • className properly merged in custom components
Compound Components
- • Compound components used correctly:
- • InputIcon always wrapped in InputSlot (CRITICAL)
- • ButtonText used for all button text content
- • FormControl sub-components used (FormControlLabel, FormControlError, etc.)
- • Card sub-components used (CardHeader, CardBody, CardFooter)
- • Checkbox sub-components used (CheckboxIndicator, CheckboxIcon, CheckboxLabel)
- • All other component sub-components as per official docs
Icons
- • Icons follow priority: pre-built icons → Lucide Icons → createIcon for custom icons
- • Icons imported from
@/components/ui/icon
Cross-Platform Compatibility
- • Cross-platform compatibility verified:
- • All components use Gluestack wrappers (no direct react-native imports)
- • KeyboardAvoidingView uses Gluestack wrapper
- • Tested on both native and web platforms
- • All interactions work on both platforms
Performance & Best Practices
- • Performance & best practices:
- • TypeScript types defined for components and props
- • Expensive components memoized with React.memo
- • Callbacks memoized with useCallback when needed
- • Animations use Reanimated worklets (not Animated API)
- • Safe areas handled with SafeAreaView or useSafeAreaInsets
- • Long lists use FlatList (not ScrollView + map)
- • Platform-specific code uses Platform.select
- • Tested on real devices (not just simulators)
Anti-Patterns to Avoid
❌ Don't: Mix React Native and Gluestack Components
// ❌ INCORRECT: Mixing React Native and Gluestack components
import { View, Text } from "react-native";
import { Button } from "@/components/ui/button";
<View>
<Text>Mixed usage</Text>
<Button>Click</Button>
</View>
Why it's bad: Loses theming, accessibility, and cross-platform consistency.
Correct approach:
// ✅ CORRECT: Use Gluestack components consistently
import { Box } from "@/components/ui/box";
import { Text } from "@/components/ui/text";
import { Button, ButtonText } from "@/components/ui/button";
<Box>
<Text>Consistent usage</Text>
<Button>
<ButtonText>Click</ButtonText>
</Button>
</Box>
❌ Don't: Use Non-Semantic Color Tokens
STRICTLY PROHIBITED - You MUST use ONLY semantic tokens for ALL colors.
// ❌ PROHIBITED: Generic typography tokens
<Text className="text-typography-900">Heading</Text>
<Text className="text-typography-700">Body</Text>
<Text className="text-typography-500">Muted</Text>
// ❌ PROHIBITED: Neutral color tokens
<Box className="bg-neutral-100">Card</Box>
<Text className="text-neutral-600">Text</Text>
<Box className="border-neutral-300">Border</Box>
// ❌ PROHIBITED: Gray/Slate color scales
<Box className="bg-gray-50">Background</Box>
<Text className="text-gray-900">Content</Text>
<Box className="border-gray-200">Border</Box>
// ❌ PROHIBITED: Numbered color tokens
<Box className="bg-blue-600">Primary</Box>
<Text className="text-red-500">Error</Text>
<Box className="border-green-400">Success</Box>
// ❌ PROHIBITED: Arbitrary color values
<Box className="bg-[#3b82f6]">Arbitrary</Box>
<Text className="text-[#DC2626]">Error</Text>
// ❌ PROHIBITED: Inline color styles
<Box style={{ backgroundColor: '#fff' }}>White</Box>
<Text style={{ color: 'red' }}>Error</Text>
// ❌ PROHIBITED: Opacity utilities
<Text className="text-black opacity-70">Muted</Text>
<Box className="bg-blue-600 bg-opacity-90">Transparent</Box>
Why it's bad:
- •❌ Breaks theming and dark mode
- •❌ Creates maintenance debt
- •❌ Violates design system
- •❌ Inconsistent colors across app
Correct approach:
// ✅ CORRECT: Use ONLY semantic tokens <Text className="text-foreground">Heading</Text> <Text className="text-foreground">Body</Text> <Text className="text-muted-foreground">Muted</Text> <Box className="bg-muted">Card</Box> <Text className="text-muted-foreground">Text</Text> <Box className="border-border">Border</Box> <Box className="bg-background">Background</Box> <Text className="text-foreground">Content</Text> <Box className="border-border">Border</Box> <Box className="bg-primary">Primary</Box> <Text className="text-destructive">Error</Text> <Box className="border-primary">Success</Box> // ✅ CORRECT: Alpha values instead of opacity <Text className="text-foreground/70">Muted</Text> <Box className="bg-primary/90">Transparent</Box>
❌ Don't: Skip Sub-Components
// ❌ INCORRECT: ButtonText is required
<Button>Click Me</Button>
// ❌ INCORRECT: InputIcon not wrapped in InputSlot
<Input>
<InputIcon as={MailIcon} />
<InputField />
</Input>
// ❌ INCORRECT: Missing FormControl sub-components
<FormControl>
<Text>Email</Text>
<InputField />
</FormControl>
Why it's bad: Components won't render correctly, breaks styling and accessibility.
Correct approach:
// ✅ CORRECT: Proper sub-component usage
<Button>
<ButtonText>Click Me</ButtonText>
</Button>
// ✅ CORRECT: InputIcon wrapped in InputSlot
<Input>
<InputSlot>
<InputIcon as={MailIcon} />
</InputSlot>
<InputField />
</Input>
// ✅ CORRECT: FormControl with proper sub-components
<FormControl>
<FormControlLabel>
<FormControlLabelText>Email</FormControlLabelText>
</FormControlLabel>
<Input>
<InputField />
</Input>
</FormControl>
❌ Don't: Use Inline Styles When className Works
// ❌ INCORRECT: Inline styles for static values
<Box style={{ padding: 16, backgroundColor: '#fff' }} />
<Text style={{ fontSize: 18, fontWeight: 'bold' }} />
Why it's bad: Bypasses optimization, breaks theming, harder to maintain.
Correct approach:
// ✅ CORRECT: Use className <Box className="p-4 bg-background" /> <Text size="lg" bold className="text-foreground" />
❌ Don't: Use Arbitrary Spacing Values
// ❌ INCORRECT: Arbitrary spacing values
<Box className="p-[13px] m-[27px]" />
<Box style={{ padding: 13, margin: 27 }} />
<VStack className="gap-[15px]" />
Why it's bad: Creates maintenance burden, inconsistent spacing across app.
Correct approach:
// ✅ CORRECT: Use spacing scale <Box className="p-3 m-6" /> <VStack space="lg" />
❌ Don't: Use className for Component Props
// ❌ INCORRECT: Using className instead of props <VStack className="gap-4"> <Box>Item</Box> </VStack> <Button className="bg-primary px-8 py-2"> <ButtonText>Click</ButtonText> </Button> <Heading className="text-2xl font-bold">Title</Heading>
Why it's bad: Loses type safety, harder to maintain, bypasses design system.
Correct approach:
// ✅ CORRECT: Use component props <VStack space="lg"> <Box>Item</Box> </VStack> <Button variant="default" size="lg"> <ButtonText>Click</ButtonText> </Button> <Heading size="2xl" bold>Title</Heading>
❌ Don't: Import from react-native for Wrapped Components
// ❌ INCORRECT: Direct React Native imports
import { KeyboardAvoidingView, View, Text } from 'react-native';
<KeyboardAvoidingView>
<View>
<Text>Content</Text>
</View>
</KeyboardAvoidingView>
Why it's bad: Breaks cross-platform compatibility, loses theming and accessibility.
Correct approach:
// ✅ CORRECT: Use Gluestack wrappers
import { KeyboardAvoidingView } from '@/components/ui/keyboard-avoiding-view';
import { Box } from '@/components/ui/box';
import { Text } from '@/components/ui/text';
<KeyboardAvoidingView>
<Box>
<Text>Content</Text>
</Box>
</KeyboardAvoidingView>
❌ Don't: Use ScrollView for Long Lists
// ❌ INCORRECT: ScrollView with map for long lists
<ScrollView>
{items.map((item) => (
<Box key={item.id}>
<Text>{item.name}</Text>
</Box>
))}
</ScrollView>
Why it's bad: No virtualization, all items rendered at once, poor performance.
Correct approach:
// ✅ CORRECT: Use FlatList for long lists
<FlatList
data={items}
renderItem={({ item }) => (
<Box>
<Text>{item.name}</Text>
</Box>
)}
keyExtractor={(item) => item.id}
/>
❌ Don't: Use Animated API for Animations
// ❌ INCORRECT: Animated API (runs on JS thread)
import { Animated } from 'react-native';
const animValue = new Animated.Value(0);
Animated.timing(animValue, {
toValue: 100,
duration: 300,
}).start();
Why it's bad: Runs on JavaScript thread, can cause jank and dropped frames.
Correct approach:
// ✅ CORRECT: Use Reanimated (runs on UI thread)
import { useSharedValue, withTiming } from 'react-native-reanimated';
const animValue = useSharedValue(0);
animValue.value = withTiming(100, { duration: 300 });
Common Mistakes Summary
| Mistake | Impact | Correct Approach |
|---|---|---|
| Using React Native primitives | Loses theming, accessibility, cross-platform support | Use Gluestack components |
Using typography-*, neutral-*, gray-* tokens | Breaks theming and dark mode | Use ONLY semantic tokens |
Using numbered colors (red-500, blue-600) | Breaks theming and dark mode | Use semantic tokens |
Using opacity utilities (opacity-70) | Inconsistent transparency | Use alpha values (/70, /90) |
| Raw color values | Breaks theming and dark mode | Use semantic tokens |
| Skipping sub-components | Components won't render correctly | Use proper compound components |
| Inline styles for static values | Bypasses optimization, harder to maintain | Use className |
| Arbitrary spacing values | Creates maintenance burden | Use spacing scale |
| className instead of props | Loses type safety | Use component props when available |
| Direct react-native imports | Breaks cross-platform compatibility | Use Gluestack wrappers |
| ScrollView for long lists | Poor performance | Use FlatList |
| Animated API | Janky animations | Use Reanimated worklets |
Critical Issues (Must Fix Immediately)
🔴 Critical: InputIcon Not Wrapped in InputSlot
// ❌ CRITICAL ERROR: Will break rendering
<Input>
<InputIcon as={MailIcon} />
<InputField />
</Input>
// ✅ MUST FIX: Wrap InputIcon in InputSlot
<Input>
<InputSlot>
<InputIcon as={MailIcon} />
</InputSlot>
<InputField />
</Input>
🔴 Critical: Missing ButtonText
// ❌ CRITICAL ERROR: Button won't render correctly <Button>Submit</Button> // ✅ MUST FIX: Use ButtonText <Button> <ButtonText>Submit</ButtonText> </Button>
🔴 Critical: Using React Native Instead of Gluestack
// ❌ CRITICAL ERROR: Breaks cross-platform support
import { View, Text } from 'react-native';
// ✅ MUST FIX: Use Gluestack components
import { Box } from '@/components/ui/box';
import { Text } from '@/components/ui/text';
🔴 Critical: Using Non-Semantic Color Tokens
// ❌ CRITICAL ERROR: Using prohibited tokens <Text className="text-typography-900">Heading</Text> <Box className="bg-neutral-100">Card</Box> <Text className="text-gray-700">Content</Text> <Box className="bg-blue-600">Primary</Box> // ✅ MUST FIX: Use ONLY semantic tokens <Text className="text-foreground">Heading</Text> <Box className="bg-muted">Card</Box> <Text className="text-foreground">Content</Text> <Box className="bg-primary">Primary</Box>
Code Review Guidelines
High Priority (Must Have)
- •Component consistency - All components use Gluestack wrappers
- •Compound components - All sub-components used correctly
- •CRITICAL: Semantic tokens ONLY - Zero tolerance for:
- •
typography-*,neutral-*,gray-*,slate-*tokens - •Numbered colors:
red-500,blue-600,green-400 - •Arbitrary values:
[#3b82f6],[#DC2626] - •Opacity utilities:
opacity-70,bg-opacity-90 - •Must use ONLY:
text-foreground,bg-primary,border-border, etc.
- •
- •Component props - Props used instead of className when available
Medium Priority (Should Have)
- •TypeScript types - All props and components typed
- •Spacing scale - No arbitrary spacing values
- •Performance - FlatList for long lists, memoization for expensive components
- •Dark mode - Proper dark mode support
Low Priority (Nice to Have)
- •Animations - Using Reanimated instead of Animated API
- •Code organization - Logical component structure
- •Documentation - Comments for complex logic
Escalation Guidance
When a design request cannot be satisfied with existing patterns:
Step 1: Push Back Early
Explain the implications:
- •Performance impact
- •Maintenance burden
- •Breaks theming/dark mode
- •Inconsistent with design system
Example:
"Using arbitrary spacing values like
p-[13px]creates maintenance issues and breaks consistency. Can we usep-3(12px) orp-4(16px) from our spacing scale instead?"
Step 2: Propose Alternatives
Map to existing tokens:
// Request: "Make it slightly lighter red" // ❌ Don't use: bg-red-400 // ✅ Propose: bg-destructive/80 (with alpha)
Suggest new semantic tokens:
// Request: "I need a success color" // ✅ Propose: Add success token to design system // Then use: text-success, bg-success
Step 3: Add to Design System
If truly needed, add token to gluestack-ui-provider/config.ts:
// Add new semantic token
export const config = {
tokens: {
colors: {
success: '#22c55e',
'success-foreground': '#ffffff',
},
},
};
Step 4: Document Exception
If inline style is unavoidable, document why:
/**
* Using inline style for dynamic safe area padding
* Cannot use className as value comes from hook
*/
<Box style={{ paddingBottom: insets.bottom }}>
{/* Content */}
</Box>
Quick Validation Script
Use this mental checklist when reviewing code:
- •✅ Gluestack components? (not React Native)
- •✅ Compound components correct? (ButtonText, InputSlot, etc.)
- •✅ CRITICAL: Semantic tokens ONLY?
- •❌ No
typography-*,neutral-*,gray-*,slate-* - •❌ No numbered colors:
red-500,blue-600 - •❌ No arbitrary values:
[#3b82f6] - •❌ No opacity utilities:
opacity-70 - •✅ Only semantic:
text-foreground,bg-primary,border-border
- •❌ No
- •✅ Component props? (space, size, variant)
- •✅ Spacing scale? (no arbitrary values)
- •✅ TypeScript types? (all props typed)
- •✅ Performance? (FlatList, memoization)
- •✅ Cross-platform? (Gluestack wrappers)
Reference
- •Official Documentation: https://v4.gluestack.io/ui/docs
- •Component Verification:
https://v4.gluestack.io/ui/docs/components/${componentName}/