Kandar Applications ← Back to Kandar Applications
Developer resources

Flutter UI Polish Guide

Three passes that make any Flutter app feel considered: a real theme, readable and accessible screens, and a loading moment that doesn't flash or stall.

seed color
Do this first — everything else builds on it

Theming

If colors and fonts are set inline on individual widgets right now, move them all into one ThemeData. One seed color, passed through ColorScheme.fromSeed, generates an entire accessible palette — primary, surface, error and the rest — so there's no hand-picking ten colors that need to work together.

class AppTheme { static ThemeData light = ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFFF2872B), // one brand color brightness: Brightness.light, ), textTheme: _textTheme, ); }

Spacing works the same way. Define a small scale once, and use it everywhere instead of scattering 16, 15, 18 across files — consistent spacing does more for a polished feel than almost anything else.

class Spacing { static const xs = 4.0, sm = 8.0, md = 16.0, lg = 24.0, xl = 32.0; }

Migrate screen by screen: replace hardcoded Color(0xFF...) and one-off TextStyles with Theme.of(context) references as you touch each file.

  • One seed color, via ColorScheme.fromSeed
  • All text styles come from textTheme, none inline
  • Spacing uses one shared scale, not magic numbers
  • Buttons and cards pull from one shared border radius
  • Dark mode looks acceptable, even before it's pixel-perfect
Make it usable before it looks finished

Readability & tooltips

Wrap any icon-only button in a Tooltip — icon-only AppBar actions, a bare FAB, disabled buttons that should say why they're disabled.

Tooltip( message: 'Delete item', child: IconButton( icon: const Icon(Icons.delete_outline), onPressed: _delete, ), )

For text: keep body copy at 14sp minimum, check contrast against the background (4.5:1 for body text, 3:1 for large text), and never rely on color alone for meaning — pair red or green states with an icon or label too.

Anything visually abbreviated should still read naturally to a screen reader:

Semantics( label: 'Total balance: 4,250 rupees', child: Text('₹4,250', style: ...), )
  • Every icon-only button has a tooltip
  • No body text below 14sp
  • Status uses icon + color, never color alone
  • Long text has real overflow handling, no silent clipping
  • Tap targets are at least 48×48
Do this last, once the theme is settled

Loading screen before home

Keep the flow simple: main() → SplashScreen → (real init work) → HomeScreen. Use the wait to do actual work — precache images, load preferences, check auth state — not just a timer.

Future<void> _init() async { final stopwatch = Stopwatch()..start(); await Future.wait([ _loadPrefs(), _precacheAssets(), ]); final elapsed = stopwatch.elapsedMilliseconds; if (elapsed < 800) { await Future.delayed(Duration(milliseconds: 800 - elapsed)); } if (mounted) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const HomeScreen()), ); } }
The 800ms floor stops a jarring flash on fast devices where init finishes almost instantly.

Flutter apps have a blank gap before Dart even starts — the flutter_native_splash package covers that gap natively, so the logo shows before your Dart splash screen ever mounts.

dev_dependencies: flutter_native_splash: ^2.4.0 # then: dart run flutter_native_splash:create
  • Splash does real init work, not Future.delayed alone
  • Minimum ~800ms display time
  • Uses pushReplacement, so back doesn't return to it
  • Key assets precached before Home renders
  • Native splash configured for the pre-Flutter gap

Suggested order of work

  1. Build AppTheme and wire it into MaterialApp
  2. Sweep screens, replacing hardcoded colors and text styles with theme references
  3. Add tooltips, then fix contrast and text-size issues
  4. Add the splash screen last, once the theme it displays is final