# Container

> Wraps a Screen: safe-area edges, scrolling, keyboard avoidance, Token padding and a max content width.

Component · core. Registry item `container`, shown in the vega Style. JSON: https://nativecn.dev/r/styles/vega/container.json

## Installation

```sh
npx nativecn-cli@latest add container
```

Also installs: [theme](https://nativecn.dev/docs/theme/theme), [keyboard](https://nativecn.dev/docs/primitives/keyboard).

Packages: `react-native-safe-area-context`.

## Usage

### Container demo

A Screen wrapped in Container.

```tsx
import { Button } from "@/components/button";
import { Container } from "@/components/container";
import { Text } from "@/components/text";

/**
 * A Screen wrapped in Container: safe-area edges, scrolling, keyboard avoidance, Token padding
 * and a 640 max width, all on by default.
 */
export default function ContainerDemo() {
  return (
    <Container>
      <Text variant="h2">Settings</Text>
      <Text color="mutedForeground">
        Content scrolls, stays clear of the notch and home indicator, and keeps the focused field
        above the keyboard.
      </Text>
      <Button label="Save changes" />
    </Container>
  );
}
```

## Props

| Prop | Type and description |
|---|---|
| `...ViewProps` | React Native View props pass through onto the root |
| `children` | the Screen's content |
| `scroll` | boolean (default true): scroll the content |
| `keyboard` | boolean (default true): keep the focused field above the keyboard; needs scroll |
| `padded` | boolean (default true): pad the content by the spacing Tokens |
| `edges` | ('top' \| 'bottom' \| 'left' \| 'right')[] (default ['top', 'bottom']): safe-area edges to keep clear of |
| `maxWidth` | number (default 640): caps the content width and centres it |
| `extraKeyboardSpace` | number: extra room below the content while the keyboard is open |
| `contentContainerStyle` | ViewStyle: merged after the content's own padding and gap |
| `ref` | Ref<View> |
| `style` | ViewStyle, merged last onto the root |

## Accessibility

- No role of its own: it only lays out the Screen.
- Keeps content clear of the safe-area edges (top and bottom by default), so nothing sits under the status bar or home indicator.
- With keyboard on (the default), the focused field stays above the keyboard.
- Scrolls by default, so content taller than the screen stays reachable.

## Notes

Wrap every Screen's content in Container. Scrolling uses the keyboard Primitive's KeyboardAwareScroll, so KeyboardProvider must wrap the root Layout. With a KeyboardStickyFooter below the Container, drop 'bottom' from edges (the footer pads the bottom safe area) and pass its height as extraKeyboardSpace.

## Source

### {components}/container.tsx

```tsx
import { use, type ReactNode, type Ref } from "react";
import { View, type StyleProp, type ViewProps, type ViewStyle } from "react-native";
import {
  SafeAreaInsetsContext,
  initialWindowMetrics,
  type Edge,
} from "react-native-safe-area-context";

import { KeyboardAwareScroll } from "@/components/primitives/keyboard";
import { createStyles } from "@/theme";

export type ContainerEdge = Edge;

export type ContainerProps = ViewProps & {
  children?: ReactNode;
  /** Scroll the content (default `true`). */
  scroll?: boolean;
  /**
   * Keep the focused field above the keyboard (default `true`). Needs `scroll`; it maps to
   * KeyboardAwareScroll's `enabled`.
   */
  keyboard?: boolean;
  /** Pad the content by the Theme's spacing Tokens (default `true`). */
  padded?: boolean;
  /**
   * Safe-area edges to keep clear of (default `['top', 'bottom']`). Drop `'bottom'` when a
   * KeyboardStickyFooter below the Container already pads the bottom safe area.
   */
  edges?: readonly ContainerEdge[];
  /** Caps the content width and centres it, e.g. on tablets (default `640`). */
  maxWidth?: number;
  /**
   * Extra room below the content when the keyboard is open, e.g. the height of a
   * KeyboardStickyFooter rendered after the Container.
   */
  extraKeyboardSpace?: number;
  /** Style for the content wrapper, merged after the Container's own padding and gap. */
  contentContainerStyle?: StyleProp<ViewStyle>;
  ref?: Ref<View>;
};

const DEFAULT_EDGES: readonly ContainerEdge[] = ["top", "bottom"];

const useStyles = createStyles((t) => ({
  root: { flex: 1, backgroundColor: t.colors.background },
  scroll: { flex: 1 },
  content: { flexGrow: 1, width: "100%", alignSelf: "center", gap: t.spacing[4] },
  static: { flex: 1 },
  padded: { paddingHorizontal: t.spacing[4], paddingVertical: t.spacing[4] },
}));

/**
 * Wraps a Screen's content: keeps it clear of the safe area, scrolls it, keeps the focused field
 * above the keyboard, pads it and caps its width. Wrap every Screen in it. `style` is merged
 * last onto the root.
 */
export function Container({
  children,
  scroll = true,
  keyboard = true,
  padded = true,
  edges = DEFAULT_EDGES,
  maxWidth = 640,
  extraKeyboardSpace,
  contentContainerStyle,
  style,
  ...props
}: ContainerProps) {
  const styles = useStyles();
  const insets = use(SafeAreaInsetsContext) ?? initialWindowMetrics?.insets;

  const safeArea: ViewStyle = {
    paddingTop: edges.includes("top") ? (insets?.top ?? 0) : 0,
    paddingBottom: edges.includes("bottom") ? (insets?.bottom ?? 0) : 0,
    paddingLeft: edges.includes("left") ? (insets?.left ?? 0) : 0,
    paddingRight: edges.includes("right") ? (insets?.right ?? 0) : 0,
  };
  const content = [
    styles.content,
    { maxWidth },
    padded ? styles.padded : null,
    contentContainerStyle,
  ];

  return (
    <View style={[styles.root, safeArea, style]} {...props}>
      {scroll ? (
        <KeyboardAwareScroll
          enabled={keyboard}
          extraKeyboardSpace={extraKeyboardSpace}
          style={styles.scroll}
          contentContainerStyle={content}
        >
          {children}
        </KeyboardAwareScroll>
      ) : (
        <View style={[styles.static, ...content]}>{children}</View>
      )}
    </View>
  );
}
```
