Componentticket-stub0.74 KB gzipZero dependencies

Passport

Double-frame passport cover with monogram, holder fields and staggered visa stamp marks.

Preview

Live Preview
J
NAME
John Doe
NATIONALITY
US Citizen
C
M
U

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add passport

Or install the full npm package:

npm install bigbullui
import { Passport } from "@/components/ui/passport";

<Passport holderName="John Doe" nationality="US CITIZEN" visaCountries={["Canada", "Mexico"]} />

Usage guidance

Use Passport when a ticket stub surface needs Double-frame passport cover with monogram, holder fields and staggered visa stamp marks. Keep props minimal and prefer composition with sibling stubs.

Avoid Passport for long form flows or dense data grids where a dedicated layout block fits better.

Source code

passport.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface PassportProps {
  holderName?: string;
  nationality?: string;
  visaCountries?: string[];
  className?: string;
}

export function Passport({ holderName, nationality, visaCountries = [], className }: PassportProps) {
  return (
    <div
      className={cn(
        "relative size-96 rounded-lg border border-border bg-card p-6 motion-reduce:transition-none",
        className
      )}
    >
      <div className="relative rounded-2xl border border-border bg-secondary p-2">
        <div className="relative">
          <div
            className="absolute -top-2 -right-2 size-6 rounded-full border border-border/20 flex items-center justify-center text-[8px] font-bold">
            {holderName ? holderName.charAt(0) : "H"}
          </div>
          <div className="absolute inset-1 rounded-lg border border-dashed border-border/40" />
        </div>
      </div>

      <div className="mt-4 grid rows-2 gap-4">
        <div>
          <div className="font-mono text-[9px] uppercase tracking-[0.15em] text-muted-foreground">NAME</div>
          <div className="font-mono text-[11px] font-medium">{holderName || "HOLDER NAME"}</div>
        </div>
        <div>
          <div className="font-mono text-[9px] uppercase tracking-[0.15em] text-muted-foreground">NATIONALITY</div>
          <div className="font-mono text-[11px]">{nationality || "NATIONALITY"}</div>
        </div>
      </div>

      <div className="mt-6 grid grid-cols-3 gap-2">
        {visaCountries.map((country, i) => (
          <VisaStamp key={i} country={country} index={i} />
        ))}
      </div>
    </div>
  );
}

function VisaStamp({ country, index }: { country: string; index: number }) {
  return (
    <div
      className={cn(
        "aspect-square rounded-md border border-dashed border-border/50 flex items-center justify-center text-[7px] font-bold",
        index % 2 === 0 && "animate-stamp"
      )}
    >
      {country.charAt(0).toUpperCase()}
    </div>
  );
}

Props

PropTypeDescription
holderName?string
nationality?string
visaCountries?string[]
className?string