# `bottom_tabs`

| Package | Kind | Provides the role | Needs | Choose with |
| --- | --- | --- | --- | --- |
| [`smf_bottom_tabs`](https://pub.dev/packages/smf_bottom_tabs) | layout | Layout | a router | `-m bottom_tabs` |

`bottom_tabs` shows the main navigation of the app as tabs in a bar at the bottom. Features declare which of their routes are destinations of the main navigation, each with a label and an icon, such as Home with the home icon, and this module generates the widget around them.

## What it adds to the app

The module adds `lib/core/layout/app_shell.dart` with `AppShell`, a `Scaffold` whose body is the screen of the selected destination. Below the screen is a bar with a tab for each destination: its icon over its label, below a hairline. The file draws the bar itself, with the widgets `_TabBar` and `_Tab`, in the colors of the theme of the app, so the file is where you change the look of the bar. The module adds no package to the app.

- The selected tab differs from the others only in its color, which it takes over a moment. No tab changes its size, and the bar has no indicator behind the selected icon.
- A tap on a tab selects its destination, with the tick of a selection on the device when it is another tab. The screen of the new destination fades in while it rises a little.
- A label is a text of the feature of its tab, so the tabs follow the language of the app.
- With a single destination there is nothing to switch to, so the bar is hidden and the screen fills the app.

![The bar below the start screen in the light theme: the tabs Home and Settings, each an icon over its label, below a hairline. Home is the selected tab, in green.](https://doc.saymyframe.com/demo/0.8/app_4_home.png)
![The bar below the settings screen in the dark theme, in Ukrainian: the labels of the tabs are in Ukrainian, and the second tab is the selected one.](https://doc.saymyframe.com/demo/0.8/app_8_settings_dark_uk.png)

The layout role adds `lib/core/layout/destination.dart`, whichever module provides the layout. It has `Destination`, the icon of an item and a function that returns its label, and `appDestinations`, the destinations of the app. A tab calls `destination.label(context)` each time it builds and keeps nothing of what it returned, so a label is in the language that the app is in. The role also requires every layout to keep `destinations`, `currentIndex` and `onSelect` of its `AppShell` public, so a test of the app can find a destination by its label and icon, select it with `onSelect` and read the selected one from `currentIndex` without knowing the module.

In the [guide for coding agents](https://doc.saymyframe.com/guides/coding-agents.md) of the app, the layout role describes the shell and the destinations under "Layout". The module adds that it draws the bar itself and hides it while the app has fewer than two destinations, to keep to five, where the bar takes its colors from, and what a tab says to a screen reader.

## Change the look of the bar

The bar is not the `NavigationBar` of Material, but it takes its colors where that bar does: from the navigation bar theme of the app, `NavigationBarTheme.of(context)`. For a color that the theme does not set, it reads the color scheme:

| Part of the bar | From the navigation bar theme | Otherwise, from the color scheme |
| --- | --- | --- |
| The icon and the label of the selected tab | The color of `iconTheme` for a selected destination | `primary` |
| The icon and the label of the other tabs | The color of `iconTheme` | `onSurfaceVariant` |
| The background of the bar | `backgroundColor` | `surface` |
| The hairline above the bar | | `outlineVariant` |

So for other colors, set them in the `navigationBarTheme` of the theme of the app. With [`material_theme`](https://doc.saymyframe.com/modules/material-theme.md#the-components), that is in `lib/core/theme/app_theme.dart`, where the selected tab gets the accent of the theme. The bar reads nothing else of that theme: the height, the indicator and the text style of a label there do not change it. A label has the style `labelMedium` of the text theme.

Everything else is in `app_shell.dart`: the size of an icon, the space between an icon and its label, how long a tab takes to change its color, and how the screen of a destination comes in. Two constants at the top of the file hold the height of the bar and the limit of the text size of a label:

```dart title="lib/core/layout/app_shell.dart"
/// The least height of the bar above the inset of the device. The bar grows
/// when a label needs more.
const double _barHeight = 64;

/// The most that the text size of the device scales the label of a tab. A
/// long press on a tab shows its label in a tooltip, at the full text size.
const double _maxLabelScale = 1.3;
```

## Large text, screen readers and less motion

The bar gives what a navigation bar of Flutter would give:

- The text size of the device scales a label up to 1.3 times. A label keeps to one line and ends with an ellipsis when its tab is too narrow for it, and a long press on a tab shows the label in a tooltip at the full text size. The bar is at least 64 high above the inset of the device, and grows when a label needs more.
- Each tab is one button for a screen reader, with the label of its destination, selected or not. What you add to a tab, such as a badge, is not read unless the `Semantics` of the tab says it.
- For a user who asks the device for less motion, the tab and the screen change at once.

## How the router uses it

The module that provides the router builds the main navigation around the shell, with a branch for each destination in the order of the features. Each branch keeps its stack while another tab is selected. The app opens on the tab of its start screen, and an app without destinations has no main navigation. See [Navigation](https://doc.saymyframe.com/guides/navigation.md#tabs-and-other-main-navigation).

## Limits

The bar shows at most five destinations, as the Material Design guidelines advise. A feature whose destination does not fit is left out of the app with a warning, and with `--strict`, `smf create` stops instead.

## Choosing it

`smf create` asks which module provides the layout before it asks about the router, and offers None as well. To choose it without the question:

```bash
smf create my_app -m home,bottom_tabs
```

The layout requires a router, which `smf create` adds when only one module provides it.
