Container
Wraps a Screen: safe-area edges, scrolling, keyboard avoidance, Token padding and a max content width.
Componentcore
Style
Light
Dark
Your app is ready.
Inter body text, Inter headings.
smmdlgxl
you@example.com
Continue
a0: Vega, neutral, neutral accent, default radius, Inter.Change PresetInstallation
$
npx nativecn-cli@latest add containerAlso installs: theme, keyboard.
Packages: react-native-safe-area-context.
Usage
Container demo
A Screen wrapped in Container.
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>
);
}Container demo
A Screen wrapped in Container.
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
The Vega Style's code, exactly as add copies it into your app.
{components}/container.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>
);
}{components}/container.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>
);
}
