Widget Field Guide

Explore Flutter widgets for Android and iOS. Pick a widget, run its live mobile preview, and copy the Dart code.

LIVE MOBILE PREVIEW

WelkBottomNavBar

Comic-styled bottom navigation for 2-5 destinations, with five layouts, floating mode and a state-preserving app scaffold.

Every WelkUI Flutter widget supports dark mode, including this one. Learn how to enable or disable it.

Install

Availability and installation

Bottom navigation is introduced in the 0.1.9 API. Add the dependency below to your app's pubspec.yaml, then run flutter pub get.

YAML
dependencies:
  flutter:
    sdk: flutter
  welkui_flutter: ^0.1.9

No React package or stylesheet is needed.

Latest release: welkui_flutter 0.1.9 on pub.dev. Requires Dart 3.13.4+ and Flutter 3.35.0+.

Usage

Dart
import 'package:flutter/material.dart';
import 'package:welkui_flutter/welkui_flutter.dart';

class BottomNavExample extends StatefulWidget {
  const BottomNavExample({super.key});

  @override
  State<BottomNavExample> createState() => _BottomNavExampleState();
}

class _BottomNavExampleState extends State<BottomNavExample> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBody: true,
      body: IndexedStack(
        index: _index,
        children: const [
          Center(child: Text('Home')),
          Center(child: Text('Create')),
          Center(child: Text('Profile')),
        ],
      ),
      bottomNavigationBar: WelkBottomNavBar(
        currentIndex: _index,
        onTap: (index) => setState(() => _index = index),
        items: const [
          WelkBottomNavItem(
            icon: Icon(Icons.home_outlined),
            activeIcon: Icon(Icons.home),
            label: 'Home',
          ),
          WelkBottomNavItem(icon: Icon(Icons.add), label: 'Create'),
          WelkBottomNavItem(icon: Icon(Icons.person_outline), label: 'Profile'),
        ],
      ),
    );
  }
}

Variants and behavior

  • Layouts: popCircle (default), blockCenter, popBlock, comicDock and bubbleRail.
  • Modes: defaultMode anchors to the bottom; floating adds margins, rounded corners and shadows.
  • popCircle, blockCenter and popBlock feature the middle item with 3 or 5 destinations; 2 or 4 render at regular size.
  • WelkBottomNavScaffold manages pages, nested navigation, transitions and automatic visibility. The standalone bar only reports taps.

Usage notes and accessibility

  • Available since 0.1.9. Upgrade welkui_flutter if your installed version does not contain these widgets.
  • Supply 2-5 destinations and keep currentIndex within the item range. The standalone bar does not build pages or change selection for you.
  • Use Scaffold.extendBody: true for pop-out layouts and floating mode. The bar handles the bottom safe area; do not wrap it in another SafeArea.
  • Items expose button semantics, selected state and position. semanticLabel defaults to label, so keep labels meaningful even when visually hidden.
  • Enter/Space activate focused items; focus rings and hover highlights support desktop/web previews. Touch targets are at least 48 logical pixels tall.
  • RTL mirrors layouts and horizontal page transitions. Reduced-motion requests make indicator, visibility and page transitions instant.
  • Labels ellipsize on one line and text scaling is capped at 1.3x. Test long translated labels, TalkBack and VoiceOver.

Choose the bar or the app scaffold

Use WelkBottomNavBar when you already manage pages, routing and selection. Its onTap callback includes taps on the selected item; update currentIndex yourself.

Recommended for a complete app shell: WelkBottomNavScaffold owns one nested navigator per destination, preserves page state and manages transitions and visibility. Both widgets share items, layouts, modes and styles. Do not wrap the scaffold in another Scaffold; each destination can have its own scaffold and app bar.

