---
repo: metamask-mobile
parent: ui-development
---


# MetaMask Mobile React Native UI Development Guidelines

## Core Principle

Always prioritize @metamask/design-system-react-native components and Tailwind CSS patterns over custom implementations.

## Component Hierarchy (STRICT ORDER)

### The Rule: Check Design System First
**Before writing any new component or choosing what to use, ask: "Does @metamask/design-system-react-native have this?"**

1. **FIRST**: Use `@metamask/design-system-react-native` components
   - **Always use for**: Box (layout), Text (typography), Button/ButtonBase/ButtonIcon, Icon, Checkbox
   - **Always use for**: Avatar variants (Account, Base, Favicon, Group, Icon, Network, Token)
   - **Always use for**: Badge variants (Count, Icon, Network, Status, Wrapper)
   - **Rule**: If it exists in the design system, you MUST use it

2. **SECOND**: Use `app/component-library` ONLY if design system lacks it
   - **Use for**: BottomSheet, Tabs, Headers, ListItems, Skeleton, Tags, Modal, Overlay, Toast, RadioButton, etc.
   - **Rule**: These are MetaMask-specific implementations not (yet) in the design system
   - **Important**: component-library components should themselves use design system primitives internally

3. **THIRD**: Feature-specific components
   - **Use for**: Complex, domain-specific UI that combines multiple design system/component-library components
   - **Examples**: `BridgeInputSelector`, `StakeInputView`, `NFTDetailsModal`
   - **Rule**: Must be built using Box, Text, and other design system primitives - NO raw View/Text or StyleSheet
   - **Reuse**: Search for existing feature components before building new ones to avoid duplication

4. **LAST RESORT**: Custom components with StyleSheet
   - **Only when**: Highly specialized one-off needs with no design system equivalent AND no component-library equivalent
   - **Requires**: Strong justification why design system primitives can't be composed

### Decision Tree
```
Need a component?
  ├─ Is it Box, Text, Button, Icon, Avatar, Badge, or Checkbox?
  │  └─ YES → Use @metamask/design-system-react-native [STOP]
  │
  ├─ Is it BottomSheet, Tabs, Header, ListItem, Skeleton, Tag, Modal, etc?
  │  └─ YES → Use app/component-library [STOP]
  │
  ├─ Is it feature-specific UI (e.g., BridgeInputSelector, StakeInputView)?
  │  ├─ Does it already exist? (search codebase for similar components)
  │  │  ├─ YES → Reuse existing component [STOP]
  │  │  └─ NO → Build new component using design system primitives [STOP]
  │  └─
  │
  └─ Can I compose it from Box + Text + other primitives?
     ├─ YES → Compose from design system [STOP]
     └─ NO → Consider if custom implementation is truly necessary
```

### Why This Hierarchy Matters
- **Consistency**: Design system ensures consistent look, feel, and behavior
- **Maintenance**: Centralized updates benefit all consumers
- **Accessibility**: Design system components include a11y best practices
- **Performance**: Optimized implementations tested at scale
- **Type Safety**: Full TypeScript support with JSDoc documentation

## Required Imports for React Native

```tsx
// ALWAYS prefer these imports
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import {
  Box,
  Text,
  Button,
  ButtonBase,
  Icon,
  TextVariant,
  BoxFlexDirection,
  BoxAlignItems,
  BoxJustifyContent,
  // ... other design system components
} from '@metamask/design-system-react-native';
```

## Component Documentation Access

### Type Definitions & JSDoc Comments
All @metamask/design-system-react-native components have comprehensive TypeScript definitions with JSDoc comments:

- **Box**: `/node_modules/@metamask/design-system-react-native/dist/components/Box/Box.types.d.cts`
- **Text**: `/node_modules/@metamask/design-system-react-native/dist/components/Text/Text.types.d.cts`
- **Button**: `/node_modules/@metamask/design-system-react-native/dist/components/Button/Button.types.d.cts`

When unsure about component APIs:
1. Read the `.types.d.cts` files for complete prop documentation
2. Reference `app/component-library/components/design-system.stories.tsx` for usage examples
3. Check GitHub source: https://github.com/MetaMask/metamask-design-system/tree/main/packages/design-system-react-native/src/components

### Box Component Quick Reference
- **Spacing**: Use `gap`, `padding*`, `margin*` props with values 0-12 (maps to 0px-48px)
- **Flexbox**: Use `flexDirection`, `alignItems`, `justifyContent` enum props
- **Colors**: Use `backgroundColor` and `borderColor` with semantic tokens
- **Tailwind**: Use `twClassName` for utilities not covered by props

