API reference
Everything below is exported from both @wherdkit/react and @wherdkit/react-native
(and from @wherdkit/core for the hooks/types).
<WherdProvider>
| Prop | Type | Notes |
|---|---|---|
publicKey | string | Required. Your pk_… key. |
user | WherdUser | Optional identity. { id, email?, name?, userHash?, traits? } |
theme | ThemeTokens | Optional theme override (see Customization). |
activation | { fab?, shake? } | RN only. Override server activation config. |
shakeThreshold | number | RN only. Acceleration (g) required to register a shake. Higher = stronger shake needed. Defaults to 1.8. |
autoShow | { announcements?, changelog?, messages? } | Override auto-show (defaults: announcements on, changelog & messages off). messages auto-opens the panel to a thread when a new unread admin reply arrives — checked on launch and (RN) on app foreground. |
Components
| Component | Description |
|---|---|
<MessagesWidget fab? collectEmail?> | Floating launcher + sliding panel with tabs for enabled modules. On React Native the panel renders as a native bottom-sheet when @lodev09/react-native-true-sheet is installed (correct safe-area + keyboard handling), falling back to a <Modal> otherwise. fab={{ placement?, offset?: { bottom?, side? }, label? }} positions the launcher. collectEmail forces the composer's email field on/off; by default it's hidden when the user is already identified (you passed user / called identify) and shown otherwise. |
<MessagesFAB label? placement? offset? force?> | Just the launcher. Icon circle by default; label → pill. placement = "bottom-right" | "bottom-left", offset = { bottom?, side? }. force renders it regardless of the server activation.fab toggle — an escape hatch for placing the FAB on a specific screen (e.g. Settings) while keeping it off elsewhere. |
<FeatureBoard /> | Feature requests list + create + upvote. |
<WhatsNew /> | Changelog feed; marks entries read on view. |
Hooks
Every prebuilt component is built on these hooks, so building a fully custom UI
gives you exact parity. All of them must be called inside <WherdProvider>.
useWherd()
The primary hook — reactive widget state plus imperative controls. Use it to open the widget from your own button, read live config, or identify a user.
const wherd = useWherd();
wherd.open("ideas"); // open straight to a tab
Returns:
| Field | Type | Description |
|---|---|---|
ready | boolean | True once the SDK has loaded config + identity. |
isOpen | boolean | Whether the widget panel is open. |
view | WherdView | Active tab: "messages" | "featureRequests" | "changelog". |
config | WidgetConfig | null | Live server-driven config (theme, enabled modules, copy, activation). |
user | WherdUser | null | The identified user, if any. |
verified | boolean | True when the user's identity was HMAC-verified. |
changelogUnread | number | Unread changelog count (for badges). |
messagesUnread | number | Unread admin replies across the user's threads. |
open(view?) | (v?: WherdView) => void | Open the widget, optionally to a tab. |
close() | () => void | Close the widget. |
setView(view) | (v: WherdView) => void | Switch tab without opening/closing. |
identify(user) | (u: WherdUser) => Promise<void> | Attach identity + traits at runtime. |
start({ body, metadata? }) | => Promise<SubmitResult> | Start a new message thread imperatively. |
useComposer()
Drop-in state for starting a new message thread — wire it to an input + button.
const { body, setBody, submit, submitting, submitted, error } = useComposer();
| Field | Type | Description |
|---|---|---|
body | string | Current draft text. |
setBody(v) | (v: string) => void | Update the draft. |
type / setType(t) | MessageType | Category chip: "idea" | "issue" | "other". |
email / setEmail(v) | string | Optional contact email. |
submit() | () => Promise<SubmitResult> | Send it. Validates non-empty first. |
submitting | boolean | In-flight flag for disabling the button. |
submitted | boolean | True after a successful send (show a thank-you). |
queued | boolean | True when saved offline; auto-retries later. |
error | string | null | Validation / network error message. |
reset() | () => void | Clear draft + submitted/error state. |
submit() resolves to SubmitResult = { ok: boolean; queued: boolean; id?: string; error?: string }.
useMessages()
The user's own message threads, with replies and unread counts. Powers the Messages tab; build a custom inbox/conversation UI on it.
const { threads, unreadTotal, loading, reply, markRead } = useMessages();
| Field | Type | Description |
|---|---|---|
threads | MessageThread[] | { id, body, createdAt, unread, replies: MessageReply[] }, newest first. |
unreadTotal | number | Unread admin replies across all threads. |
loading | boolean | Initial fetch flag. |
refresh() | () => Promise<void> | Re-fetch threads. |
reply(id, body) | (id, body) => Promise<boolean> | Add the user's reply to a thread. |
markRead(id) | (id) => Promise<void> | Mark a thread's admin replies read. |
useUnreadMessages()
const unread = useUnreadMessages(); // number — reactive
useAnnouncements()
Active, non-dismissed announcement banners.
const { announcements, dismiss } = useAnnouncements();
// announcements: Announcement[] = { id, title, body, severity, publishedAt }
dismiss(id);
useFeatureRequests()
Read, vote on, and create roadmap ideas. Voting is optimistic — the UI updates instantly and reconciles with the server's authoritative count.
const { requests, loading, vote, create } = useFeatureRequests();
// requests: FeatureRequest[] sorted by votes
vote(id, true); // upvote
vote(id, false); // remove vote
create({ title: "Dark mode" }); // suggest a new idea
| Field | Type | Description |
|---|---|---|
requests | FeatureRequest[] | { id, title, body, status, voteCount, hasVoted, createdAt }, vote-sorted. |
loading | boolean | Initial fetch flag. |
refresh() | () => Promise<void> | Re-fetch the list. |
vote(id, next) | (id: string, next: boolean) => Promise<void> | Toggle the caller's vote. |
create(input) | ({ title, body? }) => Promise<FeatureRequest | null> | Suggest a new idea. |
useChangelog()
Render release notes anywhere and track read state.
const { entries, loading, markAllRead } = useChangelog();
// entries: ChangelogEntry[] = { id, title, body, publishedAt, read }
| Field | Type | Description |
|---|---|---|
entries | ChangelogEntry[] | Published entries, newest first, each with a read flag. |
loading | boolean | Initial fetch flag. |
refresh() | () => Promise<void> | Re-fetch entries + unread count. |
markRead(id) | (id: string) => Promise<void> | Mark one entry read. |
markAllRead() | () => Promise<void> | Mark every entry read (clears the badge). |
useUnreadChangelog()
const unread = useUnreadChangelog(); // number — reactive
A cheap, reactive unread count — perfect for a nav bell/badge without mounting the whole changelog.
useFlag(key, defaultValue?)
const enabled = useFlag("new_checkout_flow"); // boolean — reactive
const enabled = useFlag("new_checkout_flow", true); // custom fallback
Reads a boolean feature flag by key (create flags in the dashboard under
Flags). Fails closed: returns defaultValue (default false) until flags
have loaded or when the key doesn't exist. Flags refresh on launch, on app
foreground (RN), and live over SSE when you toggle them in the dashboard — so a
flip reaches running apps in seconds without a deploy.
useResolvedTheme(systemColorScheme?)
const theme = useResolvedTheme("dark");
// theme.colors.primary, theme.radius, theme.fontFamily, theme.colorScheme
Returns concrete ResolvedTheme tokens after merging defaults ← server config
← theme prop. Use it so your custom UI matches the active widget theme.
Pass the platform's current scheme ("light"/"dark") for "system" mode.
Types
WherdUser, WherdView ("messages" | "featureRequests" | "changelog"),
MessageType, MessageThread, MessageReply, Announcement, ThemeTokens,
ResolvedTheme, WidgetConfig, FeatureRequest, ChangelogEntry — all exported.
Behavior notes
- Anonymous by default — a persisted
anonymousIdis generated; callingidentify()promotes that record in place. - Offline queue — messages submitted while offline are persisted and retried automatically on the next app launch.
- Server-driven config — theme, modules, copy, and activation are fetched from your dashboard settings at startup, so you can change them without shipping an app update.
- Live flags — feature-flag toggles push to connected clients over the same SSE stream as messages and announcements; offline clients keep their last-known values and catch up on the next launch or foreground.