Small details that build taste in Flutter.

curated by Kamran BekirovKamran Bekirov

Update browser tab title on each page

Your web app's title shows in the tab, history, bookmarks, and autocomplete bar. Leave it the same on every page and they all look alike, so a user with several tabs open can't tell them apart.

Watch Serverpod Console update the tab title as you move between pages:

The tab title is whatever the nearest Title widget above the current page says. Wrap a page in one and that page owns its tab title:

@override
Widget build(BuildContext context) {
  return Title(
    title: 'Billing · Serverpod Cloud',
    color: Theme.of(context).colorScheme.surface,
    child: Scaffold(...),
  );
}

Doing this on every page is easy to forget, and the titles scatter across the app. Wrap once instead, in the single place every page flows through: MaterialApp's builder. Its child is the active page. A fixed string here would repeat one title everywhere, so the wrapper reads the current route:

MaterialApp.router(
  routerConfig: goRouter,
  builder: (context, child) {
    return RouteTitle(
      child: child!,
    );
  },
)

This uses go_router, adapt it to your own routing state. RouteTitle watches the global goRouter and rebuilds the Title on every navigation:

class RouteTitle extends StatelessWidget {
  final Widget child;
  
  const RouteTitle({
    super.key,
    required this.child,
  });
  
  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: Listenable.merge([
        goRouter.routerDelegate,
        goRouter.routeInformationProvider,
      ]),
      builder: (context, _) {
        final String path = goRouter.routeInformationProvider.value.uri.path;
  
        return Title(
          title: titleForPath(path),
          color: Theme.of(context).colorScheme.surface,
          child: child,
        );
      },
    );
  }
}

titleForPath is yours. Map the path to a readable name, with a fallback so the tab is never blank:

String titleForPath(String path) {
  final List<String> segments = Uri.parse(path).pathSegments;
  final String? page = switch (segments) {
    ['billing'] => 'Billing',
    ['project', final String id, 'insights'] => 'Insights · $id',
    ['project', final String id] => id,
    _ => null,
  };
  
  return page == null ? 'Serverpod Cloud' : '$page · Serverpod Cloud';
}

Two notes. Title.color is required and must be opaque (alpha 0xFF); it feeds the Android task switcher and is ignored on web. And onGenerateTitle may look like the built-in fix, but it only reruns on app rebuild, not navigation, so it can't track routes.

Kamran Bekirov
Kamran Bekirov

Want this level of care in your Flutter apps?

Work With Me