# Card & Stat (React Native)

> Outlined, elevated and plain cards with header, body and footer, and KPI tiles.

GramproKit for React Native · Display · Beta · Source: https://kit.gramproindia.com/native/docs/card

> **Beta:** React Native support is in beta. APIs may change or break between prereleases (npm tag `native`).

_Interactive demo:_ [open the live example](https://kit.gramproindia.com/native/docs/card). The example's source:

```tsx
import { useState } from "react";
import { Text, View } from "react-native";
import { Button } from "@/component-lib/button";
import { Card, CardBody, CardFooter, CardHeader, Stat, trendDirection } from "@/component-lib/card";
import { useKitTheme } from "@/component-lib/shared/native";
import { Row, Section } from "./layout"; // Section and Row: page layout for the demo

export function CardDemo() {
  const { tokens } = useKitTheme();
  const [presses, setPresses] = useState(0);
  return (
    <>
      <Section title="Card · variants">
        {(["outlined", "elevated", "plain"] as const).map((variant) => (
          <Card key={variant} variant={variant}>
            <CardHeader
              title={`${variant[0].toUpperCase()}${variant.slice(1)} card`}
              description="Header, body and footer share the card's padding."
              actions={<Button size="sm" variant="ghost">Edit</Button>}
            />
            <CardBody>The body is where the content goes.</CardBody>
            <CardFooter>
              <Button size="sm" variant="outline">Cancel</Button>
              <Button size="sm">Save</Button>
            </CardFooter>
          </Card>
        ))}
      </Section>

      <Section title="Card · padding and press">
        <Row>
          {(["none", "sm", "md", "lg"] as const).map((padding) => (
            <Card key={padding} padding={padding}>
              <CardBody>{padding}</CardBody>
            </Card>
          ))}
        </Row>
        <Card onPress={() => setPresses((n) => n + 1)} aria-label="Open invoice">
          <CardHeader title="Pressable card" description={`Pressed ${presses}×`} />
          <CardBody>The whole card is one target.</CardBody>
        </Card>
      </Section>

      <Section title="Stat">
        <View style={{ flexDirection: "row", gap: 10 }}>
          <Card style={{ flex: 1 }}>
            <CardBody>
              <Stat
                label="Revenue"
                value="₹12.4L"
                trend={{ direction: trendDirection(12.4), label: "12.4%", description: "vs last month" }}
              />
            </CardBody>
          </Card>
          <Card style={{ flex: 1 }}>
            <CardBody>
              <Stat
                label="Churn"
                value="3.1%"
                icon={<Text style={{ color: tokens.muted }}>◔</Text>}
                trend={{ direction: "up", label: "0.4%", invert: true }}
                help="Lower is better"
              />
            </CardBody>
          </Card>
        </View>
        <View style={{ flexDirection: "row", gap: 10 }}>
          <Card style={{ flex: 1 }}>
            <CardBody>
              <Stat label="Orders" value="1,204" trend={{ direction: "flat", label: "0%" }} />
            </CardBody>
          </Card>
          <Card style={{ flex: 1 }}>
            <CardBody>
              <Stat label="Loading" value="—" loading />
            </CardBody>
          </Card>
        </View>
      </Section>
    </>
  );
}
```

## Install

```bash
npx @grampro/kit@native add Card
```

## Usage

```tsx
import { Card, CardHeader, CardBody, CardFooter, Stat } from "@/component-lib/card";

<Card variant="elevated" onPress={openInvoice}>
  <CardHeader title="INV-0042" description="Due 12 Nov" />
  <CardBody>₹12,400</CardBody>
</Card>
```

## Props

### CardProps

| Prop | Type | Description |
|---|---|---|
| `children` | `ReactNode` |   |
| `variant` | `CardVariant` | Default `outlined`. |
| `padding` | `CardPadding` | Padding applied to the header, body and footer. Default `md`. |
| `onPress` | `(event: GestureResponderEvent) => void` | Makes the whole card pressable (the web's `href`). Keep other pressable content out of it: a button inside a pressable card is two targets in one. |
| `interactive` | `boolean` | Darkens the card while pressed. On by default when `onPress` is set. |
| `style` | `StyleProp<ViewStyle> \| ((state: CardStyleState) => StyleProp<ViewStyle>)` |   |
| `styles` | `Partial<{ root: StyleProp<ViewStyle> }>` |   |

Also accepts the props of `Omit<ViewProps, "children" | "style">`.

### CardHeaderProps

| Prop | Type | Description |
|---|---|---|
| `title` | `ReactNode` | The heading. Announced as a heading by screen readers. |
| `description` | `ReactNode` | A line under the heading, for context the title cannot carry. |
| `actions` | `ReactNode` | Buttons or a menu, aligned to the end of the header. |
| `children` | `ReactNode` |   |
| `style` | `StyleProp<ViewStyle>` |   |
| `styles` | `Partial<{ header: StyleProp<ViewStyle>; title: StyleProp<TextStyle>; description: Style…` |   |

### StatProps

| Prop | Type | Description |
|---|---|---|
| `label` (required) | `ReactNode` | What the figure is. Always visible — a number alone means nothing. |
| `value` (required) | `ReactNode` |   |
| `trend` | `StatTrend` | The change beside the figure. Left out, no arrow is shown. |
| `help` | `ReactNode` | A note under the figure. |
| `icon` | `ReactNode` | An icon beside the label, to identify the metric at a glance. |
| `loading` | `boolean` | Shows placeholder bars instead of the figure. |
| `style` | `StyleProp<ViewStyle>` |   |
| `styles` | `Partial<{ root: StyleProp<ViewStyle>; label: StyleProp<TextStyle>; value: StyleProp<Tex…` |   |

## Native and web

- `onPress` replaces `href`; the whole card becomes one button. Elevated cards use the `raised` token, lighter in dark mode.
- Web version: [Card & Stat on the web](https://kit.gramproindia.com/card).
