@mertgulcann/rn-ui-kit
v0.1.1
Published
Cross-platform React Native UI kit with iOS-like components
Maintainers
Readme
@mertgulcann/rn-ui-kit
Cross-platform React Native / Expo UI kit. Components target an iOS-like look on Android; on iOS they prefer system APIs when that is the better UX.
This package is an Expo module (native stubs ready for future native APIs) plus a TypeScript UI layer.
Table of contents
Install
npm install @mertgulcann/rn-ui-kitBecause this is an Expo module, rebuild the native app after installing (dev client / EAS / npx expo prebuild + run). Expo Go may not include your linked module until you use a custom build.
Peer dependencies
| Package | Required | Notes |
|---------|----------|--------|
| expo | Yes | Expo Modules / autolinking |
| react | Yes | |
| react-native | Yes | |
| react-native-reanimated | Yes | Android custom alert animations |
| react-native-worklets | Yes | Used with Reanimated |
These are not bundled with the kit; your app must already provide them (typical Expo apps do).
Project structure
android/ Expo native module (Kotlin stub)
ios/ Expo native module (Swift stub)
expo-module.config.json
src/
ui-components/
alert/
android.tsx Custom iOS-like alert UI (Android)
ios.tsx Stub (iOS uses system Alert via hook)
use-alert.tsx Imperative API
types.ts
theme/ Theme tokens + RnUiProvider
backdrop/ Default solid backdropEdit Android alert visuals in src/ui-components/alert/android.tsx.
Edit iOS native alert wiring in src/ui-components/alert/use-alert.tsx.
Alert (useAlert)
Imperative confirmation / dialog API inspired by React Native’s Alert.alert, with a custom iOS-style card on Android.
Platform behavior
| Platform | What happens |
|----------|----------------|
| iOS | Calls native Alert.alert. No custom modal is rendered (modal is null). |
| Android | Renders a centered iOS-like card (blur/dim backdrop, spring animation, hairline separators, role-colored actions). You must mount {alert.modal} in the tree. |
Quick start
import { useAlert } from "@mertgulcann/rn-ui-kit";
import { Button, View } from "react-native";
export function LogoutScreen() {
const alert = useAlert();
return (
<View>
<Button
title="Log out"
onPress={() =>
alert.show({
title: "Log out",
message: "Are you sure you want to log out?",
buttons: [
{ label: "Cancel", role: "cancel", onPress: () => {} },
{
label: "Log out",
role: "destructive",
onPress: () => {
// perform logout
},
},
],
})
}
/>
{alert.modal}
</View>
);
}API reference
useAlert(options?)
const alert = useAlert(options?: UseAlertOptions);
alert.show(config: AlertShowConfig): void;
alert.hide(): void;
alert.modal: React.ReactNode; // Android only; null on iOSUseAlertOptions
| Prop | Type | Description |
|------|------|-------------|
| onSelectionHaptic | () => void | Called on action press / dismiss (Android custom UI). |
| renderBackdrop | (opts) => ReactNode | Replaces the default solid backdrop behind the dim layer. |
Same options can also be set once on RnUiProvider.
AlertShowConfig (alert.show(...))
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| title | string | Yes | — | Alert title |
| message | string | No | — | Supporting message |
| buttons | AlertAction[] | Yes | — | Actions (can be [] for title/message-only on iOS; Android still shows the card) |
| layout | "horizontal" \| "vertical" | No | auto | See Layout |
| dismissOnBackdropPress | boolean | No | false | Backdrop / Android back dismiss |
AlertAction
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| label | string | Yes | Button text (text on native iOS Alert) |
| onPress | () => void | Yes | Fired when the action is chosen |
| role | AlertActionRole | No | "default" | "cancel" | "destructive" |
| disabled | boolean | No | Disables the action (Android custom UI) |
| loading | boolean | No | Shows a spinner instead of the label (Android custom UI) |
On Android, choosing an action calls hide() first, then your onPress.
Button roles
| Role | Android look | iOS native Alert style |
|------|----------------|---------------------------|
| default | Accent / info color | default |
| cancel | Semibold weight | cancel |
| destructive | Error / red color | destructive |
Layout
Applies to the Android custom alert (iOS uses the system alert layout).
"vertical"— always stack actions."horizontal"or omitted — with exactly two buttons, places them side by side only if both labels fit in half-width slots (labels are measured; never truncated). Otherwise falls back to vertical.
Dismiss / backdrop
- Default: user must tap an action (
dismissOnBackdropPress: false). - If
dismissOnBackdropPress: true:- Tap outside / Android hardware back dismisses.
- Runs cancel action if one exists (
role: "cancel"), otherwise just closes. - On iOS, maps to
Alert.alert(..., { cancelable: true }).
Loading & disabled
Android only (native iOS alert has no loading spinner API):
alert.show({
title: "Delete account",
buttons: [
{ label: "Cancel", role: "cancel", onPress: () => {} },
{
label: "Delete",
role: "destructive",
loading: isDeleting,
disabled: isDeleting,
onPress: () => deleteAccount(),
},
],
});Haptics
Not bundled. Wire your own:
import * as Haptics from "expo-haptics";
import { useAlert } from "@mertgulcann/rn-ui-kit";
const alert = useAlert({
onSelectionHaptic: () => {
void Haptics.selectionAsync();
},
});Or once at the app root via RnUiProvider.
Custom backdrop (e.g. blur)
Default backdrop is a solid dim. For blur, pass renderBackdrop:
import { BlurView } from "expo-blur";
import { useAlert } from "@mertgulcann/rn-ui-kit";
import { StyleSheet } from "react-native";
const alert = useAlert({
renderBackdrop: () => (
<BlurView intensity={25} style={StyleSheet.absoluteFill} />
),
});Theming (RnUiProvider)
useAlert works without a provider (built-in light/dark defaults follow the system color scheme).
Use RnUiProvider to align colors with your app:
import { RnUiProvider, useAlert } from "@mertgulcann/rn-ui-kit";
export function App() {
return (
<RnUiProvider
themeOverride={{
colors: {
info: "#007AFF",
error: "#FF3B30",
backgroundEmphasis: "rgba(242, 242, 247, 0.92)",
},
}}
onSelectionHaptic={() => {
/* optional */
}}
>
<RootNavigator />
</RnUiProvider>
);
}You can also pass a full theme object (RnUiTheme: colors, spacing, radius, typography).
Full examples
Destructive confirm (logout / delete)
alert.show({
title: "Delete note",
message: "This cannot be undone.",
buttons: [
{ label: "Cancel", role: "cancel", onPress: () => {} },
{ label: "Delete", role: "destructive", onPress: onDelete },
],
});Single OK button
alert.show({
title: "Saved",
message: "Your changes were saved.",
buttons: [{ label: "OK", role: "default", onPress: () => {} }],
});Force vertical actions
alert.show({
title: "Choose an option",
layout: "vertical",
buttons: [
{ label: "Option A", onPress: () => {} },
{ label: "Option B", onPress: () => {} },
{ label: "Cancel", role: "cancel", onPress: () => {} },
],
});Dismissible info (tap outside)
alert.show({
title: "Tip",
message: "You can dismiss this by tapping outside.",
dismissOnBackdropPress: true,
buttons: [{ label: "Got it", role: "cancel", onPress: () => {} }],
});Migration alias
import { useConfirmationModal } from "@mertgulcann/rn-ui-kit";
// identical to useAlert — deprecated aliasRelated deprecated type aliases: ConfirmationModalButton, ConfirmationModalShowConfig, etc. Prefer AlertAction, AlertShowConfig, …
Native module notes
android/andios/currently expose a minimal Expo module namedRnUiKit(no native methods yet).- They exist so future platform APIs can be added without changing package shape.
- UI for Alert lives in TypeScript under
src/ui-components/alert/, not in the native folders.
License
MIT © M-rt4
