Isaiah

BJORK Cohort retention

Component 26

About

Cohort retention is a production-ready charts component from BJORK UI, packaged for shadcn registries.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds BJORK Cohort retention to your project and fits it to your theme.

Code

How BJORK Cohort retention is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";
import { ThemeProvider as BjorkDemoThemeProvider } from "next-themes";
import { useState } from "react";
import { CohortRetention, type RetentionCohort } from "@/components/ui/bjork-cohort-retention";
import { mulberry32 } from "@/components/ui/bjork-cohort-retention-utils/components-bjork-ui-_core-random";
import { gaussian } from "@/components/ui/bjork-cohort-retention-utils/components-bjork-ui-charts-_kit-scale";
import { DemoColumn } from "@/components/ui/bjork-cohort-retention-utils/app-charts-_demo-controls";

const monthFmt = new Intl.DateTimeFormat("en-US", { month: "short", year: "numeric", timeZone: "UTC" });

const dayFmt = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", timeZone: "UTC" });

interface Curve {
  first: number;
  floor: number;
  tau: number;
}

function makeCohorts(seed: number, cadence: "month" | "week", before: Curve, after: Curve): RetentionCohort[] {
  const rnd = mulberry32(seed);
  const n = 12;
  const change = 6;
  return Array.from({ length: n }, (_, i) => {
    const start = cadence === "month" ? Date.UTC(2025, 9 + i, 1) : Date.UTC(2026, 5, 29) + i * 7 * 86400000;
    const label = cadence === "month" ? monthFmt.format(start) : dayFmt.format(start);
    // Steady growth, plus the launch month pulling in a bigger, slightly less committed cohort.
    const launch = i === change ? 1.38 : 1;
    const base = cadence === "month" ? 1650 : 410;
    const size = Math.round(base * 1.05 ** i * launch * (1 + gaussian(rnd) * 0.05));
    const curve = i >= change ? after : before;
    const values: number[] = [size];
    let prev = 1;
    for (let p = 1; p < n - i; p++) {
      const ideal = curve.floor + (curve.first - curve.floor) * Math.exp(-(p - 1) / curve.tau) - (i === change ? 0.02 : 0);
      const rate = Math.min(prev + 0.004, ideal + gaussian(rnd) * 0.012);
      prev = rate;
      values.push(Math.round(size * Math.max(0, rate)));
    }
    return { id: `${cadence}-${i}`, label, size, values };
  });
}

const DATA = {
  monthly: makeCohorts(41, "month", { first: 0.38, floor: 0.16, tau: 3 }, { first: 0.47, floor: 0.23, tau: 3.6 }),
  weekly: makeCohorts(73, "week", { first: 0.51, floor: 0.21, tau: 2.4 }, { first: 0.6, floor: 0.27, tau: 2.8 }),
};

const PERIODS = {
  monthly: { title: "Months since signup", format: (p: number, style: "short" | "long") => (style === "long" ? `Month ${p}` : `M${p}`) },
  weekly: { title: "Weeks since signup", format: (p: number, style: "short" | "long") => (style === "long" ? `Week ${p}` : `W${p}`) },
};
function SourceDemo(){
const isPreview = false;
const [display, setDisplay] = useState<"percent" | "count">("percent");
const [cadence, setCadence] = useState<"monthly" | "weekly">("monthly");
function reset() {
    setDisplay("percent");
    setCadence("monthly");
  }
const per = PERIODS[cadence];
return <>

      {isPreview ? (
        <div className="w-[700px]">
          <CohortRetention
            cohorts={DATA.monthly}
            periodTitle={PERIODS.monthly.title}
            formatPeriod={PERIODS.monthly.format}
            activeCell={[8, 3]}
            rowHeight={26}
            ariaLabel="Monthly signup cohort retention"
          />
        </div>
      ) : (
        <DemoColumn width={780}>
          <CohortRetention
            cohorts={DATA[cadence]}
            periodTitle={per.title}
            formatPeriod={per.format}
            display={display}
            ariaLabel={cadence === "monthly" ? "Monthly signup cohort retention" : "Weekly signup cohort retention"}
          />
        </DemoColumn>
      )}
    
</>;
}
export default function BjorkRegistryPreview(){return <BjorkDemoThemeProvider attribute="class" defaultTheme="dark" enableSystem={false} storageKey="bjork-review-demo-theme"><div data-component-demo className="dark flex min-h-screen w-full overflow-x-hidden items-center justify-center bg-[var(--bjork-bg)] p-8 text-[var(--bjork-text)]"><div className="w-full max-w-[850px]"><SourceDemo/></div></div></BjorkDemoThemeProvider>}

Dependencies

More by Isaiah

View all
  • Agent Plan

    Agent Plan

    2.2K
  • Video Scroll Hero

    Video Scroll Hero

    1.4K
  • Voice Powered Orb

    Voice Powered Orb

    1.3K
  • Reveal Text

    Reveal Text

    1.1K
  • Leads Data Table

    Leads Data Table

    933
  • Portfolio Gallery

    Portfolio Gallery

    919
  • Message Dock

    Message Dock

    832
  • Ruler Carousel

    Ruler Carousel

    617