Loyalty and gamification components for React.
An open-source component library for the surfaces a loyalty member actually touches. You copy the folders you need and own the code outright, so there is no abstraction to fight and no version to bump. Outcomes stay on your server, every gesture has an accessible equivalent, and the whole set themes through CSS custom properties, so it drops into the design system you already have.
Summer rewards
Scratch & win
Reward mechanics a compliance review survives
The card above is wired to a mock server that takes network-shaped time to answer. Drag across it. What you cannot see from the outside is the part that matters: nothing in the browser knows the result until the server says so, and that holds for every chance mechanic in the set.
Wiring it up is three props. Your endpoint decides the outcome, you render the reward, you supply the copy.
<ScratchCard
getOutcome={() => fetch("/api/draw", { method: "POST" }).then(r => r.json())}
renderOutcome={(o) =>
o.kind === "win" ? <Prize reward={o.reward} /> : <NoWin />
}
labels={labels}
/>Not on React? Have an AI build it
Copy-in distribution has a hard edge: a folder of .tsx is worth nothing to a Vue, Svelte, SwiftUI or React Native team. So every component also emits a prompt — a complete written specification you hand to Claude, Cursor, Copilot or whatever you already use, which then builds the component in your stack.
It is not a description of the component. It carries the behaviour contract, the state machine, the accessibility requirements and the compliance rules, each with the reasoning attached so an agent knows which parts it may not trade away — and it separates the framework-independent invariants from the React notes it should adapt rather than copy. It ends in acceptance checks the agent is told to run before claiming it works.
The component is the reference implementation; the specification is what travels. Both are generated from whatever you set in the playground.
Build a scratch-card component in MY stack, to this specification. Do not port React code — implement these invariants with my platform's own idioms. ## The reference implementation A working React implementation of this component exists, is documented, and is running live. Use it to resolve anything ambiguous below and to check your behaviour against — not as code to translate line by line: - Source: `registry/scratch-card/` in https://github.com/mrcl-st/loyalty-patterns - Documented behaviour, props, tokens and states: https://mrcl.st/loyalty-patterns/docs/components/scratch-card - Running, with exactly the settings in this prompt applied: https://mrcl.st/loyalty-patterns/playground/scratch-card Where the reference and this specification disagree, the specification wins. It is the contract; the reference is one platform's expression of it. ## Invariants (must all hold, whatever the platform) Behaviour: - Reveal when 65% of the panel has been erased. Below that the card stays covered. - The prize is visible through the holes from the first stroke, like a physical card. - Brush diameter is 14% of the panel's shorter side, round. - The user scratches with pointer or touch. - A win reveals with a brief pop and a spark burst in the reward accent; a loss settles quietly. Both are suppressed when reduced motion is requested.
Questions
What is this?
An open-source set of React components for the customer-facing side of loyalty and gamification programs: scratch cards, spin wheels, points counters, stamp cards, tier progress and referral widgets. It covers the surfaces a member touches, not the admin tooling behind them.
Is it a design system?
No, and it assumes you already have one. The components carry no visual opinion of their own beyond sensible defaults, and they read only CSS custom properties, so they inherit your colours, radii, typography and spacing rather than competing with them.
What can I not change?
Three things, and they are the point rather than a limitation: outcomes are decided by your server, every gesture has an equivalent that needs no gesture, and all copy arrives through props. Everything else is yours — colour, typography, radius, motion, the reward's presentation, the failure text, the markup around the panel. Those three are structural because each is a way to ship a real problem quietly: a client-side draw is a compliance incident, a gesture-only reward is one nobody with a tremor can claim, and a hardcoded string is a component that cannot be translated.
How are outcomes decided?
By your server, always. You pass either an outcome that was already decided or a getOutcome function the component calls once when the member starts interacting, so the network round-trip hides inside the gesture. No probability logic ships to the client, because a reward mechanic running on a client-side random number is a compliance problem waiting for someone to open DevTools. A failed request is a first-class state with a retry rather than a card stuck waiting.
Is it accessible?
Accessibility is structural rather than optional. Scratching, spinning and dragging are path-based gestures, and WCAG 2.5.1 Level A requires a single-pointer equivalent, so every gesture-driven component ships one and will not let you remove it — which serves people with tremor or limited dexterity as much as keyboard and screen-reader users. Results announce through a live region using your own copy, prize content stays hidden from screen readers until it is revealed, focus moves to the result, and all animation collapses under prefers-reduced-motion rather than merely shortening.
How do I theme it to my brand?
Set CSS custom properties, which is the only styling mechanism the components read. Roughly thirty semantic tokens cover the whole kit, and each component exposes its own tokens that fall back to those. MUI, shadcn, Chakra, Base UI and plain CSS all bridge in with a mapping file of about twenty lines pointing --lk-* names at the tokens you already have, so your design system stays the source of truth — and a multi-brand campaign becomes a token swap rather than a branch per brand.
Which frameworks are supported?
The components are React. The theming layer is framework-agnostic CSS, and each component keeps its logic in a plain TypeScript engine with no framework dependency, so a Vue, Svelte or web-component wrapper would be a small shell rather than a rewrite. For anything further afield, every component also emits a portable specification you can hand to a coding agent in your own stack.
What is the portable specification?
A prompt carrying the component's behaviour contract, state machine, accessibility requirements and compliance rules, with the reasoning attached to each one, and the framework-independent invariants separated from the React-specific notes you should adapt rather than copy. Copy-in distribution only helps React developers; the specification is the part that travels to SwiftUI, Flutter or anything else.
How do I install it?
There is no package. Clone the repository and copy the token file plus the component folders you want into your project, which makes them yours to edit. React 18 or later is the only runtime dependency, and the stylesheets are plain CSS, so no build plugin or CSS-in-JS runtime is required.
What does it cost, and what is the licence?
Nothing, under the MIT licence. You can use it commercially, modify it and ship it without attribution requirements beyond the licence text.
Which components are available?
The docs list every component that exists, and the playground runs each one live with its behaviour settings exposed. Both are generated from the registry, so they are always current. A component is added when it meets the standard the others are held to, rather than to fill out a roadmap.

Built by Marcel Stuliglowa, a lead product designer working on enterprise loyalty and gamification platforms.
Take a component
Copy a folder and edit it, or copy the specification and have it built in your own stack. Both start in the same place.