Dart
WelkTheme(
  data: const WelkThemeData(),
  child: MaterialApp(
    home: WelkBottomNavScaffold(
      items: [
        WelkBottomNavItem(
          icon: const Icon(Icons.home_outlined),
          activeIcon: const Icon(Icons.home),
          label: 'Home',
          builder: (_) => const Scaffold(
            body: Center(child: Text('Home')),
          ),
        ),
        WelkBottomNavItem(
          icon: const Icon(Icons.add),
          label: 'Create',
          builder: (_) => const Scaffold(
            body: Center(child: Text('Create')),
          ),
        ),
        WelkBottomNavItem(
          icon: const Icon(Icons.person_outline),
          label: 'Profile',
          builder: (_) => const Scaffold(
            body: Center(child: Text('Profile')),
          ),
        ),
      ],
    ),
  ),
)

Five layouts

  • popCircle (default): an enlarged middle circle pops above the bar with a hard shadow; selection lifts it and applies the selected fill.
  • blockCenter: a larger square built into the bar, separated by ink outlines. Keeps a flat silhouette without overlapping page content.
  • popBlock: an asymmetric square protrudes above the bar and tilts and lifts on selection, like a peeled sticker.
  • comicDock: bold outlines, hand-drawn corners and a raised, tilted active tile with action lines. All destinations are equal size; best in floating mode.
  • bubbleRail: a pill with a gliding active bubble and two decorative bouncing bubbles. No featured middle destination.

Set layout: WelkBottomNavLayout.popBlock on either widget. The first three layouts feature the second item with 3 destinations or the third with 5. With 2 or 4 items, none is enlarged. The featured item remains enlarged even when another destination is selected.

Anchored and floating modes

WelkBottomNavMode.defaultMode anchors to the bottom, extends its surface into the home-indicator safe area, rounds only the top corners and draws a top outline. It has no elevation by default. WelkBottomNavMode.floating adds outer margins plus the bottom safe area, rounds all corners and draws a full outline, a hard comic shadow and a soft elevation shadow.

Dart
// Reuse the items from the scaffold example.
WelkBottomNavScaffold(
  items: items,
  layout: WelkBottomNavLayout.comicDock,
  mode: WelkBottomNavMode.floating,
  style: const WelkBottomNavStyle(
    margin: EdgeInsets.fromLTRB(24, 0, 24, 16),
    borderRadius: 24,
    elevation: 6,
    width: 480,
  ),
)

Pages, state and nested navigation

Every scaffold item needs a builder. By default, pages are built on their first visit (lazyLoad: true) and retained offstage (keepPagesAlive: true), preserving scroll positions, fields and counters. Inactive pages have tickers paused and are excluded from focus traversal. Set lazyLoad: false to build all pages immediately, or keepPagesAlive: false to dispose pages on leaving and lose their local state.

Each destination keeps its own back stack. Push from a context inside the destination to open a subpage; switching tabs preserves it. System back pops the active destination's subpage first. Hero animations work within a destination. To cover the whole app, including the bar, push onto the root navigator.

Dart
// From a destination page's BuildContext:
Navigator.of(context).push(
  MaterialPageRoute<void>(
    builder: (_) => const Scaffold(
      body: Center(child: Text('Details')),
    ),
  ),
);

// Full-screen flow above the navigation shell:
Navigator.of(context, rootNavigator: true).push(
  MaterialPageRoute<void>(
    fullscreenDialog: true,
    builder: (_) => const Scaffold(
      body: Center(child: Text('Sign in')),
    ),
  ),
);

Named routes

Set onGenerateRoute on an item to resolve named routes for that destination. Push using a context inside its page:

Dart
WelkBottomNavItem(
  icon: const Icon(Icons.home),
  label: 'Home',
  builder: (_) => const HomePage(),
  onGenerateRoute: (settings) => switch (settings.name) {
    '/details' => MaterialPageRoute<void>(
      settings: settings,
      builder: (_) => const DetailsPage(),
    ),
    _ => null,
  },
)