## Styling Rules (ENFORCE STRICTLY)

### ✅ ALWAYS DO:

- Use `const tw = useTailwind();` hook instead of importing twrnc directly
- Use `Box` component instead of `View`
- Use `Text` component with variants instead of raw Text with styles
- Use `twClassName` prop for static styles
- Use `tw.style()` function for interactive/dynamic styles
- Use design system color tokens: `bg-default`, `text-primary`, `border-muted`
- Use component props first: `variant`, `color`, `size`, etc.

### ❌ NEVER SUGGEST:

- `import tw from 'twrnc'` (use useTailwind hook instead)
- `StyleSheet.create()` (use Tailwind classes)
- Raw `View` or `Text` components (use Box/Text from design system)
- Arbitrary color values like `bg-[#3B82F6]` or `text-[#000000]`
- Inline style objects unless for dynamic values
- Mixing multiple styling approaches unnecessarily

## Code Pattern Templates

### Basic Container:

```tsx
const MyComponent = () => {
  const tw = useTailwind();

  return (
    <Box twClassName="w-full bg-default p-4">
      <Text variant={TextVariant.HeadingMd}>Title</Text>
    </Box>
  );
};
```

### Flex Layout:

```tsx
<Box
  flexDirection={BoxFlexDirection.Row}
  alignItems={BoxAlignItems.Center}
  justifyContent={BoxJustifyContent.Between}
  twClassName="gap-3"
>
```

### Interactive Element:

```tsx
<ButtonBase
  twClassName="h-20 flex-1 rounded-lg bg-muted px-0 py-4"
  style={({ pressed }) =>
    tw.style(
      'w-full flex-row items-center justify-center',
      pressed && 'bg-pressed',
    )
  }
>
  <Text fontWeight={FontWeight.Medium}>Button Text</Text>
</ButtonBase>
```

### Pressable with Tailwind:

```tsx
<Pressable
  style={({ pressed }) =>
    tw.style(
      'w-full flex-row items-center justify-between px-4 py-2',
      pressed && 'bg-pressed',
    )
  }
>
```

## Box Component Best Practices

### Prefer Props Over twClassName for Layout
✅ **DO** - Use typed props for type safety and consistency:
```tsx
<Box
  flexDirection={BoxFlexDirection.Row}
  alignItems={BoxAlignItems.Center}
  justifyContent={BoxJustifyContent.Between}
  gap={3}
  padding={4}
  margin={2}
>
```

❌ **DON'T** - Use twClassName for properties that have dedicated props:
```tsx
<Box twClassName="flex-row items-center justify-between gap-3 p-4 m-2">
```

### When to Use twClassName
Use `twClassName` for:
- Width and height: `w-full`, `h-20`, `w-[337px]`
- Complex positioning: `absolute`, `relative`, `top-0`, `left-0`
- Borders (partial): `rounded-lg`, `border-t`
- Shadows and opacity: `shadow-lg`, `opacity-50`
- Utilities not covered by props: `overflow-hidden`, `z-10`

### Spacing System
- Use numeric props (0-12) for spacing: `padding={4}` = 16px
- Each unit = 4px (so 12 = 48px max)
- For custom spacing beyond 48px, use twClassName: `twClassName="p-20"`

### Color Tokens
Always use semantic color tokens:
```tsx
// ✅ Semantic tokens
<Box backgroundColor={BoxBackgroundColor.BackgroundDefault}>
<Box backgroundColor={BoxBackgroundColor.PrimaryDefault}>
<Box backgroundColor={BoxBackgroundColor.ErrorMuted}>

// ❌ Arbitrary colors
<Box twClassName="bg-[#3B82F6]">
<Box style={{ backgroundColor: '#FF0000' }}>
```

## Component Conversion Guide

| DON'T Use                            | USE Instead                            |
| ------------------------------------ | -------------------------------------- |
| `<View>`                             | `<Box>`                                |
| `<Text style={...}>`                 | `<Text variant={TextVariant.BodyMd}>`  |
| `StyleSheet.create()`                | `twClassName="..."`                    |
| `style={{ backgroundColor: 'red' }}` | `twClassName="bg-error-default"`       |
| `flexDirection: 'row'`               | `flexDirection={BoxFlexDirection.Row}` |
| Manual padding/margin                | `twClassName="p-4 m-2"`                |

## Platform-Specific Gotchas

