Skip to content
react-wrapped

Share card

renderShareCard draws the whole year on one card and gives it back as a PNG. It is painted on a canvas, so the file is the same on every machine: 1200 by 630, saved at twice that. Use it for the picture people post, pin to a profile or drop into a README.

Drawing the card.

Installation

bash
pnpm add @react-wrapped/graphics @react-wrapped/core gsap

Usage

Call it with a spec and a palette. It needs a browser, so call it from an effect or a click. The component below draws the card once per palette and shows it with a download link.

share-card.tsx
import { useEffect, useState } from "react";

import { resolvePalette, type WrappedConfig } from "@react-wrapped/core";
import { renderShareCard, type ShareCardSpec } from "@react-wrapped/graphics";

export function ShareCard({
  spec,
  config,
  fileName,
}: {
  spec: ShareCardSpec;
  config: WrappedConfig;
  fileName: string;
}) {
  const [url, setUrl] = useState<string | null>(null);
  const [failed, setFailed] = useState(false);
  const { palette, accent, mode } = config;

  // Drawn on a canvas, so only in the browser, and again whenever the palette changes.
  useEffect(() => {
    let made: string | null = null;
    let gone = false;
    const draw = async () => {
      try {
        const png = await renderShareCard(spec, resolvePalette({ palette, accent, mode }));
        if (gone) return;
        made = URL.createObjectURL(png);
        setUrl(made);
      } catch {
        setFailed(true);
      }
    };
    void draw();
    return () => {
      gone = true;
      if (made) URL.revokeObjectURL(made);
    };
  }, [spec, palette, accent, mode]);

  if (failed) return <p>The card could not be drawn in this browser.</p>;
  if (!url) return <p>Drawing the card.</p>;
  return (
    <div>
      <img src={url} alt={`@${spec.handle}, ${spec.caption}`} width={1200} height={630} />
      <div role="group" aria-label="Share card">
        <a href={url} download={fileName}>
          Download the PNG
        </a>
      </div>
    </div>
  );
}

The spec

KeyTypeWhat it is
handlestringWritten as @handle next to the avatar.
avatarUrlstring | nullThe picture. With null, or when it does not load, the handle's first two letters on a disc.
captionstringWhat this Wrapped is called. Set under the handle, before the period.
periodstringThe window the numbers cover, already formatted.
graph?{ levels: readonly number[]; lead: number }The year as cells, the same shape as a scene's calendar. Leave it out and the numbers move up.
statsreadonly { label: string; value: string }[]The headline numbers, already formatted. Up to four share the row.
tokens?readonly WrappedToken[]What this person got, as tinted plates at the bottom. The first two are drawn.
brand?stringA quiet line in the top right: a site address or a product name.
ts
import type { ShareCardSpec } from "@react-wrapped/graphics";
import { PIXEL_ART } from "@react-wrapped/core";

export const card: ShareCardSpec = {
  handle: "ada",
  avatarUrl: null,
  caption: "Orbit Wrapped",
  period: "Oct 2025 to Oct 2026",
  stats: [
    { label: "Contributions", value: "2,052" },
    { label: "Longest streak", value: "48 days" },
    { label: "Biggest day", value: "58" },
    { label: "Top language", value: "TypeScript" },
  ],
  tokens: [{ name: "The Marathoner", kind: "Coder type", rarity: "epic", art: PIXEL_ART.mountain }],
  brand: "orbit.example",
};

What it draws

  • The palette's background with one soft light in the top left corner, as on the video.
  • The avatar or the initials, the handle, and the caption with the period after it.
  • The year as cells, when you pass graph.
  • The numbers in one row. A value too long for its column is set smaller until it fits.
  • Up to two tokens at the bottom, each tinted in its rarity colour with its pixel art.

As a scene

The same content closes the video as the summary scene. In the gallery the card is one tile: hand it image: () => renderShareCard(card, palette).

Last updated . Read this page as Markdown.