Skip to content

Toolbar

A bar of controls: an application header, a page's action row, the strip along the bottom of an editor.

tsx
import { Toolbar } from 'neba';

<Toolbar render={<header />} start={<Logo />} end={<Button>Deploy</Button>}>
  Workspace
</Toolbar>;

Three slots and a row. start and end are pinned to their ends and children takes what is left, which is the arrangement every toolbar has ever had — so it is laid out here rather than left to a caller and a spacer <div> they have to remember.

Props

PropTypeDefaultDescription
variantshared'solid' | 'outline' | 'text''outline'Weight of the bar, said the way a container says it — the sheet is never dyed, because a toolbar holds other people’s controls and those arrive with colours of their own
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The scale of the padding and the radius. Not a height: a toolbar is as tall as the controls in it plus its padding
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
densityshared'default' | 'compact''default'Padding only — which is why there is no separate `dense` prop meaning the same thing
elevationshared0 | 1 | 2 | 30Drop shadow depth. `0` even when the bar is pinned: a shadow under a header says "there is content beneath this", and that is only true once the page has been scrolled
positionshared'static' | 'sticky' | 'fixed''static'How the bar sits in the page’s scroll. `sticky` takes up its own space and stops at the edge, so nothing underneath has to be padded around it; `fixed` leaves the flow entirely, so the page needs padding of its own
sideshared'top' | 'bottom''top'Which edge it is held against when `position` is not `static`
dividerbooleanfalseDraws a hairline along the edge that faces the content — under a `top` bar, over a `bottom` one
startReactNodePinned to the start of the bar: a logo, a title, a back button
endReactNodePinned to the end: the actions
renderuseRender.RenderPropRenders something other than a div (render={<header />}). Base UI's own escape hatch
childrenReactNodeThe middle. Takes whatever width `start` and `end` leave

Every native <div> attribute passes through.

Examples

Density

A Toolbar has no height of its own. It is as tall as the controls in it plus its padding, and that padding is the size / density pair every other surface uses — so density="compact" gives you the dense bar without a second prop meaning the same thing, and without the type scale moving.

Pinned

position is CSS's own three values, spelled the way CSS spells them.

  • sticky is what an application header usually wants: it takes up its own space, so nothing underneath has to be padded around it.
  • fixed leaves the flow entirely, so the page needs padding of its own or the first screenful sits behind the bar.

A pinned bar drops its radius, because a rounded corner against the edge of the screen is a gap with nothing behind it. divider gives it the hairline that says there is content beneath.

elevation stays 0 even when the bar is pinned. A shadow under a header is a way of saying "there is content beneath this", and that is only true once the page has been scrolled — so raise it yourself on scroll, or leave it flat and turn on divider.

Why there is no role="toolbar"

That role is a promise about keyboard behaviour: one tab stop for the whole bar, arrow keys between the controls in it. A bar that claims it without implementing it is worse for a keyboard reader than one that never claimed anything.

What a page header wants is render={<header />} — a real landmark, which is the thing a screen reader user will actually navigate by. What a genuine roving-focus toolbar wants is a ButtonGroup, which is one.

Coming from Material UI

MUINeba
<AppBar><Toolbar>…</Toolbar></AppBar>One component. position is on it directly
position="sticky"The same. 'static', 'sticky', 'fixed'
variant="dense"density="compact" — padding, which is all density is ever allowed to change
color="primary"color reaches the hairline and the focus ring. A bar that holds other people's controls does not dye its own sheet
elevation={4}elevation is 03, and 0 means no shadow at all
<Box sx={{ flexGrow: 1 }} /> as a spacerNot needed. start, children and end are the three slots

Released under the MIT License