// HomePage and DetailsPage are your app's page widgets.
// Inside HomePage:
Navigator.of(context).pushNamed('/details');

Selection and programmatic control

initialIndex selects the first page (default 0, clamped to range). onIndexChanged reports selection changes and onReselect reports repeat taps, useful for scrolling to the top. By default, reselecting also pops that destination to its root; disable with popToRootOnReselect: false.

Dart
final nav = WelkBottomNavScaffold.of(context);
nav.select(0);       // Includes the normal tap/reselect behavior.
nav.popToRoot();     // Current destination.
nav.popToRoot(2);    // A specific destination.
final index = nav.currentIndex;
final visible = nav.isBarVisible;
final navigator = nav.navigatorOf(1); // Null until built.

// Nullable lookup when the context may be outside the shell:
final optionalNav = WelkBottomNavScaffold.maybeOf(context);

For control from outside its subtree, retain a GlobalKey<WelkBottomNavScaffoldState> in your state, pass it as the scaffold's key, then call navKey.currentState?.select(2).

Page transitions

  • none: instant switch.
  • fade (default): fade in.
  • fadeThrough: fade in while scaling from 94%.
  • slideUp: fade in while rising slightly.
  • slideHorizontal: fade and slide from the direction of travel, mirrored for RTL.

Set transition: WelkBottomNavTransition.slideHorizontal. transitionDuration defaults to style.animationDuration (320 ms) and transitionCurve to Curves.easeOutCubic.

Visibility and scroll behavior

  • sticky (default): stays visible while scrolling, but still honors subpage and keyboard hiding.
  • hideOnScrollDown: hides on downward scrolling and reveals on upward scrolling after scrollSensitivity logical pixels (default 12). Horizontal scroll, overscroll and bounces are ignored. Reaching the top, changing destinations or navigating to/from a subpage resets scroll hiding.
  • alwaysVisible: never hides; overrides both hideOnSubpages and hideWhenKeyboardVisible.

Both hideOnSubpages and hideWhenKeyboardVisible default to true. Opening a nested Details page hides the bar; returning to the root reveals it with the same destination selected. Set hideOnSubpages: false to keep it available on subpages. Hidden bars are removed from hit-testing and accessibility semantics.

Dart
// Floating dock that hides while reading:
WelkBottomNavScaffold(
  items: items,
  layout: WelkBottomNavLayout.comicDock,
  mode: WelkBottomNavMode.floating,
  visibility: WelkBottomNavVisibility.hideOnScrollDown,
  scrollSensitivity: 24,
  transition: WelkBottomNavTransition.fadeThrough,
)

Safe areas and content insets

The scaffold lays pages above the bar in anchored mode (extendBody: false) and behind it in floating mode (extendBody: true). Override explicitly if needed. While visible, the bar's occupied space is exposed through MediaQuery.padding.bottom so standard SafeArea, Scaffold and default ListView padding keep content reachable. The inset animates away as the bar hides.

If you override list padding, include the inset yourself:

Dart
ListView(
  padding: EdgeInsets.fromLTRB(
    16, 16, 16,
    16 + MediaQuery.paddingOf(context).bottom,
  ),
  children: const [
    ListTile(title: Text('Your content')),
  ],
)

For standalone integration, WelkBottomNavBar.layoutExtent(context, itemCount: 3) returns reserved bottom height, excluding pop-out items. Pass the same layout, mode and style as the bar when computing your content padding. Do not add a second safe-area wrapper around the bar.

Items and badges

WelkBottomNavItem reference
ParameterTypeDefaultDescription
iconWidgetRequiredUnselected icon. IconTheme supplies color and size.
labelStringRequiredVisible label and default accessibility label.
activeIconWidget?nullSelected icon; falls back to icon. Changes cross-fade and scale.
builderWidgetBuilder?nullRoot page builder. Required for every scaffold destination; unused by the standalone bar.
onGenerateRouteRouteFactory?nullResolves named routes on the destination's nested navigator.
semanticLabelString?nullOverrides the screen-reader label; otherwise uses label.
badgeWidget?nullSmall widget over the icon's top-end corner, such as an unread count.

