Kujun
Local-first iOS habit tracker with optional NFC check-ins, a GitHub-style consistency heat map, streaks, and a Home Screen widget
Overview
Kujun (from Korean 꾸준, kkujun: "steady, consistent") is a local-first habit tracker for iOS. Pair a habit with an inexpensive NFC tag, place it where the action happens, and scan there to check in. It also includes a GitHub-style consistency heat map, streaks, and a Home Screen widget.
Built with Expo SDK 57 as a real-world example, not a template you are meant to ship as-is. Use it to study file-based routing, SQLite, SwiftUI widgets, local notifications, and iOS 26 liquid glass wired together in one app.
No accounts and no backend. Everything lives in a local SQLite database on the device.
Key Features
This project includes:
- NFC check-ins - Pair a habit with an inexpensive NTAG213/216 sticker, place it where the habit happens, and scan it to complete the habit
- Optional by design - Habits without a tag still work with a normal tap. NFC is recommended, never required
- Track habits - Choose a name, SF Symbol icon, accent color, and weekly schedule
- Today view - Scan or tap what is due today, with haptics and a small celebration
- Consistency heat map - A GitHub-style contribution grid per habit, plus current/best streaks and completion rate
- Home Screen widget - A SwiftUI widget (small and medium) showing your last 7-14 days and streaks, tappable straight into the app
- Reminders - Optional per-habit local notifications at a chosen time
- Onboarding - A focused first-run welcome that leads into creating the first habit. Reminders and NFC are introduced only when relevant
- Private by design - No account, no server, no tracking. Your habits stay on the device
Why it is useful to study
Most habit apps let you tap a checkbox from the couch. Kujun adds a little real-world friction: the check-in happens where the habit happens. Tags are a personal commitment device, not a security system. Unpairing or disabling NFC verification takes 24 hours, so the friction means something.
The widget is the trickiest part of the project. Widget code is serialized and runs inside the native widget extension, so it cannot reference anything from module scope. The app sends it a plain snapshot of props after every state change. That pattern is worth studying if you want to build widgets with Expo.
Tech Stack
- Framework: Expo SDK 57 and React Native 0.86
- Navigation: Expo Router with file-based routing and typed routes
- Language: TypeScript, with React Compiler enabled
- Persistence: expo-sqlite on device
- Widgets: expo-widgets and @expo/ui (SwiftUI Home Screen widget written in TSX)
- UI: expo-glass-effect and expo-symbols for iOS 26 liquid glass and SF Symbols
- Device APIs: expo-notifications, expo-haptics, react-native-nfc-manager, and react-native-reanimated
Run it
Requires Xcode. This app uses a Home Screen widget and iOS 26 APIs, so it cannot run in Expo Go.
bun install
bun run iosOn a physical device:
bun run ios:deviceWhat You Get with Pro Lifetime Access
When you become a Pro member at Code with Beto, you unlock lifetime access to all our premium resources. Including: complete source code, templates, and tools through our private GitHub organization. Every project we release is available there, searchable and ready to use.
We actively maintain everything to ensure a smooth dev experience on iOS (with Android support coming soon), with easy setup and up-to-date dependencies. Pro access also includes entry to our private Discord community, where you can ask questions, get support, and connect with other developers.
Get Started Today
Questions? Join our Discord server where our community of developers is ready to help! Or if you prefer, send us an email at beto@codewithbeto.dev