Skip to main content

Components

Seven core components, all theme-driven and accessible. Each composes one or more hooks from Hooks.

<MissionList>

Fetches a page of missions and renders one <MissionCard> per row. Owns the useMissions subscription so SSE-fed progress updates flow through a single source of truth.

<MissionList limit={10} status="active" onClaim={(id) => client.claimMission(id)} />
PropTypeDefaultDescription
campaignIdstringFilter to a single campaign.
limitnumber50Initial page size; "Load more" expands by this amount.
status"active" | "completed" | "claimed" | "locked" | "all"Filter by progress status.
onClaim(missionId: string) => Promise<void>Forwarded to each card. Optional.
classNamestringAppended to the root container.

States: loading (skeletons), error (alert + retry), empty (neutral copy), loaded (card stack).


<MissionCard>

Single-mission tile with progress, reward badge, and claim CTA. Presentational — does not subscribe to its own data; parent passes mission + progress directly.

<MissionCard mission={mission} progress={progress} onClaim={onClaim} />
PropTypeDefaultDescription
missionMissionRequired. The mission to render.
progressMissionProgress | undefinedOptional — absent means "no row on the server yet".
onClaim(missionId: string) => Promise<void>Triggered when the user clicks "Claim".
classNamestringAppended to the root.

The claim button state machine: hidden (locked/active) → visible+enabled (completed) → disabled "Claiming…" (in flight) → disabled "Claimed" (status=claimed).

tip

The card fires a qk.claim.attempt event when the button is clicked so you can build an attempted-vs-completed funnel. The actual claim mutation belongs to the parent onClaim handler.


<CoinBalance>

Displays the user's balance for one currency. Optional rolling animation when the value changes.

<CoinBalance currency="gold" animated />
PropTypeDefaultDescription
currencystringRequired. E.g. "gold", "point", "gem".
animatedbooleanfalseRolls the displayed number on change (300 ms ease-out cubic).
classNamestringAppended to the root.

Respects prefers-reduced-motion; the rolling animation snaps to the final value when reduced-motion is set.


<CampaignBanner>

Hero strip with optional banner image and a live countdown to endAt.

<CampaignBanner campaignId="spring-2026" />
PropTypeDescription
campaignIdstringRequired.
classNamestringAppended to the root.

Falls back to a gradient (primary → coin) when bannerUrl is unset. Countdown ticks once per second; uses aria-live="off" so screen readers aren't spammed.


<ProgressBar>

Accessible horizontal progress meter. Standalone — not coupled to any hook, so you can use it for non-QuestKit progress UIs too.

<ProgressBar value={3} max={10} label="Progress: 3 of 10" />
PropTypeDefaultDescription
valuenumberRequired. Clamped to [0, max].
maxnumberRequired. Clamped to a minimum of 1.
labelstringSets aria-label for screen readers.
classNamestringAppended to the container.

Renders with role="progressbar" + aria-valuenow / aria-valuemin / aria-valuemax.


<RewardClaimToastHost>

Portal-based reward toast layer. Mount once near the root of your app, inside the provider.

import { RewardClaimToastHost, useRewardClaimToast } from "@questkit/react";

function App() {
return (
<QuestKitProvider config={...}>
<RewardClaimToastHost durationMs={4000} />
<Routes />
</QuestKitProvider>
);
}

function ClaimButton({ missionId }) {
const { show } = useRewardClaimToast();
// ... call show({ kind: "currency", currency: "gold", amount: 50 }) on claim
}
PropTypeDefaultDescription
durationMsnumber4000How long each toast stays before fading.

The host portals into document.body so toasts always layer above your normal tree. SSR-safe — the portal target is resolved on the client mount tick.

useRewardClaimToast() returns { show: (reward: Reward) => void }. The emitter is module-scoped, so show() works from anywhere — no need for a Context provider.


<RecommendedMissions>

AI-curated mission suggestions. Composes useRecommendations() + per-ID useMission() to render up to three <MissionCard>s with the AI's reason as a caption.

<RecommendedMissions onClaim={onClaim} />
PropTypeDescription
onClaim(missionId: string) => Promise<void>Forwarded to each card.
classNamestringAppended to the root.

Shows a "Refreshes hourly" hint when cached: true. Falls back to an empty-state message when the AI has no suggestions yet.

When the server indicates fallback: true (AI binding outage or malformed response), the widget renders a tasteful empty-state ("AI picks unavailable right now") with no error code visible — the rest of the UI is unaffected.

note

The mini-game widgets (<SpinWheel>, <ScratchCard>) live on their own page — see Mini-Games.