Deck
Browse the catalog

Foundations

Deck’s token architecture has three layers. Components only ever reference the middle one.

Primitive tokens        Semantic tokens             Component usage
--deck-primitive-*  →   --deck-color-action-*   →   .deck-button--primary
#2e6e5b                 action/primary              background-color

Never reference a primitive from a component. Semantic tokens are what resolve differently between light and dark mode; a primitive is a fixed value.

Brand

MeetCard’s mark and wordmark, exported as Mark and Wordmark. Fixed brand colors (Signal Green + Ink), not semantic tokens — the logo stays MeetCard green in both themes rather than adapting like body content does.

Mark

Wordmark

This is the “Full Color — Light” variant: Ink-colored type needs a light backdrop to read. No dark or mono variant has been supplied yet, so avoid placing Wordmark directly on a dark or solid-brand surface. Mark alone (just the two green card shapes, no type) is safer on darker surfaces — it’s what AppBar’s stories use in the app shell.

import { Mark, Wordmark } from '@meetcard/deck'

<AppBar brand={<Mark style={{ height: 24, width: 24 }} />} />

Deck’s own identity vs. MeetCard’s

This Storybook’s own chrome (its favicon and sidebar logo, in .storybook/theme.ts and public/favicon.svg) uses a separate mark and wordmark for Deck itself — a stack of three cards, distinct from the Mark/Wordmark above. That’s deliberate: this Storybook documents Deck the tool, so its chrome carries Deck’s own identity, while Mark/Wordmark (above) are MeetCard’s product brand, meant for actual product UI. Deck’s own assets live in .storybook/brand/ and aren’t part of the public API — there was no product need to export them as components.

Color

Sourced from Figma → Brand guidelines → MeetCard - Brand Colors (July 2026). Light values are exact; dark values are derived and verified to clear WCAG AA (4.5:1 body text, 3:1 UI boundaries) against their own surface.

background

default
subtle
elevated
overlay
brand
brand-subtle

text

default
muted
disabled
inverse
brand
display
error
warning

border

default
strong
focus
error
brand

action

primary
primary-hover
primary-pressed
primary-disabled
primary-text
secondary
secondary-border
secondary-text
destructive
destructive-hover
destructive-text

status

success-background
success-text
success-border
warning-background
warning-text
warning-border
error-background
error-text
error-border

Two deliberate dark-mode inversions

  • Slate becomes the elevated surface. The page sits deeper than Slate, so cards read as raised rather than flush.
  • Solid fills flip their on-color. In dark mode action/primary and action/destructive are bright with Ink text, because white on a usable-lightness green cannot reach 4.5:1.

Typography

Inter, across four semantic scales. All sizes in px, aligned to a 4px baseline.

display

2xlMeetCard
xlMeetCard
lgMeetCard
mdMeetCard
smMeetCard

heading

xlYour network is your net worth.
lgYour network is your net worth.
mdYour network is your net worth.
smYour network is your net worth.
xsYour network is your net worth.

body

xlCapture every connection, activate every relationship.
lgCapture every connection, activate every relationship.
mdCapture every connection, activate every relationship.
smCapture every connection, activate every relationship.
xsCapture every connection, activate every relationship.

label

lgFollow up · Add to CRM · Share card
mdFollow up · Add to CRM · Share card
smFollow up · Add to CRM · Share card
xsFollow up · Add to CRM · Share card

Display serif

--deck-font-serif (Fraunces) is a second typeface reserved for signature, editorial moments — today that’s just the name on PersonCard. It is opt-in via <Heading family="serif">, never the default, and like Inter, Deck does not bundle or fetch it: load Fraunces however your app loads fonts, and the stack degrades to Georgia.

Spacing

Base 4px, Tailwind-aligned. Use via Stack’s gap, or var(--deck-space-N).

0
1
2
4
6
8
10
12
16
20
24
28
32
40
48
64
80
96

Radius

lg (8px) is the default for cards, buttons, and inputs. full is for avatars, badges, and pill buttons.

none
sm
md
lg
xl
2xl
3xl
full

Elevation

Applied per component, never page-level.

xs

focus rings, inputs

sm

cards, menus

md

panels, popovers

lg

modals, drawers

xl

command palette

2xl

sheets, overlays

Motion & breakpoints

Motion and breakpoints are not yet specified in the Figma kit — the values below are Deck defaults and should be replaced when design publishes them.

Token Value Use
--deck-duration-fast 120ms Hover, color changes
--deck-duration-base 200ms Elevation, transforms
--deck-duration-slow 320ms Entrances, overlays
--deck-ease-standard cubic-bezier(0.2, 0, 0, 1) Default

All durations collapse to 0ms under prefers-reduced-motion: reduce.

Breakpoints are sm 640, md 768, lg 1024, xl 1280, 2xl 1536. CSS cannot read a custom property inside a media query, so JS consumers should use the mediaQuery helper:

import { mediaQuery } from '@meetcard/deck'

window.matchMedia(mediaQuery('md')) // "(min-width: 768px)"

Checking a component responsively

Use Viewport size in the toolbar above the canvas. It carries device widths for “how does this look on a phone”, and Edge · pairs for “does the breakpoint fire correctly”.

The control only appears on a story, not on a Docs page. A Docs page renders many stories at once, so there is no single canvas to resize — if you clicked a component name in the sidebar you landed on Docs, and the control is genuinely absent. Open any story beneath it and it appears.

The edge presets straddle rather than sit on a breakpoint, because Deck’s queries are max-width: at exactly 640px the mobile rule matches, so a preset named “sm 640” would show the narrow layout — the opposite of what you’d expect. sm − 1px and sm + 1px show each side honestly.

Two things worth knowing while you’re in there:

  • Only sm 640 and lg 1024 are actually queried in CSS. md 768, xl 1280 and 2xl 1536 exist as tokens but no rule references them, so they are offered as device widths only.
  • At exactly 640px, Sheet widens (min-width: 640px) while every other component is still narrow (max-width: 640px). Both rules match on the line. Nothing is broken, but that width is not a state any real screen sits in.

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