React Native
The official React Native SDK for Swake. Feedback forms, bug reporting, surveys, NPS, voting boards, notifications, and breadcrumbs — fully themeable and offline-first.
Overview
@swake/react-native is a feedback, bug-reporting, and survey SDK for React Native apps — a floating trigger button, feedback form, bug reporter with screenshot annotation, in-app voting board, and NPS/survey modals, all offline-first so nothing is lost on a flaky connection.
A few concepts come up throughout this guide:
| Concept | What it is |
|---|---|
apiKey | Your project's key, the only required option to Swake.init(). |
<SwakeProvider> | Wraps your app and renders every Swake overlay — feedback form, bug reporter, survey modals, shake prompt, and the trigger FAB. Required for any Swake UI to appear. |
identify() | Links feedback, surveys, and notifications to a user. Almost everything downstream depends on it. |
| Offline queue | Submissions and replies are queued and retried automatically — persisted across app restarts if @react-native-async-storage/async-storage is installed. |
| Theme | A single theme object, referenced by nearly every widget, applied to the native UI. |
Getting Started
Requirements
| Dependency | Minimum version |
|---|---|
| React Native | >= 0.73 |
| React | >= 18 |
| TypeScript (recommended) | >= 5 |
Installation
npm install @swake/rn-swake-sdk lucide-react-native react-native-svg
# or
yarn add @swake/rn-swake-sdk lucide-react-native react-native-svg
# or
pnpm add @swake/rn-swake-sdk lucide-react-native react-native-svg
Quick Start
Two things are required: call Swake.init() once at app startup, and wrap your component tree with <SwakeProvider>.
import Swake, { SwakeProvider } from '@swake/react-native';
// 1. Initialise before any component renders
Swake.init({
apiKey: 'ep_live_your_key_here',
});
export default function App() {
return (
// 2. Wrap your app so modals and overlays can render
<SwakeProvider>
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
</SwakeProvider>
);
}
<SwakeProvider> renders the feedback form modal, bug reporter, survey overlays, shake prompt, and the floating trigger button. It must be an ancestor of any screen where you want Swake UI to appear.
Full init options
Everything except apiKey is optional.
Swake.init({
apiKey: 'ep_live_your_key_here',
baseUrl: 'https://api.swake.io', // custom API endpoint
debug: false, // verbose logging
appVersion: '2.1.0', // auto-detected if omitted
// Shake gesture
shakeToReport: true,
shakeThreshold: 2.5, // g-force
shakeCooldown: 10000, // ms between prompts
// Floating trigger button
trigger: { /* see Trigger Button section */ },
// Surveys
surveys: { /* see Surveys & NPS section */ },
// Breadcrumbs
breadcrumbs: { /* see Breadcrumbs section */ },
// Global theme
theme: { /* see Theme Customization section */ },
});
Identifying Users
Call identify() after the user signs in. This links all feedback, notifications, and survey responses to their account. Call clearIdentity() on logout.
import Swake from '@swake/react-native';
async function onLoginSuccess(user: User) {
await Swake.identify({
userId: user.id, // required — your internal user ID
email: user.email, // optional but recommended
name: user.name, // optional
metadata: { // optional — custom traits
plan: 'pro',
company: 'Acme Corp',
},
});
}
function onLogout() {
Swake.clearIdentity();
}
email and name are preserved server-side if omitted on subsequent calls. metadata is fully replaced on every call.
Feedback
Feedback Form
Open the built-in feedback form modal. It includes a type selector, title and description fields, and file attachment support.
// Open with defaults
Swake.showFeedbackForm();
// Open with options
Swake.showFeedbackForm({
defaultType: 'feature', // pre-select a type
showTypeSelector: true, // show bug / idea / help tabs
title: "What's on your mind?", // header text
placeholder: 'Tell us more...', // description placeholder
theme: { primaryColor: '#0EA5E9' }, // per-call theme override
});
You can also open the form via the imperative shorthand:
// Opens the feedback form (equivalent to showFeedbackForm)
Swake.open();
// Opens the bug reporter instead
Swake.open('bug');
Bug Reporter
The bug reporter captures a screenshot, lets the user annotate it (draw, rectangles, ellipses), and submits it alongside the feedback form — all in one flow.
For the bug reporter (screenshot + annotation), install both react-native-view-shot and react-native-svg.
// Open the bug reporter
Swake.showBugReporter();
// With a theme override
Swake.showBugReporter({
theme: { primaryColor: '#EF4444' },
});
Required dependencies: react-native-view-shot (screenshot capture) and react-native-svg (annotation canvas). The SDK falls back to a regular feedback form if either is missing.
Shake to report
When shakeToReport is enabled (the default), shaking the device shows a prompt asking the user if they want to report a bug. Accepting opens the bug reporter.
// Enabled by default in init:
Swake.init({
apiKey: '...',
shakeToReport: true, // default
shakeThreshold: 2.5, // g-force sensitivity
shakeCooldown: 10000, // ms between prompts
});
// Toggle at runtime
Swake.setShakeToReport(false); // disable temporarily
Submitting Feedback
Submit feedback from your own custom UI. The submission is queued offline-first and synced automatically when connectivity is available.
import Swake from '@swake/react-native';
const submission = await Swake.submitFeedback({
type: 'bug', // 'bug' | 'feature' | 'question'
title: 'Checkout button unresponsive',
description: 'Tapping the checkout button on iOS 17 does nothing.',
attachments: [ // optional — max 5 files
{ uri: photo.uri, filename: 'screenshot.png', mimeType: 'image/png' },
],
});
console.log('Created:', submission.id);
Trigger Button (FAB)
The SDK renders a floating action button (FAB) that opens the feedback form on tap and the bug reporter on long-press. Configure it in init().
Swake.init({
apiKey: '...',
trigger: {
position: 'bottom-right', // 'bottom-left' | 'top-right' | 'top-left'
icon: 'chat', // 'feedback' | 'bug' | 'idea' | { uri: '...' }
label: 'Feedback', // optional — makes it an extended FAB
hidden: false, // start hidden, show later
topOffset: 0, // additional spacing from top (when position is 'top-*')
bottomOffset: 0, // additional spacing from bottom (when position is 'bottom-*')
style: {
backgroundColor: '#6366F1',
size: 56, // diameter in dp
borderRadius: 28, // half of size = circle
shadow: true,
},
},
});
// Show / hide at runtime
Swake.setTriggerVisible(false);
Swake.setTriggerVisible(true);
| Position | Description |
|---|---|
bottom-right | Bottom-right corner (default) |
bottom-left | Bottom-left corner |
top-right | Top-right corner (respects safe area) |
top-left | Top-left corner (respects safe area) |
{ x, y } | Absolute pixel position |
Avoiding overlaps with navigation
Use topOffset and bottomOffset to add extra spacing when your app has bottom tab bars, navigation headers, or other fixed UI elements. The FAB automatically respects safe areas and adds these offsets on top.
// Example: FAB positioned above React Navigation bottom tabs
Swake.init({
apiKey: '...',
trigger: {
position: 'bottom-right',
bottomOffset: 60, // tab bar height + some padding
},
});
// Example: FAB below a custom top header
Swake.init({
apiKey: '...',
trigger: {
position: 'top-right',
topOffset: 50, // header height
},
});
Recommended values: For standard React Navigation tab bars, use bottomOffset: 60 (iOS) or bottomOffset: 70 (Android). For headers, use topOffset: 44-60 depending on your header design.
Engagement
Surveys & NPS
Surveys are configured in the Swake portal and delivered to users who match targeting rules. After identify(), the SDK automatically checks eligibility and shows the first matching survey.
Swake.init({
apiKey: '...',
surveys: {
enabled: true, // enable survey rendering (default: true)
autoShow: true, // show after identify() (default: true)
delay: 3000, // ms before presenting (default: 3000)
cooldown: 86_400_000, // min ms between surveys (default: 24h)
},
});
Manual trigger
// Re-check eligibility and show if a survey matches
await Swake.checkSurveys();
// Force-show a specific survey by ID (bypasses cooldown)
await Swake.showSurvey('surv_01HW...');
Event callbacks
Swake.onSurveyShown(({ id, type }) => {
console.log('Survey shown:', type, id);
});
Swake.onSurveyCompleted(({ survey, response }) => {
console.log('Completed', survey.type, response.answers.length, 'answers');
});
Swake.onSurveyDismissed(({ id }) => {
console.log('Dismissed:', id);
});
Inline polls
Embed a single-question poll directly in your UI. Shows percentage results after voting. Poll questions can be single-choice (a tap casts the vote) or multi-select (multiple_choice) — the widget adapts automatically based on the question configured in the portal, letting the user pick several options (up to the per-question limit) and submit with a Vote button.
import { PollWidget } from '@swake/react-native';
<PollWidget
surveyId="surv_01HW..." // optional — auto-picks first eligible if omitted
onVote={(index) => console.log('Voted option', index)}
onDismiss={() => console.log('Poll dismissed')}
/>
Whether a poll is single- or multi-select is set per question in the portal (via the option-selection limit) — no SDK flag needed. For a multi-select question, onVote fires once with the first selected option's index.
Voting Board
Open a public voting board as a native in-app screen — item list with upvote buttons, rendered from the public board API. Users can browse and vote on items. The SDK uses the identified user's identity automatically if identify() was called.
// Open the native board UI (no WebView needed)
Swake.openVotingBoard('public-roadmap', {
theme: { colorScheme: 'dark' }, // optional
});
The board is fully native — no react-native-webview is needed. Browsing works anonymously; casting a vote requires identify().
Programmatic voting
Build your own voting UI by fetching items and casting votes via the API.
// Fetch board items
const { items, hasMore } = await Swake.getBoardItems('public-roadmap', {
sort: 'votes', // 'votes' | 'recent'
page: 1,
});
// Cast a vote (requires identify)
const result = await Swake.vote('public-roadmap', items[0].id);
// { status: 'voted', voteCount: 42 }
// Remove a vote
await Swake.removeVote('public-roadmap', items[0].id);
Notifications
Notifications
Users receive in-app notifications when a team member replies to their feedback or changes a status. Use these methods to build a notification inbox or badge.
import Swake from '@swake/react-native';
// Badge count
const unread = await Swake.getUnreadCount();
// List notifications
const { notifications, pagination } = await Swake.listNotifications({
unreadOnly: true,
limit: 20,
});
// Mark as read
await Swake.markNotificationRead(notifications[0].id);
await Swake.markAllNotificationsRead();
Push tokens
Register device push tokens for server-sent notifications. Call after identify() and after the user grants permission.
// Register
await Swake.registerPushToken('apns', deviceToken); // iOS
await Swake.registerPushToken('fcm', deviceToken); // Android
await Swake.registerPushToken('expo', expoPushToken); // Expo
// Unregister on logout
await Swake.unregisterPushToken(deviceToken);
Customization
Theme Customization
Set a global theme in init() to brand every Swake widget — the feedback form, bug reporter, surveys, voting board, and the floating trigger button.
Swake.init({
apiKey: '...',
theme: {
primaryColor: '#8B5CF6',
successColor: '#10B981',
dangerColor: '#EF4444',
backgroundColor: '#FFFFFF',
surfaceColor: '#F9FAFB',
overlayColor: 'rgba(0,0,0,0.5)',
textColor: '#111827',
textSecondary: '#6B7280',
borderColor: '#E5E7EB',
borderRadius: 12,
fontFamily: 'Inter',
colorScheme: 'light', // 'light' | 'dark' | 'system'
},
});
Token reference
| Token | Default | Description |
|---|---|---|
primaryColor | #6366F1 | Buttons, active states, FAB background |
successColor | #10B981 | Success checkmark, resolved badges |
dangerColor | #EF4444 | Error borders, destructive actions |
backgroundColor | #FFFFFF | Modal / panel background |
surfaceColor | #F9FAFB | Input fields, secondary surfaces |
overlayColor | rgba(0,0,0,0.45) | Backdrop behind modals |
textColor | #111827 | Primary text |
textSecondary | #6B7280 | Labels, placeholders, metadata |
borderColor | #E5E7EB | Input borders, dividers |
borderRadius | 8 | Corner radius for inputs and buttons |
fontFamily | System default | Font family applied to all text |
colorScheme | system | Light / dark mode (React Native only) |
Per-call overrides
Every UI method (showFeedbackForm, showBugReporter, openVotingBoard, etc.) accepts a theme parameter that merges on top of the global theme for that single call.
Swake.showFeedbackForm({
theme: { primaryColor: '#0EA5E9' },
});
Swake.openVotingBoard('roadmap', {
theme: { colorScheme: 'dark' },
});
Merge order: SDK defaults → global init() theme → per-call theme. Only the tokens you set are overridden.
Breadcrumbs
Breadcrumbs create an automatic event trail attached to every feedback submission — helping your team understand what the user did before reporting an issue.
Swake.init({
apiKey: '...',
breadcrumbs: {
enabled: true, // default: true
maxEntries: 50, // ring buffer size (max: 100)
captureNavigation: true, // screen changes
captureNetwork: true, // HTTP requests
captureConsole: true, // console.error / console.warn
},
});
React Navigation integration
import { useNavigationContainerRef } from '@react-navigation/native';
import Swake from '@swake/react-native';
function App() {
const navRef = useNavigationContainerRef();
return (
<NavigationContainer
ref={navRef}
onStateChange={() => Swake.setNavigationRef(navRef)}
>
<RootNavigator />
</NavigationContainer>
);
}
Manual breadcrumbs
// Add a custom breadcrumb
Swake.addBreadcrumb({
category: 'custom', // 'navigation' | 'network' | 'console' | 'custom'
message: 'User tapped checkout',
data: { cartTotal: 49.99 },
});
// Read current trail
const trail = Swake.getBreadcrumbs();
// Clear
Swake.clearBreadcrumbs();
Offline
Offline Support
All feedback submissions, comments, and identify calls are automatically queued when the device is offline and synced when connectivity returns. Install @react-native-async-storage/async-storage for persistence across app restarts.
import { offlineQueue } from '@swake/react-native';
// Check queue size
console.log('Pending items:', offlineQueue.size);
// Force flush (sends everything now)
await offlineQueue.flush();
// Clear all queued items
await offlineQueue.clear();
AI Setup
Using Claude Code
Prefer to let an AI coding agent do the wiring? Run Claude Code from the root of your React Native project and paste the prompt below. It reads these docs as the source of truth, detects your stack, asks you for your API key and which peers to install, then wires up the provider, init(), identify(), breadcrumbs, and the native setup — in one shot.
Prerequisites
Before you run the prompt, set up your project in the Swake portal and grab the values the agent will ask for — it only writes placeholders to your env files, so you supply the real values yourself.
- Sign in to the Swake portal and create a project (or open an existing one).
- In Project settings → API keys, get existing or create a key and copy it — it's shown once and starts with
ep_live_. - If you want the in-app voting board, create a board and copy its slug from the board's URL / settings.
Have these ready when the agent prompts you — the same values you'd otherwise wire up by hand in the Quick Start:
| Value | Where to find it | Example |
|---|---|---|
apiKey | Project settings → API keys | ep_live_… |
baseUrl | API endpoint — optional, only needed when self-hosting or routing through your own backend | https://api.swake.io |
boardSlug | Voting board settings — optional; passed to openVotingBoard() / vote(), not init() | public-roadmap |
The prompt is additive and safe: a Swake or network error must never break your app's boot or existing behavior. The agent will pause and ask before installing packages or writing secrets, and it never guesses APIs that aren't in these docs.
The prompt
Run Claude Code from your project root and paste this:
Fully integrate the Swake SDK (feedback infra: forms, bug reports, surveys, notifications, voting boards). Additive: a Swake/network error must NEVER break boot or existing behavior. Never guess or fake APIs.
1. FIRST read the docs (source of truth for package, peers, init/identify, provider, native setup). Fetch with a browser User-Agent (a plain fetch returns 403), e.g. curl -sL -A "Mozilla/5.0" "https://swake.io/docs/sdk?framework=react-native" (match the framework param). Then detect the stack; if no matching SDK exists, STOP and report.
2. ASK the user (AskUserQuestion tool) for API key (ep_live_...) + base/portal URL + board slug → env + .env.example (placeholders only). Then ASK to confirm which peers to install: required (lucide-react-native, react-native-svg) + optional per feature (async-storage, view-shot, shake, image-picker, webview). Install approved only.
3. Mount the provider once at the app root, above navigation.
4. Call init() once, idempotently. With onboarding, set surveys.autoShow:false + init lazily on the first main screen. Theme→your palette; re-init on dark toggle.
5. identify() with a stable ID + metadata; clearIdentity() on logout.
6. Route every call via one wrapper (try/catch + configured/initialized guard); feed breadcrumbs. Native: iOS usage strings + pods; strip Android ACTIVITY_RECOGNITION.
7. Verify on Android AND iOS release builds: a real submission hits the dashboard; no surveys over onboarding. Report changes, manual steps, assumptions.
Reference
Full API Reference
Click any method for its full signature, parameters, and an example.
| Method | Description |
|---|---|
| Initialise the SDK (call once) | |
| Set the current user identity | |
| Clear identity and wipe queue | |
| Submit feedback programmatically | |
| Open the feedback form modal | |
| Open the bug reporter with screenshot | |
| Open the submission list screen | |
| Open a submission detail screen | |
| Open feedback form or bug reporter | |
| Close any open Swake UI | |
| Open a voting board in a WebView | |
| Cast a vote on a board item | |
| Remove a vote | |
| Fetch board items | |
| Re-check survey eligibility | |
| Force-show a survey by ID | |
| Get unread notification count | |
| List notifications | |
| Mark a notification as read | |
| Mark all notifications as read | |
| Register push token | |
| Unregister push token | |
| Show/hide the FAB | |
| Toggle shake detection | |
| Add a custom breadcrumb | |
| Read the breadcrumb trail | |
| Clear all breadcrumbs | |
| Wire React Navigation for breadcrumbs | |
| Fetch a targeted ad | |
| Clear cached ads | |
| Start a new replay capture window | |
| Stop capture and discard the buffer | |
| Returns true when actively recording | |
| Tear down all listeners |
Event callbacks
| Callback | Fires when |
|---|---|
| A survey is displayed | |
| User completes a survey | |
| User dismisses a survey | |
| Workspace hits a plan limit |
Optional Dependencies
Peer packages needed for specific features — the SDK degrades gracefully if any are missing.
| Feature | Package |
|---|---|
| Offline queue persistence | @react-native-async-storage/async-storage |
| Shake to report | react-native-shake (or expo-sensors) |
| Screenshot capture | react-native-view-shot |
| Screenshot annotation | react-native-svg |
| In-app voting board | react-native-webview |
| Trigger button icons | lucide-react-native |
| Haptic feedback | expo-haptics |