# ScrollView

> A scrollable view component that allows content to be scrolled when it exceeds the container size.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/scroll-view
- Markdown: https://ui.ahmedbna.com/docs/components/scroll-view.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/scroll-view.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/scroll-view.json
- Install: `npx bna-ui add scroll-view`
- Preview recording: https://demo.ahmedbna.com/0240-scroll-view-demo.MP4

---

**Example:** A basic scrollable view with content

```tsx
// components/demo/scroll-view/scroll-view-demo.tsx
import { ScrollView } from '@/components/ui/scroll-view';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function ScrollViewDemo() {
  const card = useColor('card');

  return (
    <View
      style={{
        height: 200,
        borderWidth: 1,
        borderColor: card,
        borderRadius: BORDER_RADIUS,
      }}
    >
      <ScrollView style={{ padding: 16 }}>
        {Array.from({ length: 20 }, (_, i) => (
          <Text
            key={i}
            style={{
              marginBottom: 8,
              padding: 12,
              backgroundColor: card,
              borderRadius: 6,
            }}
          >
            Scrollable item {i + 1}
          </Text>
        ))}
      </ScrollView>
    </View>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add scroll-view
```

### Manual

**1.** Copy and paste the following code into your project.

```tsx
// components/ui/scroll-view.tsx
import { forwardRef } from 'react';
import { ScrollView as RNScrollView, ScrollViewProps } from 'react-native';

export const ScrollView = forwardRef<RNScrollView, ScrollViewProps>(
  ({ style, ...otherProps }, ref) => {
    return (
      <RNScrollView
        ref={ref}
        style={[{ backgroundColor: 'transparent' }, style]}
        {...otherProps}
      />
    );
  }
);
```

**2.** Update the import paths to match your project setup.

## Usage

```tsx
import { ScrollView } from '@/components/ui/scroll-view';
```

```tsx
<ScrollView>
  <Text>Your scrollable content here</Text>
</ScrollView>
```

## Examples

#### Default

**Example:** A basic scrollable view with content

```tsx
// components/demo/scroll-view/scroll-view-demo.tsx
import { ScrollView } from '@/components/ui/scroll-view';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function ScrollViewDemo() {
  const card = useColor('card');

  return (
    <View
      style={{
        height: 200,
        borderWidth: 1,
        borderColor: card,
        borderRadius: BORDER_RADIUS,
      }}
    >
      <ScrollView style={{ padding: 16 }}>
        {Array.from({ length: 20 }, (_, i) => (
          <Text
            key={i}
            style={{
              marginBottom: 8,
              padding: 12,
              backgroundColor: card,
              borderRadius: 6,
            }}
          >
            Scrollable item {i + 1}
          </Text>
        ))}
      </ScrollView>
    </View>
  );
}
```

#### Vertical Scrolling

**Example:** Vertical scrolling with multiple items

```tsx
// components/demo/scroll-view/scroll-view-vertical.tsx
import { ScrollView } from '@/components/ui/scroll-view';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function ScrollViewVertical() {
  const colors = [
    '#ef4444',
    '#f97316',
    '#eab308',
    '#22c55e',
    '#3b82f6',
    '#8b5cf6',
    '#ec4899',
  ];

  return (
    <View
      style={{
        height: 300,
        borderWidth: 1,
        borderColor: '#e5e7eb',
        borderRadius: BORDER_RADIUS,
      }}
    >
      <ScrollView
        contentContainerStyle={{ padding: 16, gap: 12 }}
        showsVerticalScrollIndicator={true}
      >
        {Array.from({ length: 15 }, (_, i) => (
          <View
            key={i}
            style={{
              height: 80,
              backgroundColor: colors[i % colors.length],
              borderRadius: 12,
              justifyContent: 'center',
              alignItems: 'center',
            }}
          >
            <Text style={{ color: 'white', fontWeight: 'bold', fontSize: 16 }}>
              Card {i + 1}
            </Text>
          </View>
        ))}
      </ScrollView>
    </View>
  );
}
```

#### Horizontal Scrolling

**Example:** Horizontal scrolling with cards

