Skip to main content

Card Component

The Card component is a versatile container with an elevation system that automatically adjusts background colors based on the theme. It includes optional title and description props, animated press feedback, and flexible children support.

Import

import { Card } from "@/components/Card";

Basic Usage

import { Card } from "@/components/Card";
import { ThemedText } from "@/components/ThemedText";

function MyScreen() {
  return (
    <Card>
      <ThemedText>Card content goes here</ThemedText>
    </Card>
  );
}

Props

elevation
number
default:"1"
Elevation level that determines the background color:
  • 0: theme.backgroundRoot
  • 1: theme.backgroundDefault
  • 2: theme.backgroundSecondary
  • 3: theme.backgroundTertiary
title
string
Optional title displayed at the top of the card using ThemedText with h4 typography
description
string
Optional description displayed below the title with reduced opacity (0.7) and small typography
children
React.ReactNode
Content to render inside the card
onPress
() => void
Callback function invoked when the card is pressed. When provided, the card becomes interactive with press animations
style
ViewStyle
Additional styles to apply to the card container

Real-World Examples

Goals Screen Input Groups

From GoalsScreen.tsx:156-161:
<Card style={styles.section}>
  <ThemedText style={styles.sectionTitle}>
    T1 - Main Lifts (1RM Goals)
  </ThemedText>
  {mainLifts.map((exercise) => renderExerciseInput(exercise))}
</Card>
This demonstrates using Card as a grouping container for related inputs, with custom styling applied.

Elevation System

The Card component uses a semantic elevation system for visual hierarchy:
Background: theme.backgroundRootUsed for the lowest level, typically matching the root background.
<Card elevation={0}>
  Root level content
</Card>

Animation Behavior

When onPress is provided, the Card becomes animated:

Press Animation

const handlePressIn = () => {
  scale.value = withSpring(0.98, springConfig);
};

const handlePressOut = () => {
  scale.value = withSpring(1, springConfig);
};
  • Press In: Scales down to 0.98
  • Press Out: Springs back to 1.0
  • Spring Config: Same configuration as Button for consistency

Styling

Default Styles

const styles = StyleSheet.create({
  card: {
    padding: Spacing.xl,
    borderRadius: BorderRadius["2xl"],
  },
  cardTitle: {
    marginBottom: Spacing.sm,
  },
  cardDescription: {
    opacity: 0.7,
  },
});

Custom Styling Example

<Card 
  style={{
    marginBottom: 16,
    paddingVertical: 20,
  }}
  title="Custom Card"
>
  <ThemedText>Content</ThemedText>
</Card>

Layout Patterns

Vertical Stack

function MyScreen() {
  return (
    <ScrollView>
      <Card title="First Section" style={{ marginBottom: 16 }}>
        <ThemedText>Content 1</ThemedText>
      </Card>
      <Card title="Second Section" style={{ marginBottom: 16 }}>
        <ThemedText>Content 2</ThemedText>
      </Card>
      <Card title="Third Section">
        <ThemedText>Content 3</ThemedText>
      </Card>
    </ScrollView>
  );
}

Horizontal Grid

function MyScreen() {
  return (
    <View style={{ flexDirection: 'row', gap: 12 }}>
      <Card style={{ flex: 1 }} title="Card 1">
        <ThemedText>Content</ThemedText>
      </Card>
      <Card style={{ flex: 1 }} title="Card 2">
        <ThemedText>Content</ThemedText>
      </Card>
    </View>
  );
}

TypeScript Interface

interface CardProps {
  elevation?: number;
  title?: string;
  description?: string;
  children?: React.ReactNode;
  onPress?: () => void;
  style?: ViewStyle;
}

Background Color Helper

The Card uses an internal helper function to determine background color:
const getBackgroundColorForElevation = (
  elevation: number,
  theme: any,
): string => {
  switch (elevation) {
    case 1:
      return theme.backgroundDefault;
    case 2:
      return theme.backgroundSecondary;
    case 3:
      return theme.backgroundTertiary;
    default:
      return theme.backgroundRoot;
  }
};

Best Practices

Maintain a consistent elevation hierarchy across your app:
// Screen background: elevation 0
// Standard cards: elevation 1
// Nested cards: elevation 2
// Modals/overlays: elevation 3
Use the title prop for simple headers, but use children for complex headers:
// Simple
<Card title="Settings">
  {/* settings options */}
</Card>

// Complex
<Card>
  <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
    <ThemedText type="h4">Settings</ThemedText>
    <Icon name="settings" />
  </View>
  {/* settings options */}
</Card>
Make cards interactive when they represent navigable items:
<Card 
  title="Workout Plan"
  onPress={() => navigation.navigate('WorkoutDetail')}
>
  <ThemedText>Tap to view details</ThemedText>
</Card>

Accessibility

  • When onPress is provided, the card becomes a pressable element
  • Title uses semantic heading typography (h4)
  • Description has reduced opacity for visual hierarchy
  • Animated feedback provides clear interaction cues

Source Code

Location: client/components/Card.tsx The Card component is built using:
  • react-native-reanimated for press animations
  • ThemedText for title and description
  • useTheme hook for elevation-based backgrounds
  • Animated.createAnimatedComponent(Pressable) for interactive cards

Build docs developers (and LLMs) love