Skip to content
React NativeBeta

Card & Stat

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

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

On this page
Web preview — real device may differ
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

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

PropTypeDescription
childrenReactNode
variantCardVariantDefault outlined.
paddingCardPaddingPadding applied to the header, body and footer. Default md.
onPress(event: GestureResponderEvent) => voidMakes 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.
interactivebooleanDarkens the card while pressed. On by default when onPress is set.
styleStyleProp<ViewStyle> | ((state: CardStyleState) => StyleProp<ViewStyle>)
stylesPartial<{ root: StyleProp<ViewStyle> }>

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

CardHeaderProps

PropTypeDescription
titleReactNodeThe heading. Announced as a heading by screen readers.
descriptionReactNodeA line under the heading, for context the title cannot carry.
actionsReactNodeButtons or a menu, aligned to the end of the header.
childrenReactNode
styleStyleProp<ViewStyle>
stylesPartial<{ header: StyleProp<ViewStyle>; title: StyleProp<TextStyle>; description: Style…

StatProps

PropTypeDescription
label (required)ReactNodeWhat the figure is. Always visible — a number alone means nothing.
value (required)ReactNode
trendStatTrendThe change beside the figure. Left out, no arrow is shown.
helpReactNodeA note under the figure.
iconReactNodeAn icon beside the label, to identify the metric at a glance.
loadingbooleanShows placeholder bars instead of the figure.
styleStyleProp<ViewStyle>
stylesPartial<{ 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.