```tsx
// components/demo/scroll-view/scroll-view-horizontal.tsx
import { ScrollView } from '@/components/ui/scroll-view';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function ScrollViewHorizontal() {
  const gradients = [
    ['#ff9a9e', '#fecfef'],
    ['#a18cd1', '#fbc2eb'],
    ['#fad0c4', '#ffd1ff'],
    ['#ffecd2', '#fcb69f'],
    ['#a8edea', '#fed6e3'],
    ['#d299c2', '#fef9d7'],
    ['#89f7fe', '#66a6ff'],
  ];

  return (
    <View
      style={{
        height: 150,
        borderWidth: 1,
        borderColor: '#e5e7eb',
        borderRadius: BORDER_RADIUS,
      }}
    >
      <ScrollView
        horizontal={true}
        contentContainerStyle={{ padding: 16, gap: 16, alignItems: 'center' }}
        showsHorizontalScrollIndicator={true}
      >
        {Array.from({ length: 10 }, (_, i) => (
          <View
            key={i}
            style={{
              width: 120,
              height: 100,
              backgroundColor: gradients[i % gradients.length][0],
              borderRadius: 12,
              justifyContent: 'center',
              alignItems: 'center',
              shadowColor: '#000',
              shadowOffset: { width: 0, height: 2 },
              shadowOpacity: 0.1,
              shadowRadius: 4,
              elevation: 3,
            }}
          >
            <Text style={{ color: 'white', fontWeight: 'bold', fontSize: 14 }}>
              Item {i + 1}
            </Text>
          </View>
        ))}
      </ScrollView>
    </View>
  );
}
```

#### Nested ScrollViews

**Example:** ScrollViews nested within each other

```tsx
// components/demo/scroll-view/scroll-view-nested.tsx
import { ScrollView } from '@/components/ui/scroll-view';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function ScrollViewNested() {
  return (
    <View
      style={{
        height: 300,
        borderWidth: 1,
        borderColor: '#e5e7eb',
        borderRadius: BORDER_RADIUS,
      }}
    >
      <ScrollView contentContainerStyle={{ padding: 16 }}>
        <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 16 }}>
          Vertical Scroll
        </Text>

        {Array.from({ length: 3 }, (_, sectionIndex) => (
          <View key={sectionIndex} style={{ marginBottom: 24 }}>
            <Text style={{ fontSize: 16, fontWeight: '600', marginBottom: 12 }}>
              Section {sectionIndex + 1}
            </Text>

            <View
              style={{
                height: 120,
                borderWidth: 1,
                borderColor: '#d1d5db',
                borderRadius: BORDER_RADIUS,
              }}
            >
              <ScrollView
                horizontal={true}
                contentContainerStyle={{
                  padding: 12,
                  gap: 12,
                  alignItems: 'center',
                }}
                showsHorizontalScrollIndicator={true}
              >
                {Array.from({ length: 8 }, (_, itemIndex) => (
                  <View
                    key={itemIndex}
                    style={{
                      width: 80,
                      height: 80,
                      backgroundColor: '#3b82f6',
                      borderRadius: BORDER_RADIUS,
                      justifyContent: 'center',
                      alignItems: 'center',
                    }}
                  >
                    <Text
                      style={{
                        color: 'white',
                        fontSize: 12,
                        fontWeight: 'bold',
                      }}
                    >
                      {sectionIndex + 1}.{itemIndex + 1}
                    </Text>
                  </View>
                ))}
              </ScrollView>
            </View>
          </View>
        ))}

        <Text
          style={{
            padding: 16,
            borderRadius: BORDER_RADIUS,
            textAlign: 'center',
          }}
        >
          End of scrollable content
        </Text>
      </ScrollView>
    </View>
  );
}
```

#### With Pull to Refresh

**Example:** ScrollView with pull-to-refresh functionality

