Contribution Graph
iconiq-ui/contribution-graphFreeComponent
GitHub-style contribution calendar with compound calendar, block, total-count, and legend parts — pass raw Activity data or just a GitHub username and it fetches and caches the last year of contributions itself, shimmers a skeleton grid while loading, then fades the muted grid in and lights the greens up level by level — lightest to darkest — with springy pops, plus custom per-day tooltips and reduced-motion aware behavior.
Live preview
live · rendered by the registry
Rendered by Iconiq UI on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://www.iconiqui.com/r/contribution-graph.jsonSource
1"use client";23import type { Day as WeekDay } from "date-fns";4import {5 differenceInCalendarDays,6 eachDayOfInterval,7 formatISO,8 getDay,9 getMonth,10 getYear,11 nextDay,12 parseISO,13 subDays,14 subWeeks,15} from "date-fns";16import {17 type CSSProperties,18 createContext,19 type FocusEvent,20 Fragment,21 type HTMLAttributes,22 type MouseEvent,23 type ReactNode,24 useContext,25 useEffect,26 useMemo,27 useRef,28 useState,29} from "react";30import { createPortal } from "react-dom";31import { cn } from "@/lib/utils";3233export type Activity = {34 date: string;35 count: number;36 level: number;37};3839type Week = Array<Activity | undefined>;4041export type Labels = {42 months?: string[];43 weekdays?: string[];44 totalCount?: string;45 legend?: {46 less?: string;47 more?: string;48 };49};5051type MonthLabel = {52 weekIndex: number;53 label: string;54};5556const DEFAULT_MONTH_LABELS = [57 "Jan",58 "Feb",59 "Mar",60 "Apr",61 "May",62 "Jun",63 "Jul",64 "Aug",65 "Sep",66 "Oct",67 "Nov",68 "Dec",69];7071const DEFAULT_LABELS: Labels = {72 months: DEFAULT_MONTH_LABELS,73 weekdays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],74 totalCount: "{{count}} activities in {{year}}",75 legend: {76 less: "Less",77 more: "More",78 },79};8081/** Rolling-window label used when data comes from a GitHub username. */82const ROLLING_TOTAL_LABEL = "{{count}} contributions in the last year";8384/** GitHub-style green level ramp, shared by blocks and the legend. */85const LEVEL_CLASSES = cn(86 'data-[level="0"]:fill-muted',87 'data-[level="1"]:fill-emerald-200 dark:data-[level="1"]:fill-emerald-900',88 'data-[level="2"]:fill-emerald-400 dark:data-[level="2"]:fill-emerald-700',89 'data-[level="3"]:fill-emerald-600 dark:data-[level="3"]:fill-emerald-500',90 'data-[level="4"]:fill-emerald-800 dark:data-[level="4"]:fill-emerald-300'91);9293/** Entrance timing: the muted grid fades in as a canvas, then colored blocks94 * light up in rounds by activity level — lightest greens first, darkest95 * last — so the year reads as charging up. */96const LEVEL_REVEAL_BASE = 220;97const LEVEL_REVEAL_STEP = 170;98const LEVEL_REVEAL_JITTER = 110;99100/** Small deterministic per-block offset so each level's round shimmers in101 * instead of snapping on as one frame. */102const revealJitter = (weekIndex: number, dayIndex: number) =>103 Math.round(104 ((((weekIndex * 7 + dayIndex) * 137) % 97) / 97) * LEVEL_REVEAL_JITTER105 );106107const ENTRANCE_KEYFRAMES = `@keyframes iconiq-cg-fade {108 from { opacity: 0; }109// … truncated
What it pulls in
npm packages
Files it writes
More from Iconiq UI
All 119 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | Iconiq UI | Components | Free | 2 deps | |
| AI Inputai-input | Iconiq UI | Components | Free | 3 deps | |
| Alertalert | Iconiq UI | Components | Free | 2 deps | |
| Avataravatar | Iconiq UI | Components | Free | 3 deps | |
| Accordion (Base UI)b-accordion | Iconiq UI | Components | Free | 2 deps | |
| Alert Dialog (Base UI)b-alert-dialog | Iconiq UI | Components | Free | 3 deps | |
| Autocomplete (Base UI)b-autocomplete | Iconiq UI | Components | Free | 3 deps | |
| Avatar (Base UI)b-avatar | Iconiq UI | Components | Free | 2 deps |
