Customization
Wherd is designed to go from "looks native to my app" to "fully custom UI" in three tiers. They compose — pick the highest one you need.
Precedence (lowest → highest): SDK defaults → dashboard config → theme prop.
Tier 1 — Theme tokens
Pass a theme to the provider. Works identically on web and React Native.
<WherdProvider
theme={{
colorScheme: "system", // "light" | "dark" | "system" (default: light)
colors: { primary: "#16a34a" },
radius: 16,
typography: { fontFamily: "Inter, sans-serif" }, // web only
}}
/>
Available color tokens: primary, primaryForeground, background,
foreground, muted, mutedForeground, border.
Tier 2 — Server-driven config (no release)
In the dashboard (Settings → Widget config) you control theme, which modules are enabled (Messages / Ideas / What's New), copy, activation (FAB / shake), and auto-show (announcements / release notes). The SDK fetches this at startup, so you can restyle or toggle features without shipping an app update — uniquely useful for mobile.
A theme prop in code still overrides server config where both set a value.
Positioning the launcher
The floating launcher (FAB) defaults to the bottom-right corner. Move it to clear
a tab bar or switch corners via the fab prop on <MessagesWidget>:
<MessagesWidget
fab={{
placement: "bottom-right", // or "bottom-left"
offset: { bottom: 96, side: 20 }, // bump `bottom` above a tab bar
label: "Messages", // optional pill label
}}
/>
For full control over placement, hide the built-in launcher and trigger from your own UI (e.g. a tab-bar button) — see Tier 3.
Show the launcher on one screen only
Sometimes you don't want a floating button app-wide, only on a specific screen
(e.g. Settings → Help). Keep the panel mounted app-wide with the FAB off, and
render a standalone <MessagesFAB> on that screen with force — it renders
regardless of the server activation.fab toggle, and it's the same launcher
(unread badge included). Tapping it opens the app-wide panel.
// App root — panel everywhere (for auto-show), no floating button
<WherdProvider publicKey="pk_…" activation={{ fab: false }}>
<App />
<MessagesWidget />
</WherdProvider>
// Settings screen — the launcher, only here
import { MessagesFAB } from "@wherdkit/react-native"; // or @wherdkit/react
function SettingsScreen() {
return (
<View style={{ flex: 1 }}>
{/* …settings… */}
<MessagesFAB force offset={{ bottom: 96, side: 20 }} />
</View>
);
}
Activation: shake instead of a FAB (React Native)
Override how the widget is opened with the activation prop on <WherdProvider>:
<WherdProvider activation={{ fab: false, shake: true }}>
fab— show/hide the floating launcher.shake— open the widget when the device is shaken. Requiresexpo-sensors(npx expo install expo-sensors); without it, shake is a no-op.
With shake enabled, the SDK shows a one-time hint on first launch so users
discover the gesture (important when there's no FAB). The activation prop
overrides the dashboard's server config.
Auto-show
When there's a new announcement, release note, or admin reply, the SDK can open the widget automatically so users don't miss it — once per item.
- Announcements — on by default (time-sensitive: outages, contests, maintenance).
- Release notes — off by default (less urgent).
- Messages — off by default. When on, the panel opens to the thread when a new unread admin reply arrives. Checked on launch and, on React Native, when the app returns to the foreground.
Configure per project in the dashboard, or override in code:
<WherdProvider autoShow={{ announcements: true, changelog: false, messages: true }} />
Announcements render as severity-styled banners (info / warning / critical) at the top of the widget; users can dismiss them.
Live updates
When a live transport is available, the SDK keeps everything — messages, announcements, release notes, and feature requests — current in real time over Server-Sent Events, no polling. It's automatic:
- Web uses the browser's built-in
EventSource— nothing to install. - React Native uses
react-native-sseif it's installed; otherwise it gracefully falls back to pull-only.
Either way there's a pull-based fallback: data also refetches whenever the
panel opens and (on React Native) when the app returns to the foreground — so
the SDK stays correct even without a live connection. Auto-show applies to live
events too: a new reply/announcement can open the widget per your autoShow
settings.
Tier 3 — Headless
The prebuilt components are built entirely on exported hooks, so you get full parity building your own UI:
import { useComposer, useMessages, useFeatureRequests, useChangelog, useWherd } from "@wherdkit/react";
function MyComposer() {
const { body, setBody, submit, submitting, submitted } = useComposer();
if (submitted) return <p>Thanks!</p>;
return (
<form onSubmit={(e) => { e.preventDefault(); submit(); }}>
<textarea value={body} onChange={(e) => setBody(e.target.value)} />
<button disabled={submitting}>Send</button>
</form>
);
}
Use useMessages() to build your own inbox/conversation UI (threads, replies,
unread). You can mix tiers: use the prebuilt <WhatsNew /> but a custom composer,
etc. Skip <MessagesWidget /> entirely and trigger views with
useWherd().open("messages" | "featureRequests" | "changelog").
See API reference for every hook's return shape.