```tsx
// components/demo/scroll-view/scroll-view-refresh.tsx
import { ScrollView } from '@/components/ui/scroll-view';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React, { useCallback, useState } from 'react';
import { RefreshControl } from 'react-native';

export function ScrollViewRefresh() {
  const card = useColor('card');
  const green = useColor('green');

  const [refreshing, setRefreshing] = useState(false);
  const [lastRefresh, setLastRefresh] = useState(
    new Date().toLocaleTimeString()
  );

  const onRefresh = useCallback(() => {
    setRefreshing(true);
    setTimeout(() => {
      setRefreshing(false);
      setLastRefresh(new Date().toLocaleTimeString());
    }, 2000);
  }, []);

  return (
    <View
      style={{
        height: 300,
        borderWidth: 1,
        borderColor: '#e5e7eb',
        borderRadius: BORDER_RADIUS,
      }}
    >
      <ScrollView
        contentContainerStyle={{ padding: 16 }}
        refreshControl={
          <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
        }
      >
        <View
          style={{
            padding: 16,
            backgroundColor: green,
            borderRadius: BORDER_RADIUS,
            marginBottom: 16,
          }}
        >
          <Text style={{ fontWeight: 'bold', color: '#000', marginBottom: 4 }}>
            Pull to Refresh
          </Text>
          <Text style={{ color: '#047857' }}>
            Last refreshed: {lastRefresh}
          </Text>
        </View>

        {Array.from({ length: 15 }, (_, i) => (
          <View
            key={i}
            style={{
              padding: 16,
              backgroundColor: card,
              borderRadius: BORDER_RADIUS,
              marginBottom: 8,
              borderLeftWidth: 4,
              borderLeftColor: '#3b82f6',
            }}
          >
            <Text style={{ fontWeight: '600', marginBottom: 4 }}>
              News Item {i + 1}
            </Text>
            <Text style={{ color: '#6b7280' }}>
              This is a sample news item that demonstrates the pull-to-refresh
              functionality.
            </Text>
          </View>
        ))}
      </ScrollView>
    </View>
  );
}
```

#### Custom Styling

**Example:** ScrollView with custom styling and padding

```tsx
// components/demo/scroll-view/scroll-view-styled.tsx
import { ScrollView } from '@/components/ui/scroll-view';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function ScrollViewStyled() {
  return (
    <View style={{ height: 300, borderRadius: 16, overflow: 'hidden' }}>
      <ScrollView
        style={{
          backgroundColor: '#1f2937',
          borderRadius: 16,
        }}
        contentContainerStyle={{
          padding: 20,
          gap: 16,
        }}
        showsVerticalScrollIndicator={true}
      >
        <View
          style={{
            padding: 20,
            backgroundColor: '#374151',
            borderRadius: 12,
            borderWidth: 1,
            borderColor: '#4b5563',
          }}
        >
          <Text
            style={{
              color: '#f9fafb',
              fontSize: 18,
              fontWeight: 'bold',
              marginBottom: 8,
            }}
          >
            🌙 Dark Theme ScrollView
          </Text>
          <Text style={{ color: '#d1d5db' }}>
            This ScrollView uses custom dark styling with rounded corners and
            shadows.
          </Text>
        </View>

        {Array.from({ length: 12 }, (_, i) => (
          <View
            key={i}
            style={{
              padding: 16,
              backgroundColor: i % 2 === 0 ? '#6366f1' : '#8b5cf6',
              borderRadius: 12,
              shadowColor: '#000',
              shadowOffset: { width: 0, height: 4 },
              shadowOpacity: 0.3,
              shadowRadius: 8,
              elevation: 6,
            }}
          >
            <Text
              style={{
                color: 'white',
                fontWeight: 'bold',
                fontSize: 16,
                marginBottom: 4,
              }}
            >
              Card {i + 1}
            </Text>
            <Text style={{ color: '#e5e7eb', opacity: 0.9 }}>
              Beautiful custom styled card with gradient-like colors and
              shadows.
            </Text>
          </View>
        ))}

        <View
          style={{
            padding: 20,
            backgroundColor: '#059669',
            borderRadius: 12,
            alignItems: 'center',
          }}
        >
          <Text style={{ color: 'white', fontWeight: 'bold' }}>
            ✨ End of styled content
          </Text>
        </View>
      </ScrollView>
    </View>
  );
}
```

#### Scroll Indicators

**Example:** ScrollView with custom scroll indicators

