Mobile & screens

The fixed shape every app wears — the frame the host draws

Everything that opens on Okta — platform screens and your mini-app alike — sits in one frame: a dark header 56 tall in both modes, the page below it with two rounded top corners on that same dark floor, a solid bottom bar pinned to the bottom edge at 72, and one open animation where the card you tapped becomes the screen.

And you publish nothing for it. The host draws the frame around you, and what changed inside okta_kit is the bodies of existing methods with not one signature moving — no new injected symbol, no new contract, and do not raise minContract. The app you have published today takes the shape on its first open on a host that carries it.

What the host draws around you

Layer Drawn by Note
The dark header (56) host and OktaAppBar lands on it colour for colour
The two rounded corners under it host it measures where your bar ends and clips the page below
The fixed header controls (language, theme, avatar) host a mini-app never draws these
The open animation host from the tapped card to the screen
Your app's canvas you in OktaPalette colours

The clip is conditional on your app having a bar: a Scaffold with no appBar gets no joint under it and no corners. If you want the frame, give your screen a bar.

Four lines, and that is your whole share

1 — OktaAppBar, not a raw AppBar in your own colours.

Scaffold(
  appBar: OktaAppBar.build("Today's attendance", palette), // or withActions / buildWithIcon
  body: content,
)

And if you build your own bar for some reason, take its two colours from the methods instead of writing the number yourself:

AppBar(
  backgroundColor: OktaAppBar.barBackground(palette),
  foregroundColor: OktaAppBar.barForeground(palette),
  ...
)

The header is dark in both modes and does not flip with the theme — that is the fixed shape itself. And palette.textStrong on that header is nearly black in light mode, i.e. ink that cannot be read on a dark strip; that is why barForeground exists.

2 — the bottom bar is OktaTabs.bottomBar, and that is the platform bar now.

Scaffold on this runtime accepts appBar, body and floatingActionButton and accepts no bottomNavigationBar — so the pinned bar is the last child of a Column whose body is Expanded:

final labels = <String>['Home', 'Reports'];
final icons = <IconData>[OktaIcons.home(), OktaIcons.chart()];

final rows = <Widget>[];
rows.add(Expanded(flex: 1, child: content));          // your screen body
rows.add(OktaTabs.bottomBar(labels, icons, _index, _onSelect, palette));

return Scaffold(
  appBar: OktaAppBar.build("Today's attendance", palette),
  body: Column(children: rows),
);

Write flex: 1 out. An omitted named argument arrives null on this runtime and is cast to int, so Expanded(child: …) on its own dies on the device — exactly the kind of failure reading the code never catches.

The bar comes out solid and flush: 72 tall, a hairline along its top, a 28×3 indicator above the selected tab rather than a fill behind it, icon 24 and label 11 — the host's own bar, cell for cell. bottomBarWithDots adds an unread dot to any tab flagged true.

3 — do not draw the corners or the header yourself. BoxDecoration(borderRadius:) is dropped by the bridge anyway, and a frame drawn per app is the thing one frame exists to prevent.

4 — and do not raise minContract. There is no new symbol here, and raising it with no need turns away devices your app was working on.

floatingBar stays, and is no longer the platform silhouette

If your app uses OktaTabs.floatingBar (contract 18) it keeps working and will not break — nothing was removed. But it is no longer the platform's shape: a floating bar leaves a band of the floor showing beneath it on wide windows, which is what pushed the unified shape to pin the bar to the edge and let the page run into it.

So if you want your app to read like the rest of the platform, switch the call to bottomBar with the composition above — same arguments, and floatingOver and floatingReserve go with it.

View the whole guide on one page · ↧ Raw Markdown