Xzero UI
Pro library

Referral cardPro

A give-and-get referral card where people copy or share their invite link, invite friends by email as chips, and watch invites move from invited to signed up to earned, with the credit total counting up and a milestone reward unlocking.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/referral-card
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/referral-card

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { ReferralCard } from "@/components/xzero/referral-card/referral-card";

export function Referrals({ me, referrals }) {
  return (
    <ReferralCard
      link={`https://northwind.app/r/${me.code}`}
      reward={20}
      referrals={referrals}
      milestone={{ goal: 5, reward: "3 months free", detail: "Applied to your next invoice" }}
      onShare={(channel, url) => window.open(url, "_blank", "noopener,width=600,height=640")}
      onInvite={(emails, { signal }) =>
        fetch("/api/referrals/invite", { method: "POST", body: JSON.stringify({ emails }), signal }).then(response => {
          if (!response.ok) throw new Error("Could not send the invites. Nothing was sent.");
        })}
      onMilestone={() => track("referral_milestone_unlocked")}
    />
  );
}

API reference

ReferralCard

One referral program card. The ask on top (Give $20, get $20), the invite link in a quiet field with a Copy button, X, LinkedIn and email share buttons, and an invite by email field that turns each address into a chip. Below, the funnel as three rolling numbers (invited, signed up, earned), a segmented milestone bar with the reward it unlocks, and the list of people invited with their avatar and stage (Invited, Signed up, Earned $20) as an icon plus a word. Stage changes crossfade in place, the earned total counts up, and reaching the goal turns the bar and its line into the unlocked state.

PropTypeDefaultDescription
linkstring"https://northwind.app/r/emma20"The person's invite link. The domain shows muted and the code in full color; Copy copies the full URL.
rewardnumber20Credit each side gets, in whole currency units. Drives the heading, the Earned total and the Earned label on rows.
currencystring"USD"ISO currency code used to format the reward and the total.
titlestring"Give $20, get $20"Heading. Built from reward and currency when unset.
descriptionstring–One or two lines under the heading. Defaults to what both sides get.
referralsReferral[]sample programEveryone invited, newest first: { id, name?, email, avatar?, stage: "invited" | "signed-up" | "earned", invitedAt (ISO) }. Counts, the milestone and the list are derived from it. When the array changes the card follows it. Leave unset for the simulated demo.
milestoneReferralMilestone{ goal: 5, reward: "3 months free", detail: "Applied to your next invoice" }Paid referrals needed (goal, up to 12 segments are drawn), the reward in a few words, and an optional line shown once unlocked.
shareMessagestring–Text that goes with the link in the X post, LinkedIn share and email body.
maxInvitesnumber10Most addresses one send can hold. Extra addresses are refused with a note.
simulatebooleantrue without referralsPlays the sample program once on screen (a friend subscribes, another signs up and subscribes, the milestone unlocks) and moves the first address of each simulated send through the stages.
onCopy(link: string) => void–Called after the link is copied.
onShare(channel: "x" | "linkedin" | "email", url: string) => void–Called with the channel and a ready share URL (X post intent, LinkedIn share-offsite, or mailto). Open it with window.open. Without it, sharing is simulated and the live region says so.
onInvite(emails: string[], options: { signal: AbortSignal }) => void | Promise<unknown>–Sends the invites. Return a promise to keep Send loading; a rejection keeps the chips and shows the Error's message. On success the addresses join the top of the list as Invited. Without it, sending is simulated.
onMilestone(milestone: ReferralMilestone) => void–Called once when paid referrals reach the goal.
classNamestring–Class on the root element.

referralShareUrl

Helper export that builds the share URL for a channel, the same one onShare receives.

PropTypeDefaultDescription
channel"x" | "linkedin" | "email"–Share target.
linkstring–Invite link.
messagestring–Text for the post or email body.

Keyboard interactions

KeysAction
TabMoves through Copy, the three share buttons, the email field and Send.
Enter, comma, space or TabIn the email field, turns the typed address into a chip.
EnterIn an empty email field, sends the chips.
BackspaceIn an empty email field, removes the last chip. Each chip also has its own remove button.
Cmd/Ctrl + VPasting addresses separated by commas, spaces or new lines adds them all.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in ai

Guidance for AI tools

Referral card: A give-and-get referral card where people copy or share their invite link, invite friends by email as chips, and watch invites move from invited to signed up to earned, with the credit total counting up and a milestone reward unlocking. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components