# CardNetworkMark

> Visa, Mastercard, Amex, Discover, JCB, UnionPay, Diners. One optical size, a text fallback.

Source: https://lopifi.com/components/card-network-mark
Part of: Lopifi — US GAAP books on autopilot, checked by a CPA (https://lopifi.com)

---

- **Family:** Primitives — The layer everything else composes. Build these first; nothing above them is right until they are.
- **Status:** Implemented
- **Spec:** https://lopifi.com/docs/catalog/01-primitives

## Install

```bash
npx shadcn@latest add @lopifi/theme
npx shadcn@latest add @lopifi/card-network-mark
```

## Specification

> The network, at a glance, without a request or a broken image.

```tsx
<CardNetworkMark network="amex" />
<CardNetworkMark network="visa" size={20} variant="mono" />
<CardNetworkMark network="Interac" />           {/* not drawn: prints the name */}

detectCardNetwork("3782 82")                    // "amex"
detectCardNetwork("3")                          // null: Amex, Diners Club or JCB
```

```ts
interface CardNetworkMarkProps extends Omit<React.ComponentPropsWithoutRef<"svg">, "children"> {
  network: "visa" | "mastercard" | "amex" | "discover" | "jcb" | "unionpay" | "diners" | (string & {});
  /** Height in px; the width follows at 5:3. Default 24. */
  size?: number;
  variant?: "color" | "mono";
  /** The accessible name. Defaults to the network's: "American Express". */
  label?: string;
}
```

**One artboard.** All seven are drawn on the same 40 × 24 box, so a row of them shares a height,
a baseline and an optical weight. A logo dropped in at its own aspect ratio makes Mastercard's
circles look large beside Visa's letters.

**Schematic, not artwork.** Each mark is a few shapes and at most one word: Mastercard's
overlapping circles, the Visa letters on a navy field, the Amex box, Discover's disc in place of
the O, the JCB bands, the UnionPay bands, the Diners Club ring. They're recognizable at 16px and
make no attempt to reproduce the network's artwork. A product licensed to show the official mark
passes it where a component takes one (`PaymentMethodCard`'s `networkLogo`, `CardInput`'s
`networkMarks`).

**`mono`** drops every field and draws the rest in `currentColor`, so the mark takes the ink of
what it sits on: a card face, a muted row, a dark surface.

**A text fallback, never a wrong mark.** A network it doesn't draw prints its name in a pill of
the same height. A blank space or a generic card icon would leave the reader guessing which card
it is.

**Detection** (`detectCardNetwork` in `lib/card-network.ts`) reads the issuer identification
number against the networks' published ranges: Visa 4 · Mastercard 51–55, 2221–2720 · Amex 34,
37 · Discover 6011, 644–649, 65 · JCB 3528–3589 · UnionPay 62 · Diners Club 300–305, 3095, 36,
38–39. A prefix only claims a network when every number it could still become falls inside that
network's range. "3" stays `null` until the next digit settles it, and "27" stays `null` until
the fourth, because only 2700–2720 of the 27xx block is Mastercard. The same module carries the
lengths (Amex 15, Diners Club 14, the rest 16), the security-code length (Amex 4, the rest 3) and
the printed grouping (4-6-5, 4-6-4, fours).

**Accessibility:** `role="img"` with the network's name. Pass `aria-hidden` where the name is
already in the surrounding text, such as a field that says "Visa card".

**Motion:** none. A mark is identity, not feedback.

## Rules this component obeys

Money is minor units, never floats. `null` is not `0` and neither is redacted. Negatives render in accounting parentheses with the parenthesis column reserved on both sides. Debit and credit are two columns. Per-currency decimal places. Polarity is explicit — up is not always good. Every figure states its period, entity, currency, basis and as-of.
