Card
Cards contain content and actions about a single subject. Follows the Material Design 3 Card specification.
Usage
import { Card, Typography } from '@rootnative/components'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { View } from 'react-native'
export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider>
<View style={{ flex: 1, justifyContent: 'center', padding: 16 }}>
<Card variant="elevated">
<Card.Content>
<Typography variant="titleMedium">Card Title</Typography>
<Typography variant="bodyMedium">Card content goes here.</Typography>
</Card.Content>
</Card>
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}
Variants
| Variant | Use case |
|---|---|
elevated | Default surface with shadow |
filled | Filled surface, no shadow |
outlined | Bordered surface, no shadow |
elevated shadows need the New Architecture on iOSA non-interactive elevated card (no onPress) paints its shadow through the CSS boxShadow surface on iOS. That is deliberate: iOS clips a view's own shadowOpacity-style shadow when the view clips its children to the corner radius, and boxShadow is the one surface the renderer paints outside the clip.
The practical consequence is that this shadow needs the New Architecture, which is the default from React Native 0.76 and cannot be turned off in 0.82+. On the old architecture the card renders flat — as it always did. Android and web are unaffected either way, and an interactive elevated card renders correctly everywhere.
Interactive Card
When onPress is provided, the card renders as a Pressable with state-layer feedback:
import { Card, Typography } from '@rootnative/components'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { Alert, View } from 'react-native'
export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider>
<View style={{ flex: 1, justifyContent: 'center', padding: 16 }}>
<Card variant="filled" onPress={() => Alert.alert('Tapped')}>
<Card.Content>
<Typography>Tap me</Typography>
</Card.Content>
</Card>
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}
Regions
Card is a surface and applies no padding of its own. Use the region slots to get the Material Design 3 card spacing without repeating it at every call site.
| Slot | Role |
|---|---|
Card.Media | Edge-to-edge media. No padding, and the card's corner radius clips it. |
Card.Content | Padded text block (16dp) with a small gap between children. |
Card.Actions | Row of buttons, aligned to the trailing edge by default. |
import { Button, Card, Typography } from '@rootnative/components'
import { ThemeProvider } from '@rootnative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { Alert, Image, View } from 'react-native'
export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider>
<View style={{ flex: 1, justifyContent: 'center', padding: 16 }}>
<Card variant="elevated">
<Card.Media height={160}>
<Image
source={{ uri: 'https://picsum.photos/600/400' }}
resizeMode="cover"
/>
</Card.Media>
<Card.Content>
<Typography variant="titleMedium">Yosemite</Typography>
<Typography variant="bodyMedium">
Granite cliffs, waterfalls and giant sequoia groves.
</Typography>
</Card.Content>
<Card.Actions>
<Button variant="text" onPress={() => Alert.alert('Shared')}>
Share
</Button>
<Button variant="filled" onPress={() => Alert.alert('Booked')}>
Book
</Button>
</Card.Actions>
</Card>
</View>
</ThemeProvider>
</SafeAreaProvider>
)
}
Sizing media
Card.Media needs a height from somewhere, because a React Native Image with a remote URI has no intrinsic size:
height— a fixed height in dp.aspectRatio— width / height, which keeps the media proportional as the card resizes. Ignored whenheightis set.- Neither — the media sizes itself, and its own child provides the height.
With height or aspectRatio set, children stretch to fill the region, so a plain Image needs no style of its own.
Raw children still work
The slots are additive. A card with raw children renders exactly as before and stays unpadded, so you own the spacing:
<Card>
<View style={{ padding: 16 }}>
<Typography>Hand-rolled padding</Typography>
</View>
</Card>
Prefer the slots in new code — that is what keeps spacing consistent between cards.
Props
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
children | ReactNode | - | Yes | Content rendered inside the card surface. The card applies no padding of its own. Use the region slots — `Card.Media`, `Card.Content` and `Card.Actions` — to get the MD3 spacing without repeating it at every call site. Raw children remain fully supported and unpadded, so you own the spacing when you pass them. |
variant | enum | elevated | No | Surface style variant. |
onPress | () => void | - | No | When provided, the card becomes interactive (Pressable). Omit to render as a plain View. |
disabled | boolean | | No | Disables the press interaction and reduces opacity. Only effective when `onPress` is provided. |
containerColor | string | - | No | Override the container (background) color. State-layer colors (hover, press) are derived automatically. |
onKeyDown | (event: { nativeEvent: { key?: string; }; }) => void | - | No | - |
Card.Media
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
children | ReactNode | - | Yes | Media to render — an `Image`, `expo-image`, a video surface, or any other node. The slot does not own the image, so any library works. Children are stretched to fill the slot, so a plain `Image` needs no style of its own. |
height | number | - | No | Fixed height of the media region in dp. Omit to let the media size itself (for example an `Image` with an `aspectRatio` style). |
aspectRatio | number | - | No | Aspect ratio of the media region (width / height). Ignored when `height` is set. |
onKeyDown | (event: { nativeEvent: { key?: string; }; }) => void | - | No | - |
Card.Content
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
children | ReactNode | - | Yes | Content rendered inside the padded region. |
onKeyDown | (event: { nativeEvent: { key?: string; }; }) => void | - | No | - |
Card.Actions
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
children | ReactNode | - | Yes | Action buttons, laid out in a row. |
align | enum | end | No | Horizontal placement of the actions. |
onKeyDown | (event: { nativeEvent: { key?: string; }; }) => void | - | No | - |