React components for the customer-facing side of loyalty and gamification programs. Scratch cards, spin wheels, stamp cards, coupons, points counters, streaks, missions, reward catalogs, referrals, tier ladders — the surfaces a member actually touches.
Free and MIT licensed. Not an admin panel, and not a design system: it assumes you have one.
Docs, live demos and the playground → mrcl.st/loyalty-patterns
A source you copy from, not an app you clone and run.
There is no package to install, no dependency to add, no dev server and no build step. The repository contains the components, their framework-free engines, their stylesheets and their documentation — and nothing else. Copy a folder into your project and it is yours to edit.
That is why the demos live on the site rather than here: seeing a scratch card work requires an app around it, and the playground is that app. It runs every component live, lets you tune it, and hands you back the code with your settings applied.
git clone https://github.com/mrcl-st/loyalty-patterns.gitTake two things:
registry/tokens.css— the token defaults. Once, for the project.registry/<component>/— the folder for each component you want.
Import the tokens near your global styles, then the component and its stylesheet:
import "./tokens.css";
import "@/components/scratch-card/styles.css";
import { ScratchCard } from "@/components/scratch-card/scratch-card";Both stylesheets are plain CSS, so any bundler that handles a CSS import will do — Next.js, Vite, Remix and CRA all qualify. React 18 or later is the only runtime requirement.
You do not get updates by bumping a version. What you get instead is a component you can change without fighting a maintainer's abstraction, which for UI this brand-specific is the better trade.
- Outcomes are decided by your server, never in the browser. A scratch card reveals a result; it does not compute one. Prize logic reachable from the client bundle is a compliance incident a curious DevTools user will find.
- Every gesture has a non-gesture equivalent. Scratching and flicking are path-based gestures, and WCAG 2.5.1 (Level A) requires a single-pointer alternative — which serves tremor and dexterity users, not only keyboard ones. Results announce; focus is managed.
- All copy arrives as props. A required
labelsobject per component, screen-reader announcements included. No hardcoded strings anywhere, so the type doubles as an inventory of every state the component has.
Ten, each with its own README in its folder — that same file is the component's documentation page on the site, so what you read is what ships beside the code.
| Component | Mechanic |
|---|---|
| Scratch card | Scratch-to-reveal over a server-decided outcome |
| Spin wheel | Spin-to-reveal, aimed at the segment the server chose |
| Stamp card | The punch card. A record, not an affordance |
| Coupon card | A code to carry to a till, with the clipboard as a fallible dependency |
| Points counter | A balance that moves, announced once at the destination |
| Streak tracker | Consecutive days, where "today" is an input rather than a guess |
| Missions list | Tasks with their own states inside a campaign with its own |
| Reward catalog | What the points are for. Affordability is never computed client-side |
| Referral widget | Share, then copy, then select it yourself — each step the previous one's failure state |
| Tier progress | Where a member stands between named states, without promoting them in pixels |
registry/_template/ is the shape a new component starts from, if you write one
in the same style.
Components read --lk-* CSS custom properties and nothing else. No provider, no
config file, no class-name convention, no second styling system.
:root {
--lk-brand: #4f46e5;
--lk-surface: #ffffff;
--lk-radius: 12px;
}That is the whole integration surface, which is why MUI, shadcn, Base UI and plain CSS all bridge in with about twenty lines of token mapping. Compose a theme in the playground and it exports the block for you.
Every component also emits a portable specification — a prompt you hand to a coding agent, carrying the behaviour contract, the state machine, the accessibility requirements and the compliance rules, each with its reasoning attached so the agent knows which parts it may not trade away. It separates the platform-neutral invariants from the React and canvas notes it should adapt rather than copy, and it ends in acceptance checks phrased as testable statements.
The component is the reference implementation; the specification is what travels to Vue, Svelte, SwiftUI or Flutter. Generate one, with your settings applied, from any component's page in the playground.
MIT. Use it commercially, fork it, ship it. Attribution appreciated and not required.
Issues and pull requests are welcome against the component files. Bear in mind there is nothing to run here — no dev server and no test suite — so a change is reviewed by reading it, and the fastest way to influence what ships next is to say which component you needed and did not find.
Built by Marcel Stuliglowa, a lead product designer working on enterprise loyalty and gamification platforms. The opinions in this kit are not theoretical: they come from shipping these mechanics through compliance review and multi-brand theming, at volumes where a mispriced reward is a budget line rather than a bug.