Plain Icon widgets inherit the correct color and size; explicit icon colors bypass this. Use a short badge and give the destination a meaningful accessibility label:

Dart
WelkBottomNavItem(
  icon: const Icon(Icons.mail_outline),
  activeIcon: const Icon(Icons.mail),
  label: 'Inbox',
  semanticLabel: 'Inbox, 3 unread messages',
  badge: Container(
    padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
    decoration: BoxDecoration(
      color: const Color(0xFFFF8A80),
      borderRadius: BorderRadius.circular(10),
      border: Border.all(width: 1.5),
    ),
    child: const Text('3', style: TextStyle(fontSize: 10)),
  ),
  builder: (_) => const Scaffold(
    body: Center(child: Text('Inbox')),
  ),
)

Styling and label behavior

All colors are optional and resolve from WelkTheme. In dark mode, the bar uses darkPaper, darkOutline and darkShadow, with selected fills tinted by darkButtonTint and readable foregrounds. Explicit style colors are used unchanged in both modes. Set overRideDarkMode on the scaffold to affect both bar and pages.

Dart
const baseStyle = WelkBottomNavStyle(
  selectedItemColor: Color(0xFF82CAFF),
  featuredItemColor: Color(0xFF8DDD9B),
  height: 60,
  activeItemSize: 56,
  borderRadius: 20,
  labelBehavior: WelkBottomNavLabelBehavior.selectedOnly,
  animationDuration: Duration(milliseconds: 250),
);

final warmerStyle = baseStyle.copyWith(
  selectedItemColor: const Color(0xFFFFB366),
);

For an icon-only rail, choose WelkBottomNavLayout.bubbleRail, floating mode and WelkBottomNavLabelBehavior.hidden. Labels remain available to assistive technology. alwaysShow is the default; selectedOnly animates labels in on selection.

WelkBottomNavStyle reference
ParameterTypeDefaultDescription
backgroundColorColor?theme paperBar surface; adapts to darkPaper in dark mode.
selectedItemColorColor?theme accentFill of active indicators, selected featured item and bubble.
selectedForegroundColorColor?readable on selected fillSelected icon and label color.
unselectedForegroundColorColor?theme ink at 72% opacityInactive icon and label color.
featuredItemColorColor?theme mutedUnselected enlarged middle item fill.
borderColorColor?theme outlineBar and indicator outlines.
borderWidthdouble?theme borderWidth (2)comicDock adds 1 to the theme default.
shadowColorColor?theme shadowHard comic shadow color.
shadowOffsetOffset?theme cardShadowOffset (3, 3)Hard shadow offset; comicDock defaults to (4, 4).
elevationdouble?3 floating; 0 defaultSoft shadow depth under the bar.
heightdouble64Surface height, minimum 48; excludes safe area and pop-out items.
widthdouble?nullMaximum width; null fills available width. Useful on tablets.
marginEdgeInsetsGeometryEdgeInsets.fromLTRB(16, 0, 16, 12)Outer spacing in floating mode; ignored in defaultMode.
paddingEdgeInsetsGeometryEdgeInsets.symmetric(horizontal: 6)Inner spacing around items.
borderRadiusdouble?layout defaultUniform radius; null preserves asymmetric corners or bubbleRail's pill. Anchored mode rounds only top corners.
iconSizedouble24Regular icon size; must be positive. Featured icons are 1.3x larger.
activeItemSizedouble60Featured middle item's diameter/side, minimum 40; controls pop-out height.
labelStyleTextStyle?Short Stack, 12 pxMerged into all labels.
selectedLabelStyleTextStyle?boldMerged over labelStyle for the selected label.
labelBehaviorWelkBottomNavLabelBehavioralwaysShowalwaysShow, selectedOnly (animated) or hidden.
animationDurationDurationDuration(milliseconds: 320)Indicator, icon, color, visibility and default page transition duration.
animationCurveCurveCurves.easeOutBackPop, scaling and bubble glide curve.