```tsx
// components/demo/scroll-view/scroll-view-indicators.tsx
import { ScrollView } from '@/components/ui/scroll-view';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function ScrollViewIndicators() {
  const card = useColor('card');

  return (
    <View style={{ gap: 16 }}>
      {/* Vertical with indicators */}
      <View>
        <Text style={{ fontWeight: 'bold', marginBottom: 8 }}>
          With Scroll Indicators
        </Text>
        <View
          style={{
            height: 200,
            borderWidth: 1,
            borderColor: '#e5e7eb',
            borderRadius: BORDER_RADIUS,
          }}
        >
          <ScrollView
            contentContainerStyle={{ padding: 16, gap: 8 }}
            showsVerticalScrollIndicator={true}
            indicatorStyle='white'
          >
            {Array.from({ length: 12 }, (_, i) => (
              <Text
                key={i}
                style={{
                  padding: 12,
                  backgroundColor: card,
                  borderRadius: BORDER_RADIUS,
                }}
              >
                Item {i + 1} - Scroll indicators visible
              </Text>
            ))}
          </ScrollView>
        </View>
      </View>

      {/* Horizontal without indicators */}
      <View style={{ marginTop: 8 }}>
        <Text style={{ fontWeight: 'bold', marginBottom: 8 }}>
          Without Scroll Indicators
        </Text>
        <View
          style={{
            height: 150,
            borderWidth: 1,
            borderColor: '#e5e7eb',
            borderRadius: BORDER_RADIUS,
          }}
        >
          <ScrollView
            horizontal={true}
            contentContainerStyle={{
              padding: 16,
              gap: 12,
              alignItems: 'center',
            }}
            showsHorizontalScrollIndicator={false}
          >
            {Array.from({ length: 8 }, (_, i) => (
              <View
                key={i}
                style={{
                  width: 80,
                  height: 60,
                  backgroundColor: '#fbbf24',
                  borderRadius: BORDER_RADIUS,
                  justifyContent: 'center',
                  alignItems: 'center',
                }}
              >
                <Text style={{ color: 'white', fontWeight: 'bold' }}>
                  {i + 1}
                </Text>
              </View>
            ))}
          </ScrollView>
        </View>
      </View>
    </View>
  );
}
```

#### Content Inset

**Example:** ScrollView with content inset adjustments

```tsx
// components/demo/scroll-view/scroll-view-inset.tsx
import { ScrollView } from '@/components/ui/scroll-view';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS } from '@/theme/globals';
import React from 'react';

export function ScrollViewInset() {
  const card = useColor('card');

  return (
    <View style={{ gap: 16 }}>
      {/* Standard ScrollView */}
      <View>
        <Text style={{ fontWeight: 'bold', marginBottom: 8 }}>
          Standard Content
        </Text>
        <View
          style={{
            height: 150,
            borderWidth: 1,
            borderColor: '#e5e7eb',
            borderRadius: BORDER_RADIUS,
          }}
        >
          <ScrollView contentContainerStyle={{ padding: 16 }}>
            {Array.from({ length: 8 }, (_, i) => (
              <Text
                key={i}
                style={{
                  padding: 12,
                  marginBottom: 8,
                  backgroundColor: card,
                  borderRadius: BORDER_RADIUS,
                }}
              >
                Standard item {i + 1}
              </Text>
            ))}
          </ScrollView>
        </View>
      </View>

      {/* ScrollView with content inset */}
      <View>
        <Text style={{ fontWeight: 'bold', marginBottom: 8 }}>
          With Content Inset Adjustments
        </Text>
        <View
          style={{
            height: 150,
            borderWidth: 1,
            borderColor: '#e5e7eb',
            borderRadius: BORDER_RADIUS,
          }}
        >
          <ScrollView
            contentContainerStyle={{
              paddingTop: 32,
              paddingBottom: 32,
              paddingHorizontal: 24,
            }}
            contentInset={{ top: 20, bottom: 20 }}
            contentInsetAdjustmentBehavior='automatic'
          >
            <View
              style={{
                padding: 16,
                backgroundColor: '#ddd6fe',
                borderRadius: BORDER_RADIUS,
                marginBottom: 16,
                borderWidth: 2,
                borderColor: '#8b5cf6',
              }}
            >
              <Text style={{ fontWeight: 'bold', color: '#5b21b6' }}>
                Header with Inset
              </Text>
            </View>

            {Array.from({ length: 6 }, (_, i) => (
              <Text
                key={i}
                style={{
                  padding: 12,
                  marginBottom: 8,
                  backgroundColor: '#fef3c7',
                  borderRadius: BORDER_RADIUS,
                  borderLeftWidth: 3,
                  borderLeftColor: '#f59e0b',
                }}
              >
                Inset adjusted item {i + 1}
              </Text>
            ))}

            <View
              style={{
                padding: 16,
                backgroundColor: '#dcfce7',
                borderRadius: BORDER_RADIUS,
                marginTop: 8,
                borderWidth: 2,
                borderColor: '#22c55e',
              }}
            >
              <Text style={{ fontWeight: 'bold', color: '#15803d' }}>
                Footer with Inset
              </Text>
            </View>
          </ScrollView>
        </View>
      </View>
    </View>
  );
}
```

