Intelligent UI SDK for Flutter
Your app has one interface. Your users are not one person.
Website · Documentation · Dashboard · Pricing
Morph is an intelligent UI SDK that makes your Flutter app personal for every user — automatically.
No darkTheme to write. No AppColors to duplicate. No accessibility backlog. One widget. That's it.
MorphProvider(
licenseKey: 'morph-free-demo',
child: MyApp(),
)Works without a license key — native detection is always free. Add a key for AI-powered adaptation. Get your key →
Also available for React. See React SDK →
Morph reads the room the moment your app loads.
- 🌙 Dark at night. Light in the morning.
- ♿ High contrast when the system asks.
- 🎨 Color-blind safe palette when needed.
- ⚡ Reduced motion. Bold text. Language. All detected.
Reads directly from the device OS — platformBrightness, highContrast, textScaleFactor, boldText, disableAnimations. No config needed.
System preference always wins. Time-based logic is a smart fallback — never an override.
IA reads your actual design — not just your colors.
Not a color inverter. Morph reads your ThemeData or your AppColors file — whatever you already have — and generates the opposite theme from your exact palette.
The result: a complete ColorScheme with 4 surface depth levels, WCAG AA verified on every element. It looks hand-crafted. Because the reasoning behind it was.
Your interface learns this person.
Morph observes taps, navigation patterns, session context, zoom events. All local. All private. All in Hive. Nothing leaves the device.
- After 20 interactions → zones this person always uses rise
- After 3 sessions → layout density adapts
- After 5 sessions → the interface morphs around this user's behavior
Always with their permission. Always reversible. Always with an Undo button.
# pubspec.yaml
dependencies:
morphui: ^0.1.0flutter pub add morphuivoid main() {
runApp(
MorphProvider(
licenseKey: 'morph-free-demo',
child: MyApp(),
),
);
}Morph works with whatever color structure you already have. No restructuring. No renaming. No extra files.
MorphProvider(
licenseKey: 'morph-free-demo',
colors: MorphColors(
background: AppColors.background,
surface: AppColors.surface,
primary: AppColors.primary,
text: AppColors.text,
textSecondary: AppColors.textSecondary,
border: AppColors.border,
error: AppColors.error,
success: AppColors.success,
warning: AppColors.warning,
),
child: MyApp(),
)MorphProvider(
licenseKey: 'morph-free-demo',
baseTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF4F46E5),
),
),
child: MyApp(),
)MorphProvider(
licenseKey: 'morph-free-demo',
baseTheme: AppTheme.lightTheme,
colors: MorphColors(
success: AppColors.success,
warning: AppColors.warning,
),
child: MyApp(),
)| App theme | Time of day | System preference | Action |
|---|---|---|---|
| Light | Day | None | ✅ Stay light |
| Light | Night | None | 🌙 Generate dark |
| Dark | Day | None | ☀️ Generate light |
| Dark | Night | None | ✅ Stay dark |
| Any | Any | Dark (system) | 🌙 Always dark |
| Any | Any | Light (system) | ☀️ Always light |
Morph detects your app brightness automatically from your colors. You never declare whether your app is light or dark — Morph figures it out.
// From any widget in the tree
final theme = context.morphTheme // ThemeMode
final plan = context.morphPlan // MorphPlan
final settings = context.morphSettings // MorphSystemSettings
// System settings
final brightness = settings.brightness // Brightness
final highContrast = settings.highContrast // bool
final textScale = settings.textScaleFactor // double
final boldText = settings.boldText // bool
final reducedMotion = settings.disableAnimations // boolMorphProvider(
safeMode: true,
child: MyApp(),
)Morph detects everything, applies nothing.
All values still readable via context.morphSettings.
ChameleonReorderableColumn(
zones: [
MorphZone(
id: 'search',
priority: 1,
child: SearchSection(),
),
MorphZone(
id: 'feed',
priority: 2,
child: FeedSection(),
),
MorphZone(
id: 'trending',
priority: 3,
child: TrendingSection(),
),
],
)After enough sessions, Morph reorders zones based on what this user actually uses. Always with their permission. Always with an Undo button.
MaterialApp(
navigatorObservers: [
context.morphNavObserver,
],
home: const HomePage(),
)Morph tracks navigation patterns cross-pages and suggests shortcuts automatically.
User gets a call mid-session. They come back. Morph remembers.
MorphProvider(
licenseKey: 'morph-pro-xxx',
features: MorphFeatures(
interruptionRecovery: true,
),
onResumePosition: (page, depth) {
// Scroll to saved position
_scrollController.animateTo(
_scrollController.position.maxScrollExtent
* depth / 100,
duration: const Duration(milliseconds: 600),
curve: Curves.easeOutCubic,
);
},
child: MyApp(),
)Declare the context so Morph builds the right recovery message:
// In your page
context.morphSetContext(
page: '/checkout',
context: 'checkout',
metadata: { 'total': cart.total },
)Detects left or right hand from the accelerometer. Primary actions reposition toward the thumb automatically. No permission required.
MorphProvider(
features: MorphFeatures(gripDetection: true),
child: MyApp(),
)// In your page — primary action follows the thumb
GripAdaptiveLayout(
child: ProductContent(),
primaryAction: AddToCartButton(),
)Low battery? Morph reduces animations, simplifies the interface, and on OLED screens goes darker. Your app lasts longer. Your user stays.
Charging-aware by default. When the device is plugged in, Morph
keeps the UI in normal mode regardless of battery level — a phone
charging at 12% behaves like one at 95%. No surprise downgrades while
the user is at their desk.
MorphProvider(
features: MorphFeatures(batteryAwareUI: true),
child: MyApp(),
)// Adapt your UI per battery level
BatteryAwareWidget(
normal: FullDashboard(),
medium: SimplifiedDashboard(),
low: EssentialDashboard(),
critical: CriticalDashboard(),
)The interface evolves with the time of day — not just dark/light.
MorphProvider(
features: MorphFeatures(circadianRhythm: true),
child: MyApp(),
)Tap accuracy drops. Typing slows. Morph detects fatigue and simplifies the interface to what matters right now.
MorphProvider(
features: MorphFeatures(
fatigueCognitiveDetection: true,
),
child: MyApp(),
)// Form adapts automatically
FatigueAdaptiveForm(
normalFields: allFormFields,
simplifiedFields: essentialFieldsOnly,
submitButton: SubmitButton(),
)Walking → bigger text. In a vehicle → essential only. Stationary → full interface. Uses your existing GPS feed. Zero extra permissions.
Robust to bad fixes. Updates with accuracy > 50m are dropped —
indoor / urban-canyon noise won't flip the UI to "stationary" while the
user is actually mid-trip.
Tunnel-tolerant. When the signal degrades for up to 30 seconds the adapter holds the last known context — going through a tunnel or under a bridge no longer drops the user back to "unknown".
Hysteresis on transitions. Mode changes use asymmetric thresholds (e.g. walking→cycling at 7 km/h but cycling→walking only at 5 km/h), so a steady speed at the boundary doesn't oscillate the UI.
Accelerometer-aware. When GPS reports stationary but the
accelerometer shows sustained train-like vibration, the adapter
upgrades to vehicle — useful in metros and trains where the GPS
loses lock for minutes at a time.
MorphProvider(
features: MorphFeatures(gpsContext: true),
child: MyApp(),
)
// In your location service — pass speed to Morph
context.morphGpsAdapter.onLocationUpdate(
speedKmh: position.speed * 3.6,
accuracy: position.accuracy,
)MorphProvider(
licenseKey: 'morph-pro-xxx',
// React preset — ecommerce
features: MorphFeatures.ecommerce(),
// React preset — fintech / field apps
features: MorphFeatures.fintech(),
// Or configure manually
features: MorphFeatures(
interruptionRecovery: true, // Pro
gripDetection: true, // Pro
batteryAwareUI: true, // Pro
circadianRhythm: true, // Pro
fatigueCognitiveDetection: false, // Agency
gpsContext: false, // Agency
),
child: MyApp(),
)Morph observes behavior locally and surfaces suggestions to the user at the right moment — never during a scroll, never during typing.
Each suggestion has two buttons:
[Not now] [Action]
- Not now → never ask again for 7 days
- Action → execute immediately with an Undo notice
- Refused 3 times → never shown again
Suggestions are generated locally — no network call needed.
Examples:
- "You always go to Search after Home. Want a shortcut?"
- "You read content carefully. Enable reading mode?"
- "Your battery is low. Switch to essential view?"
- "You often use the app at night. Enable dark mode automatically?"
All behavioral data stays on device by default. Enable anonymous reporting only with explicit user consent.
MorphProvider(
licenseKey: 'morph-agency-xxx',
analytics: MorphAnalyticsConfig(
enabled: true,
userConsent: _userHasConsented,
onConsentRequired: () => showConsentDialog(),
retentionDays: 30, // max 30 — enforced
),
child: MyApp(),
)What is sent — anonymized aggregates only:
- Zone scores (0.0 to 1.0) — not individual clicks
- Confirmed navigation sequences
- Scroll behavior summary
- App hash (non-reversible)
- Month only — not exact date
Never sent: clicks, timestamps, user identity, device fingerprint, app content, location data.
| Platform | Status |
|---|---|
| iOS 12+ | ✅ |
| Android 6+ | ✅ |
| ThemeData | ✅ |
| AppColors files | ✅ |
| Material 3 | ✅ |
| Cupertino style | ✅ |
| Provider | ✅ |
| Riverpod | ✅ |
| Bloc / Cubit | ✅ |
| GetX | ✅ |
| Flutter Web | 🔜 |
| Free | Pro | Agency | |
|---|---|---|---|
| Price | $0 | $19/mo | $49/mo |
| API calls/day | 50 | 2,000 | Unlimited |
| License keys | 1 | 5 | Unlimited |
| Dark mode + Accessibility | ✅ | ✅ | ✅ |
| AI theme generation | ✅ | ✅ | ✅ |
| Interruption recovery (basic) | ✅ | ✅ | ✅ |
| Behavioral intelligence V2 | — | ✅ | ✅ |
| Grip detection | — | ✅ | ✅ |
| Battery-aware UI | — | ✅ | ✅ |
| Circadian rhythm | — | ✅ | ✅ |
| Fatigue detection | — | — | ✅ |
| GPS context UI | — | — | ✅ |
| Analytics dashboard | — | — | ✅ |
| AI recommendations | — | — | ✅ |
One license key works on both React and Flutter. No extra cost.
Dark theme not matching brand
Pass your AppColors via MorphColors(). Morph reads your exact palette
and generates the opposite theme from it — never a generic dark.
Theme not updating when system changes
Make sure your MaterialApp is inside MorphProvider — not the other way around.
Grip detection not working
Check that sensors_plus is in your pubspec.yaml and that
MorphFeatures(gripDetection: true) is set.
Analytics not sending
Both enabled: true AND userConsent: true are required.
One alone is not enough.
- Automatic dark / light theme from ThemeData or AppColors
- WCAG AA — guaranteed, not hoped for
- System preferences — all of them
- AI-powered theme generation
- iOS and Android
- Zone tracking and reordering
- Navigation pattern detection
- Interruption Recovery
- Grip Detection
- Battery-aware UI
- Circadian Rhythm UI
- Suggestions system with permission
- Fatigue Detection (Agency)
- GPS Context UI (Agency)
- Analytics dashboard (Agency)
- AI-powered recommendations (Agency, opt-in)
- Flutter Web support
- Cross-platform insights with React SDK
morphui.dev · Docs · Dashboard · pub.dev · React SDK
Every user deserves an interface made for them.
Apache License 2.0