WelkBottomNavScaffold constructor parameters

WelkBottomNavScaffold reference
ParameterTypeDefaultDescription
itemsList<WelkBottomNavItem>Required2-5 destinations, each with a builder.
initialIndexint0Initial destination; clamped to the item range.
onIndexChangedValueChanged<int>?nullCalled after the selected destination changes.
onReselectValueChanged<int>?nullCalled when the selected destination is tapped again.
layoutWelkBottomNavLayoutpopCircleVisual layout shared with the standalone bar.
modeWelkBottomNavModedefaultModeAnchored defaultMode or floating.
styleWelkBottomNavStyleWelkBottomNavStyle()Shared visual configuration.
visibilityWelkBottomNavVisibilitystickysticky, hideOnScrollDown or alwaysVisible.
hideOnSubpagesbooltrueHides the bar on pushed destination subpages, unless alwaysVisible.
hideWhenKeyboardVisiblebooltrueHides the bar while the keyboard is open, unless alwaysVisible.
scrollSensitivitydouble12Logical pixels scrolled in one direction before hiding or revealing.
transitionWelkBottomNavTransitionfadenone, fade, fadeThrough, slideUp or slideHorizontal.
transitionDurationDuration?style.animationDurationPage switch duration (320 ms by default).
transitionCurveCurveCurves.easeOutCubicPage switch animation curve.
lazyLoadbooltrueBuild pages on first visit; false builds all immediately.
keepPagesAlivebooltruePreserve visited pages; false disposes them when leaving.
popToRootOnReselectbooltrueReselecting a destination pops its nested navigator to the root.
extendBodybool?true floating; false defaultDraw pages behind the bar instead of laying them out above it.
backgroundColorColor?ThemeData.scaffoldBackgroundColorSurface behind destination pages.
semanticLabelString'Bottom navigation'Accessible name of the bar.
enableHapticFeedbackbooltrueSelection-click haptic feedback on tap.
overRideDarkModebool?nullForce dark/light mode for the bar and pages; null inherits.

WelkBottomNavScaffoldState API

WelkBottomNavScaffoldState reference
ParameterTypeDefaultDescription
currentIndexintRead onlySelected destination.
isBarVisibleboolRead onlyWhether the bar is shown or animating in.
select(int index)void-Select a destination, including normal reselect behavior.
popToRoot([int? index])voidCurrent destinationPop a destination's nested navigator to its root.
navigatorOf(int index)NavigatorState?null until builtAccess a destination's nested navigator.

Try the complete demo

Run the live preview on this page or open the bottom navigation example. Compare every layout in anchored and floating modes, then choose a layout, visibility, transition and label behavior and tap Launch demo app. The full five-destination demo includes preserved state, nested subpages, badges, scroll hiding and theme switching. Native keyboard behavior and haptics should be tested on Android and iOS.

Dark mode

Control all widgets, pickers, and overlays with WelkThemeData.darkMode: set it to true to enable dark mode or false to disable it. When left null (the default), widgets follow Flutter's ThemeData.brightness. To follow the device setting, provide light and dark themes on MaterialApp and use ThemeMode.system.

Place WelkTheme above MaterialApp so open popups and overlays also follow global updates. Rebuild it with data.copyWith(darkMode: true) or data.copyWith(darkMode: false) to switch modes. This example also switches the Material theme to keep the app's scaffold and other Material widgets in sync:

Dart
import 'package:flutter/material.dart';
import 'package:welkui_flutter/welkui_flutter.dart';

class DarkModeExample extends StatefulWidget {
  const DarkModeExample({super.key});