### ScrollView Inside BottomSheet
When using a `ScrollView` inside a `BottomSheet`, you **MUST** import `ScrollView` from `react-native-gesture-handler`, not from `react-native`. The standard React Native `ScrollView` will not scroll on Android within a gesture-handler-managed `BottomSheet`.

```tsx
// ✅ CORRECT - works on both iOS and Android
import { ScrollView } from 'react-native-gesture-handler';

// ❌ WRONG - will not scroll on Android inside BottomSheet
import { ScrollView } from 'react-native';
```

## Legacy Code Migration Guidelines

### Identifying Legacy Patterns
🚫 **Anti-patterns to refactor when encountered:**
- Files using `StyleSheet.create()`
- Separate `.styles.ts` or `.styles.tsx` files
- Raw `View` components instead of `Box`
- Raw `Text` components with custom styles instead of design system `Text` with variants
- Inline style objects for static styles

### Migration Priority
1. **High Priority**: Components being actively modified
2. **Medium Priority**: Frequently used shared components in `app/component-library`
3. **Low Priority**: Stable legacy components with no active development

### Migration Steps
1. Replace `View` → `Box` from design system
2. Replace `Text` → `Text` with appropriate `TextVariant`
3. Convert `StyleSheet.create()` styles → `twClassName` props or `tw.style()`
4. Convert arbitrary colors → design system color tokens
5. Delete `.styles.ts` files after migration
6. Test thoroughly - layout can shift during migration

### Example Migration

**Before:**
```tsx
import { View, Text, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 16,
    backgroundColor: '#ffffff',
  },
  title: {
    fontSize: 16,
    fontWeight: '500',
    color: '#000000',
  },
});

<View style={styles.container}>
  <Text style={styles.title}>Title</Text>
</View>
```

**After:**
```tsx
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import { Box, Text, TextVariant, BoxFlexDirection, BoxAlignItems, FontWeight } from '@metamask/design-system-react-native';

const tw = useTailwind();

<Box
  flexDirection={BoxFlexDirection.Row}
  alignItems={BoxAlignItems.Center}
  twClassName="p-4 bg-default"
>
  <Text variant={TextVariant.BodyMd} fontWeight={FontWeight.Medium}>
    Title
  </Text>
</Box>
```

## Error Prevention & Code Review Checklist

### Before Committing Code, Verify:
- [ ] No `import tw from 'twrnc'` (must use `useTailwind()` hook)
- [ ] No raw `View` components (use `Box`)
- [ ] No raw `Text` without variants (use `Text` with `TextVariant`)
- [ ] No `StyleSheet.create()` (use `twClassName` or `tw.style()`)
- [ ] No arbitrary color values (use design system tokens)
- [ ] No separate `.styles.ts` files for new components
- [ ] Component props used before `twClassName` for layout
- [ ] Interactive styles use `tw.style()` with state functions
- [ ] `ScrollView` inside `BottomSheet` imported from `react-native-gesture-handler` (not `react-native`)

### When You See These Patterns, IMMEDIATELY Suggest Alternatives:
- Any `import tw from 'twrnc'` → `import { useTailwind } from '@metamask/design-system-twrnc-preset'`
- Any `View` component → `Box` from design system
- Any `StyleSheet` usage → Tailwind classes
- Any arbitrary color values → Design system tokens
- Any manual flex properties → Box component props + twClassName

### AI Agent Guidelines
When suggesting code changes:
1. ALWAYS read component type definitions first for accurate API usage
2. ALWAYS check `design-system.stories.tsx` for real-world patterns
3. ALWAYS search for existing feature-specific components before building new ones (use Glob/Grep to find similar components in feature directories)
4. REJECT any suggestions that violate the hierarchy
5. SUGGEST migrations when encountering legacy patterns
6. EXPLAIN why design system approach is preferred

## Design System Priority

Before suggesting any UI solution:

1. Check if `@metamask/design-system-react-native` has the component
2. Use component's built-in props (variant, color, size)
3. Add layout/spacing with `twClassName`
4. Add interactions with `tw.style()`
5. Only suggest component-library or custom components if design system lacks it

## Reference Examples

Always reference the patterns from `app/component-library/components/design-system.stories.tsx` for proper usage examples.

## Enforcement

- REJECT any code suggestions that use StyleSheet.create()
- REJECT raw View/Text usage when Box/Text components exist
- REQUIRE useTailwind hook for all Tailwind usage
- REQUIRE design system components as first choice
- ENFORCE design token usage over arbitrary values

@app/component-library/components/design-system.stories.tsx
