Deck
Browse the catalog

Build / Molecules

Card

  • molecule

The surface every MeetCard experience is built on.

Compose with CardHeader, CardBody, and CardFooter. Those sections bleed their dividers to the card's edge automatically, so padding stays a single decision made once on Card.

import { Card } from '@meetcard/deck'

Example

<Card as="article" interactive> <CardHeader divided> <Heading level={3}>Ada Lovelace</Heading> <Badge tone="success">Connected</Badge> </CardHeader> <CardBody><Text tone="muted">Analytical Engine, London</Text></CardBody> </Card>

Stories

Every preview below is the real component, rendered in this page from the same story Storybook runs. The width control resizes each preview's container; CSS media queries still follow the browser window, so breakpoint behaviour is worth checking in Storybook.

Default
Source
const Default = () => <Card style={{ maxWidth: 360 }}>
    <Text>A card is the base surface for every MeetCard experience.</Text>
</Card>;

The standard composition. Note that padding is set once on Card — the section dividers bleed to the edge on their own.

Composed
Source
const Composed = () => <Card style={{ maxWidth: 360 }}>
    <CardHeader divided>
        <Heading level={3} size="sm">Ada Lovelace
                    </Heading>
        <Badge tone="success" dot>Connected
                    </Badge>
    </CardHeader>
    <CardBody>
        <Text size="sm" tone="muted">Head of Partnerships · MeetCard
                    </Text>
    </CardBody>
    <CardFooter divided>
        <Button size="sm">Share card</Button>
        <Button size="sm" variant="ghost">Dismiss
                    </Button>
    </CardFooter>
</Card>;

Every surface. brand is the one solid fill, and it carries deck-solid- surface so content inside stays legible.

Surfaces
Source
const Surfaces = () => (
  <Stack direction="row" gap={16} wrap>
    {(['elevated', 'default', 'subtle', 'brand'] as const).map((surface) => (
      <Card key={surface} surface={surface} style={{ width: 160 }}>
        <Text size="sm">{surface}</Text>
      </Card>
    ))}
  </Stack>
);

Every elevation. sm is for cards on the page; md and up are for things that float over it.

Elevations
Source
const Elevations = () => (
  <Stack direction="row" gap={24} wrap>
    {(['xs', 'sm', 'md', 'lg', 'xl'] as const).map((elevation) => (
      <Card key={elevation} elevation={elevation} style={{ width: 120 }}>
        <Text size="sm">{elevation}</Text>
      </Card>
    ))}
  </Stack>
);

interactive adds the tactile lift. It is visual only — the card still needs a real focusable child, which is why the button below is present.

Interactive
Source
const Interactive = () => <Card interactive style={{ maxWidth: 360 }}>
    <CardHeader>
        <Heading level={3} size="sm">Tap target
                    </Heading>
    </CardHeader>
    <CardBody>
        <Text size="sm" tone="muted">Hover to see the lift; tab to see focus surface the whole card.
                    </Text>
    </CardBody>
    <CardFooter>
        <Button size="sm" variant="secondary">Open
                    </Button>
    </CardFooter>
</Card>;

Props

PropTypeDefaultDescription
as'div' | 'article' | 'section' | 'li''div'—
elevationShadow | 'none''sm'Elevation step. Figma maps sm to cards; md+ is for floating UI.
interactivebooleanfalseAdds hover/press affordance. Visual only — pair it with a real focusable child (a link or button) so the card is reachable by keyboard.
padding(typeof space)[number]20Inner padding as a Deck space step. Defaults to 20.
surface'default' | 'subtle' | 'elevated' | 'brand''elevated'—

Also accepts everything from HTMLAttributes, so id, data-* and event handlers pass straight through.

Open in Storybookfor controls, the a11y panel and the interaction runner.