Wherddocs
HomeDashboard
Guides

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. Requires expo-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-sse if 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.

NextAPI reference