UI Components
The SDK ships three React Native components for the full OTA lifecycle. They're opinionated, accessible, and themable. Drop them in and you're done — or use them as a starting point for a fully custom UI.
All three are auto-rendered by <SwiftPatchProvider> when their corresponding prop is enabled. You can also import and render them yourself for fine-grained control.
<UpdateBanner />
A subtle, non-modal banner that slides in when an update is ready to apply.
import { UpdateBanner } from '@swiftpatch/react-native';
<UpdateBanner
variant="subtle"
position="bottom"
behavior="dismissible"
/>
Rendered state:
┌───────────────────────────────────────────────┐
│ New update available │
│ v1.2.3 · Perf fixes + new feature [Later] │
│ [Apply now] │
└───────────────────────────────────────────────┘
Props
interface UpdateBannerProps {
variant?: 'subtle' | 'bold' | 'pulse';
position?: 'top' | 'bottom';
behavior?: 'dismissible' | 'persistent';
theme?: ThemeProp;
title?: string;
applyLabel?: string;
dismissLabel?: string;
onApply?: () => void;
onDismiss?: () => void;
style?: ViewStyle;
disabled?: boolean;
}
| Prop | Default | Description |
|---|---|---|
variant | 'subtle' | subtle uses the theme surface color. bold uses the accent. pulse adds a gentle scale loop on the CTA. |
position | 'bottom' | 'top' or 'bottom'. |
behavior | 'dismissible' | Show or hide the "Later" button. Set 'persistent' to force an apply. |
theme | 'auto' | 'auto', 'light', 'dark', or a partial Theme object. |
title, applyLabel, dismissLabel | — | Copy overrides. |
onApply, onDismiss | — | Fire after the respective verbs complete. |
style | — | Override the container style. |
disabled | false | Never render. |
Accessibility
The banner is announced via accessibilityLiveRegion="polite" and respects prefers-reduced-motion — it skips the slide/fade animation when the OS flag is on.
<UpdateBlocker />
A full-screen modal that appears when a mandatory release is pending. The user cannot dismiss it — the only action is Apply.
import { UpdateBlocker } from '@swiftpatch/react-native';
<UpdateBlocker
logo={require('./assets/logo.png')}
accentColor="#ff3366"
title="Critical update"
body="A security fix is installing. This takes a few seconds."
applyLabel="Install now"
/>
Rendered state:
┌───────────────────────────────────────────────┐
│ │
│ [LOGO] │
│ │
│ Critical update │
│ │
│ A security fix is installing. │
│ This takes a few seconds. │
│ │
│ Downloading… 42% │
│ ████████░░░░░░░░░░░░░░ │
│ │
│ [ Install now ] │
│ │
└───────────────────────────────────────────────┘
Props
interface UpdateBlockerProps {
logo?: ImageSourcePropType;
accentColor?: string;
title?: string;
body?: string;
applyLabel?: string;
theme?: ThemeProp;
style?: ViewStyle;
disabled?: boolean;
}
While the download is in progress the blocker shows a progress bar. Once the bundle is ready, the Apply CTA becomes active. On press, the SDK installs and restarts.
<UpdateOnboarding />
A three-step carousel shown exactly once, after the very first OTA is applied. Use it to educate new users about how updates work.
import { UpdateOnboarding } from '@swiftpatch/react-native';
<UpdateOnboarding
steps={[
{
title: 'Your app is now faster',
description: 'We updated in the background — no download, no wait.',
},
{
title: 'What is new',
description: 'Dark mode, better performance, and 3 bug fixes.',
},
{
title: 'You are all set',
description: 'Future updates will apply the same way.',
},
]}
/>
Props
interface UpdateOnboardingProps {
steps?: UpdateOnboardingStep[];
onDone?: () => void;
theme?: ThemeProp;
disabled?: boolean;
continueLabel?: string;
skipLabel?: string;
style?: ViewStyle;
}
interface UpdateOnboardingStep {
title: string;
description: string;
}
The "already shown" flag is persisted in native preferences. It never reappears on the same install, even after cold starts.
Theming
All three components share a single Theme type:
interface Theme {
background: string;
surface: string;
text: string;
textMuted: string;
accent: string;
accentText: string;
danger: string;
border: string;
}
type ThemeProp = 'auto' | 'light' | 'dark' | Partial<Theme>;
Default themes:
export const LIGHT_THEME: Theme = {
background: '#ffffff',
surface: '#f3f4f6',
text: '#0f172a',
textMuted: '#64748b',
accent: '#2563eb',
accentText: '#ffffff',
danger: '#dc2626',
border: '#e2e8f0',
};
export const DARK_THEME: Theme = {
background: '#0f0f23',
surface: '#1a1a3e',
text: '#ffffff',
textMuted: '#94a3b8',
accent: '#3b82f6',
accentText: '#ffffff',
danger: '#ef4444',
border: '#252550',
};
Override a few colors
<UpdateBanner theme={{ accent: '#ff3366', accentText: '#ffffff' }} />
Override the full theme
import { LIGHT_THEME } from '@swiftpatch/react-native';
const MY_THEME = {
...LIGHT_THEME,
accent: '#ff3366',
background: '#fafafa',
};
<SwiftPatchProvider config={{ debug: __DEV__ }} theme={MY_THEME}>
<App />
</SwiftPatchProvider>
Force light / dark
<UpdateBanner theme="dark" />
Replacing the built-in UI
Turn it all off and render your own:
<SwiftPatchProvider
config={{ debug: __DEV__ }}
autoShowBanner={false}
blockOnMandatory={false}
>
<App />
<MyCustomUpdateBanner />
</SwiftPatchProvider>
Your custom component uses useSwiftpatchUpdate():
import { useSwiftpatchUpdate } from '@swiftpatch/react-native';
function MyCustomUpdateBanner() {
const { phase, pendingUpdate, applyNow, dismiss } = useSwiftpatchUpdate();
if (phase !== 'ready' || !pendingUpdate) return null;
return (
<MyBrandedToast
title={`v${pendingUpdate.version} is ready`}
primaryAction={{ label: 'Apply', onPress: applyNow }}
secondaryAction={{ label: 'Later', onPress: dismiss }}
/>
);
}
See Hooks for the full hook API.
Next steps
- Hooks —
useSwiftpatchUpdate,useSwiftpatchProgress,useSwiftpatchReady. - Provider reference —
autoShowBanner,blockOnMandatory,theme. - Methods — the imperative verbs the UI components call under the hood.