# ModalBody

**📖 Live documentation:** https://cds.coinbase.com/components/overlay/ModalBody/?platform=mobile

A main content area component for Modal.

## Import

```tsx
import { ModalBody } from '@coinbase/cds-mobile/overlays/modal/ModalBody'
```

## Examples

ModalBody provides a scrollable content area for [Modal](/components/overlay/Modal). It wraps content in a `ScrollView` with `KeyboardAvoidingView` for proper keyboard handling.

### Basics

ModalBody renders children with default padding. Scrolling is automatically enabled when content exceeds the available height.

```jsx
function BasicExample() {
  const [visible, setVisible] = useState(false);
  return (
    <>
      <Button onPress={() => setVisible(true)}>Open Modal</Button>
      <Modal onRequestClose={() => setVisible(false)} visible={visible}>
        <ModalHeader title="Welcome" closeAccessibilityLabel="Close" />
        <ModalBody>
          <Text>
            This is the modal body content. It provides consistent padding and scrolling behavior.
          </Text>
        </ModalBody>
      </Modal>
    </>
  );
}
```

### Scrollable Content

When content exceeds the modal height, ModalBody automatically enables scrolling. The scroll behavior is dynamically determined based on content size.

```jsx
function ScrollableExample() {
  const [visible, setVisible] = useState(false);
  return (
    <>
      <Button onPress={() => setVisible(true)}>Open Modal</Button>
      <Modal onRequestClose={() => setVisible(false)} visible={visible}>
        <ModalHeader title="Terms of Service" closeAccessibilityLabel="Close" />
        <ModalBody>
          <VStack gap={3}>
            {Array.from({ length: 20 }, (_, i) => (
              <Text key={i}>
                Section {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
                eiusmod tempor incididunt ut labore et dolore magna aliqua.
              </Text>
            ))}
          </VStack>
        </ModalBody>
        <ModalFooter primaryAction={<Button onPress={() => setVisible(false)}>Accept</Button>} />
      </Modal>
    </>
  );
}
```

### Accessibility

Content within ModalBody remains accessible to assistive technologies. The built-in `KeyboardAvoidingView` ensures form inputs stay visible when the keyboard appears.

```jsx
function AccessibilityExample() {
  const [visible, setVisible] = useState(false);
  const [email, setEmail] = useState('');
  const [amount, setAmount] = useState('');

  return (
    <>
      <Button onPress={() => setVisible(true)}>Send Money</Button>
      <Modal onRequestClose={() => setVisible(false)} visible={visible}>
        <ModalHeader title="Send Payment" closeAccessibilityLabel="Close" />
        <ModalBody accessibilityLabel="Payment form">
          <VStack gap={3}>
            <TextInput
              label="Recipient Email"
              placeholder="email@example.com"
              value={email}
              onChangeText={setEmail}
            />
            <TextInput
              label="Amount"
              placeholder="0.00"
              leadingText="$"
              value={amount}
              onChangeText={setAmount}
            />
          </VStack>
        </ModalBody>
        <ModalFooter
          primaryAction={<Button onPress={() => setVisible(false)}>Send</Button>}
          secondaryAction={
            <Button onPress={() => setVisible(false)} variant="secondary">
              Cancel
            </Button>
          }
        />
      </Modal>
    </>
  );
}
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `onPointerCancel` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerCancelCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerDown` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerDownCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerEnter` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerEnterCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerLeave` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerLeaveCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerMove` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerMoveCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerUp` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerUpCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `padding` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingBottom` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingEnd` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingStart` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingTop` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingX` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingY` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `testID` | `string` | No | `-` | Used to locate this view in end-to-end tests. Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID |


