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_kitis the bodies of existing methods with not one signature moving — no new injected symbol, no new contract, and do not raiseminContract. 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.