Skip to content

SignatureStack

Hero example

When to use

Reach for SignatureStack when a multi-document signing package needs visibility before send — typically a financing close (subscription + SAFE + side letter + IP-assignment), an employee package (offer + at-will agreement + IP assignment + option grant), or any DocuSign-style envelope. The card carries enough metadata (signer count, pages, size, recipient list) for the user to verify the package contents and recipients before the send-for-signature action triggers the envelope.

Don't reach for SignatureStack for a single-document signing flow — for one document use BoardConsentCard (for resolutions) or a dedicated document-detail page. Don't use it once the package is sent either; post-send state belongs in a dedicated tracking page with per-recipient progress, not the staging card.

Do — render SignatureStack as an agent reply when staging a financing close package: "Here's the SAFE close envelope — 3 documents, 7 signers."
Don't — render SignatureStack with documents from different transactions. The envelope is the unit; cross-transaction docs need their own envelopes.

Anatomy

Four structural regions stacked top-to-bottom:

  1. Header block. Eyebrow ("Documents · Awaiting your review") + package name + right-aligned status Pill (default Draft / amber). Separated from the body by a 1px --ink-6 rule.
  2. Document list (<ul>). One row per SignatureDoc. Each row: 28×36 document thumb (background --ink-4), title, meta line ("{signers} signers · {pages} pages · {size}") with Bullet separators.
  3. Footer band. 6/20/14 padding wrapper hosting:
    • Recipient stack. Overlapping 24px circular avatars (initials only, 2px ring in --paper-50), 8px negative margin on overlap, {N} recipients label.
    • Footer actions. Primary Send for signature + secondary Edit.

The footer composes CardsKit.CardFooter with left={<RecipientStack>} slot — the standard pattern when a footer needs context plus action.

Props

PropTypeDefaultDescription
packageNamerequiredstring
docsrequiredSignatureDoc[]
recipients{ initials: string; accent?: string }[]DEFAULT_RECIPIENTS
onSend(receipt: { id: string; ts: number }) => void

recipients defaults to a three-recipient demo set (JD, ML, SP) so the design-site preview renders without consumer wiring. In production, pass the real signer roster derived from the envelope.

onSend receives a { id, ts } receipt when the primary action completes — typically a Webhook.signature_request.sent callback. The CK footer handles loading state and disables the primary while in flight.

States

The card has three semantic states driven by the package lifecycle:

StateTriggerStatus pill
Draft (default)Envelope not yet sentDraft (amber)
SendingonSend mid-flightCK footer disables primary; pill stays Draft
SentonSend completesCard should be replaced by a tracking surface — don't keep SignatureStack mounted

Density

In compact, document-row padding drops from 12/20 to 8/14. Recipient avatars stay 24px. The eyebrow size and letter-spacing stay constant — legibility floor at this letter-spacing matters more than vertical rhythm.

Themes

In dark, the document thumb uses --ink-4 (which adapts), and the avatar border ring (originally --paper-50) reads from the dark surface token automatically.

Tokens consumed

No tokens match.
colors2 tokensv2.3.0
#F5F3EE
matter
src ↗
#f5f3ee
brand
src ↗
colors17 tokensv2.3.0
#3FBF6E
matter
src ↗
#1b7a47
matter
src ↗
#E8A845
matter
src ↗
#8a5e1c
matter
src ↗
#8C8C8C
matter
src ↗
#5A5A5A
matter
src ↗
#6E8AD8
matter
src ↗
#3a5cb8
matter
src ↗
#1f8a5b
brand
src ↗
rgba(31, 138, 91, 0.1)
brand
src ↗
rgba(31, 138, 91, 0.35)
brand
src ↗
#6b7280
brand
src ↗
rgba(107, 114, 128, 0.1)
brand
src ↗
rgba(107, 114, 128, 0.3)
brand
src ↗
#b0322b
brand
src ↗
rgba(176, 50, 43, 0.1)
brand
src ↗
rgba(176, 50, 43, 0.35)
brand
src ↗

Document thumb reads --ink-4. List background reads --paper-50. Document row border reads --ink-6. Avatar ring reads --paper-50. Status pill reads --status-amber-bg / --status-amber-text.

Accessibility

  • Keyboard interactions. Tab order: Send for signatureEdit. Enter / Space activate. Document rows and recipient avatars are not focusable — actions live in the footer.
  • ARIA roles and properties. Document thumbs and bullets are aria-hidden (decorative). Recipient avatars are aria-hidden; the recipient-count text carries the accessible identity. The status pill has no role override — accessible name is its text content.
  • Focus order. Footer only.
  • Screen-reader expectations. Reading order: "Documents · Awaiting your review", package name, status label, then each document as "title, {signers} signers, {pages} pages, {size}", then "{N} recipients", then footer buttons.
  • Reduced motion. No keyframes in the card. Footer hover lift collapses to opacity under reduced motion.
  • Forced colors. Document thumb → Canvas with ButtonBorder. Avatars → Highlight. Bullets disappear (system-color rendering removes the dot, which is fine — the surrounding text supplies the separation).
  • WIG rules. Decorative thumbs and bullets carry aria-hidden (decorative-icon rule). Non-breaking space in measurements like "28 pages" via &nbsp; (WIG numeric rule). Touch targets on footer buttons exceed 44×44px.

Do / Don't

Do — pass realistic size strings ("1.2 MB", "412 KB") with proper non-breaking spaces between number and unit.
Don't — pass file paths or URLs in title. The title is the document name as the signer will see it.
Do — derive recipients initials from the actual signer roster, in DocuSign-style envelope order.
Don't — render the card with the default DEFAULT_RECIPIENTS placeholder in production. That set is for the design-site preview only.
Do — keep docs.length between 2 and 8 — that's the sweet spot for a multi-document envelope.
Don't — render the card for a single-document package. Use the document detail page directly.

Recipes

This card appears in:

Code example

import { SignatureStack } from "@matter/components";

export function FinancingClosePackageInline({ envelope }: { envelope: SigningEnvelope }) {
  return (
    <SignatureStack
      packageName={envelope.package_name}
      docs={envelope.documents.map((d) => ({
        title: d.title,
        signers: d.signer_count,
        pages: d.page_count,
        size: d.size_human,
      }))}
      recipients={envelope.recipients.map((r) => ({
        initials: r.initials,
        accent: r.brand_color,
      }))}
      onSend={(receipt) => recordEnvelopeSent(envelope.id, receipt)}
    />
  );
}

Source

packages/components/src/InlineCard/SignatureStack

On this page