- Accordion
- Action Sheet
- Alert Dialog
- Alert
- Audio Player
- Audio Recorder
- Audio Waveform
- Avatar
- AvoidKeyboard
- Badge
- BottomSheet
- Button
- Camera Preview
- Camera
- Card
- Carousel
- Checkbox
- Collapsible
- Color Picker
- Combobox
- Date Picker
- File Picker
- Gallery
- Hello Wave
- Icon
- Image
- Input OTP
- Input
- Link
- MediaPicker
- Mode Toggle
- Onboarding
- ParallaxScrollView
- Picker
- Popover
- Progress
- Radio
- ScrollView
- SearchBar
- Separator
- Share
- Sheet
- Skeleton
- Spinner
- Switch
- Table
- Tabs
- Text
- Toast
- Toggle
- Video
- View
Convex is a real-time backend. You write queries and mutations as TypeScript functions, and every client subscribed to a query re-renders when its data changes — no refetching, no cache invalidation.
BNA UI ships two Convex scaffolds: one with a backend and no sign-in, one with
@convex-dev/auth and the sign-in screens
already built.
A schema and a live query. No authentication.
Google, Apple, password and email OTP sign-in, pre-wired.
This section covers the schema, authentication, realtime, storage, actions and deployment for both scaffolds, plus provider setup for Google, Apple and Resend.
The auth starter
Built around the same principles as the rest of BNA UI:
- Open Code: the authentication screens and the Convex functions are copied into your project, not imported from a package.
- Mobile-First: flows designed for React Native, with tokens in the platform keychain.
- Cross-Platform: iOS, Android and web.
- Real-Time: every query is a live subscription.
Sign-in methods
| Method | Provider | Ships with a button |
|---|---|---|
| Anonymous | @convex-dev/auth Anonymous | Yes |
| Email + password | @convex-dev/auth Password, with reset | Yes |
| Email OTP | Resend | Yes |
@auth/core Google | Yes | |
| Apple | @auth/core Apple | Yes |
| GitHub | @auth/core GitHub | No — configured only |
GitHub is set up in convex/auth.ts but the sign-in screen has no button for
it. Model one on components/auth/google.tsx if you want it.
What lands in your project
app/_layout.tsx ConvexAuthProvider, switching on auth state
components/auth/
├── auth.tsx The sign-in screen: Password / OAuth / OTP tabs
├── password.tsx Sign in, sign up, forgot and reset password
├── email-otp.tsx Passwordless email codes
├── google.tsx Google sign-in button
├── apple.tsx Apple sign-in button
└── singout.tsx Sign-out button, used in the settings tab
convex/
├── auth.ts Providers, password rules, redirect allow-list
├── auth.config.ts JWT issuer
├── schema.ts authTables + a users table
├── users.ts User queries and mutations
├── http.ts Mounts the auth HTTP routes
├── resendOTP.ts Email OTP delivery
├── passwordReset.ts Password-reset codes
└── resendPasswordOTP.ts
The root layout renders a spinner while the session resolves, the sign-in screen when signed out, and your tabs when signed in:
<ConvexAuthProvider client={convex} storage={secureStorage}>
<AuthLoading>
<Spinner size='lg' variant='circle' />
</AuthLoading>
<Unauthenticated>
<Auth />
</Unauthenticated>
<Authenticated>
<Stack>{/* your app */}</Stack>
</Authenticated>
</ConvexAuthProvider>storage is expo-secure-store on iOS and Android — the Keychain and the
Keystore respectively — and the platform default on web.
Reading the signed-in user
export const get = query({
handler: async (ctx) => {
const authId = await getAuthUserId(ctx);
if (!authId) {
throw new Error('Not authenticated');
}
return await ctx.db.get(authId);
},
});Password rules
convex/auth.ts requires at least 8 characters with one digit, one lowercase
and one uppercase letter. Edit validatePasswordRequirements to change it — it
throws, and the message surfaces in the sign-up form.
Environment
| Variable | Where it lives | Set by | Used for |
|---|---|---|---|
EXPO_PUBLIC_CONVEX_URL | .env.local | npx convex dev | Building the client |
EXPO_URL | Convex deployment | bna-ui convex | Allow-listing your app scheme |
SITE_URL | Convex deployment | bna-ui convex | Allow-listing the web redirect target |
AUTH_RESEND_KEY | Convex deployment | you | Email OTP and password reset |
AUTH_GOOGLE_ID/SECRET | Convex deployment | you | Google sign-in |
AUTH_APPLE_ID/SECRET | Convex deployment | you | Apple sign-in |
CONVEX_SITE_URL | Convex deployment | Convex, automatically | The JWT issuer |
Deployment variables are set with npx convex env set NAME value and listed
with npx convex env list. They are not in .env.local, because server-side
functions read them.
The full walkthrough, including production, is in the auth installation guide.
Guides
- Database and schema — defineTable, indexes, and how authorization replaces RLS
- Authentication — session storage, route guards, OAuth, and the redirect allow-list
- Google · Apple · Resend
- Storage · Realtime · Actions
- Deployment — EAS, CI, and dev vs. prod deployments
- Troubleshooting — and a Supabase → Convex migration guide