  @override
  State<DarkModeExample> createState() => _DarkModeExampleState();
}

class _DarkModeExampleState extends State<DarkModeExample> {
  WelkThemeData _theme = const WelkThemeData(darkMode: false);

  @override
  Widget build(BuildContext context) {
    return WelkTheme(
      data: _theme,
      child: MaterialApp(
        theme: ThemeData.light(),
        darkTheme: ThemeData.dark(),
        themeMode: _theme.darkMode == true ? ThemeMode.dark : ThemeMode.light,
        home: Scaffold(
          body: Center(
            child: WelkSwitch(
              label: 'Dark mode',
              value: _theme.darkMode == true,
              onChanged: (enabled) {
                setState(() {
                  _theme = _theme.copyWith(darkMode: enabled);
                });
              },
            ),
          ),
        ),
      ),
    );
  }
}

Override a widget

Set overRideDarkMode: true to force dark mode or overRideDarkMode: false to force light mode, regardless of the global setting. Omit it (or pass null) to inherit. Container overrides flow into nested WelkUI widgets, and dialog, drawer, and snackbar show functions accept the same override. Local themes are carried into overlays when opened.

Dart
WelkButton(
  overRideDarkMode: true,
  variant: WelkButtonVariant.secondary,
  onPressed: () {},
  child: const Text('Always dark'),
)

WelkButton(
  overRideDarkMode: false,
  onPressed: () {},
  child: const Text('Always light'),
)

Dark palette and appearance

Neutral surfaces, text, and icons adapt automatically. Semantic variant fills receive a 10% black tint, with black borders and whitish-gray hard shadows. Secondary buttons use charcoal-gray surfaces; ghost buttons stay transparent. QR codes keep a light backing and dark modules for scanning. Built-in avatar portraits switch to white assets in dark mode; custom icons and profile images are not filtered.

Customize the palette through WelkThemeData using darkPaper, darkMuted, darkInk, darkOutline, darkShadow, darkButtonTint, and darkFocus. See the Flutter README's global dark mode guide for the package reference.

Constructor parameters

Required parameters are marked in the default column. Other parameters are optional.

WelkBottomNavBar parameter reference
ParameterTypeDefaultDescription
overRideDarkModebool?nullForce dark (true) or light (false) mode. Null inherits the theme preference.
itemsList<WelkBottomNavItem>Required2-5 destinations. builder is unused by the standalone bar.
currentIndexintRequiredSelected destination index, managed by your application.
onTapValueChanged<int>?nullReceives every tapped index, including the selected destination.
layoutWelkBottomNavLayoutpopCirclepopCircle, blockCenter, popBlock, comicDock or bubbleRail.
modeWelkBottomNavModedefaultModeAnchored defaultMode or floating.
styleWelkBottomNavStyleWelkBottomNavStyle()Shared colors, sizing, labels and animation configuration.
semanticLabelString'Bottom navigation'Screen-reader label for the navigation container.
enableHapticFeedbackbooltrueSelection-click haptic feedback on tap.
Project setup, theming and typography

WelkUI Flutter targets Android and iOS. It uses Dart widgets, not the React package or its CSS stylesheet. Defaults work without a theme wrapper.

Override shared colors and strokes with WelkTheme and immutable WelkThemeData:

Dart
WelkTheme(
  data: const WelkThemeData().copyWith(
    accent: const Color(0xFFFFD166),
    ink: const Color(0xFF202020),
    paper: const Color(0xFFFFFDF5),
  ),
  child: WelkButton(
    onPressed: () {},
    child: const Text('Your colors. WelkUI character.'),
  ),
)

The Short Stack font is bundled and registered by the package, so you don't need to declare font assets or download fonts at runtime. To use it outside the widgets:

Dart
const Text(
  'Made with WelkUI',
  style: TextStyle(
    fontFamily: 'ShortStack',
    package: 'welkui_flutter',
    fontSize: 20,
  ),
)