Components

Card

Card is a rounded surface that groups related content. It can have a header above the content and a footer below it. The header, the content and the footer are your JSX; the card adds the surface, the padding and the divider.

ui-card.tsx Edit in Playground Open in new tab
// Card: elevated and outlined, with a header and a footer slot.
import { mount, Scroll, signal, Text, View } from "nsh";
import { Button, Card } from "nsh/ui/mobile";

function App() {
  const likes = signal(0);
  return (
    <Scroll style={{ flex: 1, backgroundColor: "#ffffff" }}>
      <View style={{ padding: 16, paddingTop: 24, gap: 16 }}>
        <Text style={{ fontSize: 22, fontWeight: "700" }}>Card</Text>
        <Card
          header={<Text>Weekly summary</Text>}
          footer={
            <View style={{ flexDirection: "row", gap: 8 }}>
              <Button title="Reset" variant="plain" onPress={() => likes.set(0)} />
              <Button title={`Like (${likes()})`} onPress={() => likes.set(likes() + 1)} />
            </View>
          }
        >
          <Text>You walked 42 km this week, 8 km more than last week.</Text>
          <Text style={{ color: "#737373" }}>The header, this body and the footer are the app's JSX.</Text>
        </Card>
        <Card variant="outlined">
          <Text style={{ fontWeight: "600" }}>Outlined</Text>
          <Text>A border, no shadow, and no header or footer: only children.</Text>
        </Card>
        <Card
          variant="outlined"
          header={
            <View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
              <View style={{ width: 32, height: 32, borderRadius: 16, backgroundColor: "#a78bfa" }} />
              <Text>Ada Lovelace</Text>
            </View>
          }
        >
          <Text>A header can be any row, such as an avatar and a name.</Text>
        </Card>
      </View>
    </Scroll>
  );
}

mount(App);
import { Card } from "nsh/ui/mobile";

#Usage

import { mount, Text, View } from "nsh";
import { Button, Card } from "nsh/ui/mobile";

function App() {
  return (
    <View style={{ flex: 1, padding: 16, paddingTop: 48 }}>
      <Card
        header={<Text>Weekly summary</Text>}
        footer={<Button title="Details" variant="plain" onPress={() => console.log("details")} />}
      >
        <Text>You walked 42 km this week.</Text>
        <Text>That is 8 km more than last week.</Text>
      </Card>
    </View>
  );
}

mount(App);

#Layout

  • Header: 16 pt padding at the sides and the top. Text in the header is semibold and 1 pt larger than the theme’s font.size, so a plain <Text> works as a title. A header can be any JSX, for example an avatar and a name in a row.
  • Content: the children are stacked in a column with 8 pt gaps. With a header, the content has 8 pt padding at the top and 16 pt at the sides and the bottom. Without a header, it has 16 pt padding on all sides.
  • Footer: a row aligned to the end, with 8 pt gaps, 16 pt horizontal and 12 pt vertical padding, and a divider above it. Put actions here.
<Card
  variant="outlined"
  header={
    <View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
      <View style={{ width: 32, height: 32, borderRadius: 16, backgroundColor: "#a78bfa" }} />
      <Text>Ada Lovelace</Text>
    </View>
  }
  footer={
    <View style={{ flexDirection: "row", gap: 8 }}>
      <Button title="Ignore" variant="plain" onPress={ignore} />
      <Button title="Reply" onPress={reply} />
    </View>
  }
>
  <Text>Can we move the meeting to Thursday?</Text>
</Card>

The header and footer only exist while you pass them: header={cond() ? <Text>…</Text> : undefined} adds and removes the header, and the content’s padding changes to match.

#Variants

VariantLook
elevated (default)A 1 pt border and a small shadow.
outlinedA 1 pt border, no shadow.
<Card>…</Card>
<Card variant="outlined">…</Card>

#Props

PropTypeDefaultDescription
variant"elevated" | "outlined""elevated"Whether the card has a shadow.
headerJSX.ElementnoneAbove the content: a title or an avatar row. Its text is semibold.
footerJSX.ElementnoneBelow the content, after a divider: actions, aligned to the end.
childrenJSX.ElementnoneThe content.
class, classList, style, testID, onLayout, refApplied to the card’s root view. class and style override the card’s own styles.

Card has no events. Put handlers on your own content.

#Theming

PartTheme key
Surfacegroup_box.background
Text color (inherited by the content)group_box.foreground
Border and footer dividerborder
Corner radiusradius.lg (16 pt by default)
Header text sizefont.size + 1

See Theming.

#Accessibility

The card adds no role or label. Screen readers read the header, content and footer in order, as separate elements.

  • Group: rows in a rounded box with dividers.
  • Button: actions in a footer.