## API Reference

### ScrollView

A wrapper around React Native's ScrollView with enhanced styling capabilities. All other props from React Native's ScrollView are also supported.

| Prop                             | Type                                   | Default   | Description                                                                   |
| -------------------------------- | -------------------------------------- | --------- | ----------------------------------------------------------------------------- |
| `style`                          | `ViewStyle`                            | -         | Additional styles to apply to the scroll view.                                |
| `contentContainerStyle`          | `ViewStyle`                            | -         | Styles applied to the scroll view content container.                          |
| `horizontal`                     | `boolean`                              | `false`   | When true, the scroll view's children are arranged horizontally.              |
| `showsVerticalScrollIndicator`   | `boolean`                              | `true`    | When true, shows a vertical scroll indicator.                                 |
| `showsHorizontalScrollIndicator` | `boolean`                              | `true`    | When true, shows a horizontal scroll indicator.                               |
| `scrollEnabled`                  | `boolean`                              | `true`    | When false, the content does not scroll.                                      |
| `bounces`                        | `boolean`                              | `true`    | When true, the scroll view bounces when it reaches the end.                   |
| `bouncesZoom`                    | `boolean`                              | `true`    | When true, gestures can drive zoom past min/max.                              |
| `alwaysBounceVertical`           | `boolean`                              | `false`   | When true, the scroll view bounces vertically even when content is smaller.   |
| `alwaysBounceHorizontal`         | `boolean`                              | `false`   | When true, the scroll view bounces horizontally even when content is smaller. |
| `pagingEnabled`                  | `boolean`                              | `false`   | When true, the scroll view stops on multiples of the scroll view's size.      |
| `scrollEventThrottle`            | `number`                               | -         | Controls how often the scroll event will be fired while scrolling.            |
| `onScroll`                       | `function`                             | -         | Fires at most once per frame during scrolling.                                |
| `onScrollBeginDrag`              | `function`                             | -         | Called when the user begins to drag the scroll view.                          |
| `onScrollEndDrag`                | `function`                             | -         | Called when the user stops dragging the scroll view.                          |
| `onMomentumScrollBegin`          | `function`                             | -         | Called when the momentum scroll starts.                                       |
| `onMomentumScrollEnd`            | `function`                             | -         | Called when the momentum scroll ends.                                         |
| `refreshControl`                 | `RefreshControl`                       | -         | A RefreshControl component for pull-to-refresh functionality.                 |
| `keyboardDismissMode`            | `'none' \| 'on-drag' \| 'interactive'` | `'none'`  | Determines when the keyboard is dismissed.                                    |
| `keyboardShouldPersistTaps`      | `'always' \| 'never' \| 'handled'`     | `'never'` | Determines when the keyboard should stay visible after a tap.                 |

## Accessibility

The ScrollView component maintains React Native's built-in accessibility features:

- Automatically announces scrollable content to screen readers
- Supports gesture-based navigation for users with accessibility needs
- Maintains proper focus management during scrolling
- Compatible with VoiceOver and TalkBack
- Supports dynamic text sizing and high contrast modes
