Bottom panel
A bottom panel is a non-modal surface that floats at the bottom of the viewport while the page underneath stays interactive.
import { BottomPanel } from '@balance-web/bottom-panel';A bottom panel floats above the bottom edge of the viewport and is used for contextual actions, most commonly bulk actions on a selection. It is deliberately blank: the panel owns placement, surface styling, elevation and the show/hide transition, and the consumer places any content inside it.
How it differs from a bottom drawer
Bottom drawer is a modal dialog. It renders a blanket over the page, traps focus, locks page scroll and closes on Escape. A bottom panel does none of these things:
- The page remains fully interactive. Users can keep selecting rows, paging and filtering while the panel is visible.
- Nothing is announced as a dialog. The panel is a named
regionlandmark. - There is no built in close behaviour. The consumer decides when the panel shows and hides.
- The panel is as tall as its content and, by default, as wide as its content.
Reach for a bottom drawer when the task needs the user's full attention. Reach for a bottom panel when the content supports what the user is already doing on the page.
Showing and hiding
The consumer owns the open state and passes it through the isOpen prop. The panel mounts and slides in when isOpen becomes true, and slides out and unmounts when it becomes false. There is no separate controller component.
const [selectedIds, setSelectedIds] = useState([]);
return (
<BottomPanel isOpen={selectedIds.length > 0} aria-label="Bulk actions">
{/** ... panel content goes here */}
</BottomPanel>
);Because the panel is blank, there is no state inside it to reset on close. If your content holds state that should clear when the panel hides, key it or conditionally render it yourself.
Accessibility
The panel renders as a region landmark, so the aria-label prop is required. Use a short noun phrase that describes what the panel is for, such as "Bulk actions" or "Selected transactions".
The panel never takes focus away from the page. If the panel appears in response to a user action, keep focus where the user was working and let them move into the panel when they are ready.
Width and placement
By default the panel is as wide as its content, horizontally centred, and kept a fixed gutter away from the viewport edges.
maxWidth
Use maxWidth to cap the width of the panel. Content that would make the panel wider than this wraps or scrolls according to how you lay it out.
<BottomPanel isOpen={isOpen} aria-label="Bulk actions" maxWidth={960}>
{/** ... */}
</BottomPanel>minWidth
Use minWidth to stop the panel shrinking below a floor when its content is narrow, for example to keep it in step with the table it acts on. The panel is fixed to the viewport and cannot scroll, so keep the floor below the narrowest viewport you support or its left edge is cut off there.
<BottomPanel isOpen={isOpen} aria-label="Bulk actions" minWidth={720}>
{/** ... */}
</BottomPanel>offset
The panel is centred within the full viewport width. When the app has a persistent sidebar, pass offset.left so the panel is centred within the content column instead. offset.bottom raises the panel above a sticky footer.
<BottomPanel
isOpen={isOpen}
aria-label="Bulk actions"
offset={{ left: 90, bottom: 24 }}
>
{/** ... */}
</BottomPanel>Composition
The panel applies no padding. Compose the inside with layout primitives, and keep the horizontal padding consistent with other overlays.
<BottomPanel isOpen={isOpen} aria-label="Bulk actions">
<Box paddingX="large" paddingY="medium">
<Inline gap="large" alignY="center">
{/** ... */}
</Inline>
</Box>
</BottomPanel>Overlays opened from inside the panel, such as comboboxes and menus, stack above it.
Recipes
Bulk actions
The panel appears as soon as one row is selected and disappears when the selection is cleared. Notice that the list stays interactive while the panel is visible.
Minimal
A plain toggle to show the enter and exit transition.
Constrained width beside a sidebar
maxWidth caps the panel and offset.left centres it within the content column of a page that has a fixed sidebar.