FLUTTER: Flutter UI — Styles, Navigation, and Routing

A polished Flutter app requires consistent theming, intuitive gesture handling, and well-structured navigation. This covers styling with TextStyle and ThemeData, handling user gestures, and implementing the three core navigation patterns — stack, tab, and drawer — with both named and direct routing.

Building a great Flutter UI goes beyond placing widgets on screen. It requires a coherent visual language applied consistently through themes, smooth response to user gestures, and a clear navigation structure that guides users through the app without confusion.


Styles

Styles control the visual appearance of widgets. Flutter separates visual properties from structural ones, making it easy to apply consistent styling across the entire app.

TextStyle

TextStyle applies fine-grained control over the appearance of text in any widget that renders a string.

dart
Text(
  'Hello, Flutter!',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
    letterSpacing: 1.2,
    fontStyle: FontStyle.italic,
  ),
)

Common TextStyle properties:

PropertyTypeDescription
fontSizedoubleText size in logical pixels
fontWeightFontWeightbold, normal, w500, etc.
colorColorText color
letterSpacingdoubleSpace between characters
decorationTextDecorationUnderline, strikethrough, etc.

Theme

Themes apply consistent styling across an entire application without repeating style definitions in every widget. Define a ThemeData instance in MaterialApp and widgets automatically inherit its values.

dart
MaterialApp(
  theme: ThemeData(
    primarySwatch: Colors.blue,
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
    textTheme: const TextTheme(
      bodyMedium: TextStyle(fontSize: 16, color: Colors.black87),
      headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
      ),
    ),
  ),
  home: const MyHomePage(),
)

Access the active theme anywhere in the widget tree:

dart
final theme = Theme.of(context);
Text('Themed text', style: theme.textTheme.headlineMedium)


User Interaction

Flutter handles two broad categories of user interaction: input widgets and gesture detectors.

Input Widgets

Buttons interact through the onPressed callback. Text fields use a TextEditingController to read and manipulate the entered text.

dart
final TextEditingController _emailController = TextEditingController();

TextField(
  controller: _emailController,
  keyboardType: TextInputType.emailAddress,
  decoration: const InputDecoration(
    labelText: 'Email address',
    hintText: 'user@example.com',
    border: OutlineInputBorder(),
  ),
)

Gesture Detection

For gestures beyond simple button taps — swipes, long presses, drags — Flutter provides dedicated gesture widgets.

md
Gesture Detection Widgets
--------------------------
GestureDetector    --> Low-level, full gesture access
InkWell            --> Tap with Material ripple effect
Dismissible        --> Swipe to dismiss (list items)

dart
// GestureDetector — detect any gesture on any widget
GestureDetector(
  onTap: () => print('Tapped'),
  onLongPress: () => print('Long pressed'),
  onDoubleTap: () => print('Double tapped'),
  onHorizontalDragEnd: (details) => print('Swiped'),
  child: Container(
    color: Colors.blue,
    width: 100,
    height: 100,
  ),
)

// InkWell — tap with ripple animation
InkWell(
  onTap: () => print('Tapped with ripple'),
  child: const Padding(
    padding: EdgeInsets.all(16),
    child: Text('Tap me'),
  ),
)


Navigation is the process of moving between screens (also called routes or pages) in a Flutter app. Flutter uses a Navigator — a widget that manages a stack of pages — as its core navigation primitive.

md
Navigation Patterns
-------------------
Stack Navigation   --> Linear flow, push/pop screens
Tab Navigation     --> Parallel sections, switch instantly
Drawer Navigation  --> Side menu, app-wide destinations


Stack Navigation

Stack navigation behaves like a stack of cards. When you navigate forward, a new screen is pushed onto the stack. When the user goes back, the top screen is popped off.

md
Screen Stack (Stack Navigation)
--------------------------------
+------------------+
|   Detail Screen  |  <-- Currently visible (top of stack)
+------------------+
|   List Screen    |
+------------------+
|   Home Screen    |  <-- Bottom of stack
+------------------+

dart
// Push a new screen onto the stack
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const DetailScreen()),
);

// Pop the current screen off the stack (go back)
Navigator.pop(context);

// Replace the current screen (no back button)
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => const NewScreen()),
);

Tab Navigation

Tab navigation enables users to switch between parallel sections of the app without losing their state in each section.

dart
DefaultTabController(
  length: 3,
  child: Scaffold(
    appBar: AppBar(
      title: const Text('Tab Navigation'),
      bottom: const TabBar(
        tabs: [
          Tab(icon: Icon(Icons.home), text: 'Home'),
          Tab(icon: Icon(Icons.search), text: 'Search'),
          Tab(icon: Icon(Icons.person), text: 'Profile'),
        ],
      ),
    ),
    body: const TabBarView(
      children: [
        HomeTab(),
        SearchTab(),
        ProfileTab(),
      ],
    ),
  ),
)

Drawer Navigation

The Drawer widget slides in from the side of the screen, providing access to app-wide navigation destinations.

dart
Scaffold(
  appBar: AppBar(title: const Text('My App')),
  drawer: Drawer(
    child: ListView(
      children: [
        const DrawerHeader(
          decoration: BoxDecoration(color: Colors.blue),
          child: Text('Menu', style: TextStyle(color: Colors.white, fontSize: 24)),
        ),
        ListTile(
          leading: const Icon(Icons.home),
          title: const Text('Home'),
          onTap: () => Navigator.pushReplacementNamed(context, '/'),
        ),
        ListTile(
          leading: const Icon(Icons.settings),
          title: const Text('Settings'),
          onTap: () => Navigator.pushReplacementNamed(context, '/settings'),
        ),
      ],
    ),
  ),
  body: const Center(child: Text('Main content')),
)


Routing

Routing defines how your app maps navigation events to screens. Flutter provides two routing approaches: named routes and direct routes.

Named Routes

Named routes define a centralized map of route names to screen builders. This keeps navigation logic in one place and improves readability.

dart
void main() {
  runApp(MaterialApp(
    initialRoute: '/',
    routes: {
      '/': (context) => const HomeScreen(),
      '/products': (context) => const ProductListScreen(),
      '/settings': (context) => const SettingsScreen(),
    },
  ));
}

// Navigate using the route name
Navigator.pushNamed(context, '/products');

Direct Routes

Direct routes create the target widget inline at the call site. This is simpler for small apps or screens that require constructor parameters.

dart
// Push with data passed to the target screen
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProductDetailScreen(productId: '42'),
  ),
);

// Return data from a screen back to the caller
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const SelectionScreen()),
);

Platform-Specific Route Transitions

Flutter provides two built-in page route classes that produce platform-native transition animations:

Route ClassTransition StylePlatform
MaterialPageRouteSlide up from bottomAndroid
CupertinoPageRouteSlide in from rightiOS

dart
Navigator.push(
  context,
  CupertinoPageRoute(builder: (context) => const DetailScreen()),
);


  • Use named routes for apps with many screens to centralize navigation logic
  • Use direct routes when passing complex objects to the next screen
  • Prefer pushReplacement for login flows where the back button should not return to the previous screen
  • Use Navigator.popUntil to return multiple levels up the stack at once
  • Consider dedicated routing packages like GoRouter or AutoRoute for large apps with complex navigation requirements