ActionBar
freeActionBar keeps bulk actions close to selected records without occupying the page at rest.
@floating-ui/react
Preview
Dark
import { useState } from 'react';
import ActionBar from '@/components/ui/ActionBar';
import Avatar from '@/components/ui/Avatar';
import Button from '@/components/ui/Button';
import Checkbox from '@/components/ui/Checkbox';
const contacts = [
{
id: 'alice',
name: 'Alice Smith',
handle: '@alicesmith',
avatar: '/img/avatars/thumb-1.jpg',
},
{
id: 'bob',
name: 'Bob Johnson',
handle: '@bobjohnson',
avatar: '/img/avatars/thumb-2.jpg',
},
{
id: 'clara',
name: 'Clara Garcia',
handle: '@claragarcia',
avatar: '/img/avatars/thumb-3.jpg',
},
];
export default function UsageDemo() {
const [selected, setSelected] = useState<string[]>(['bob']);
const selectedLabel = selected.length === 1 ? 'contact' : 'contacts';
return (
<div className="flex justify-center p-4">
<Checkbox.Group
className="w-fit max-w-sm gap-4 mt-8"
value={selected}
vertical
onChange={(nextValue) => setSelected(nextValue as string[])}
>
{contacts.map((contact) => (
<Checkbox
key={contact.id}
aria-label={`Select ${contact.name}`}
className="items-center gap-2 font-normal"
value={contact.id}
>
<span className="inline-flex min-w-0 items-center gap-2">
<Avatar
alt={contact.name}
size={28}
src={contact.avatar}
/>
<span className="inline-flex min-w-0 items-baseline gap-2">
<span className="truncate font-semibold text-foreground">
{contact.name}
</span>
<span className="truncate text-muted-foreground">
{contact.handle}
</span>
</span>
</span>
</Checkbox>
))}
</Checkbox.Group>
<ActionBar open={selected.length > 0} width={560}>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<span className="font-medium">
<span className="font-semibold text-foreground">
{selected.length} {selectedLabel}
</span>{' '}
selected
</span>
<div className="flex items-center gap-2">
<Button onClick={() => setSelected([])}>Clear</Button>
<Button variant="solid" onClick={() => setSelected([])}>
Message
</Button>
</div>
</div>
</ActionBar>
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add ActionBarExamples
Basic
Preview
Dark
import { useState } from 'react';
import ActionBar from '@/components/ui/ActionBar';
import Button from '@/components/ui/Button';
export default function BasicDemo() {
const [open, setOpen] = useState(false);
return (
<div className="flex min-h-72 w-full items-center justify-center p-4">
<Button variant="solid" onClick={() => setOpen(true)}>
Open action bar
</Button>
<ActionBar open={open}>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<span className="font-medium">
<span className="font-semibold text-foreground">1 item</span>{' '}
selected
</span>
<div className="flex items-center gap-2">
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="solid" onClick={() => setOpen(false)}>
Confirm
</Button>
</div>
</div>
</ActionBar>
</div>
);
}Width
Preview
Dark
import { useState } from 'react';
import ActionBar from '@/components/ui/ActionBar';
import Button from '@/components/ui/Button';
export default function WidthDemo() {
const [open, setOpen] = useState(true);
return (
<div className="min-h-72 w-full p-4">
<div className="max-w-sm space-y-2">
<div className="text-sm font-medium text-foreground">Wide action bar</div>
<p className="text-sm text-muted-foreground">
The action strip can be widened for multiple bulk actions.
</p>
{!open ? (
<Button variant="solid" onClick={() => setOpen(true)}>
Show wide bar
</Button>
) : null}
</div>
<ActionBar open={open} width={700}>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<span className="font-medium">
<span className="font-semibold text-foreground">8 items</span>{' '}
selected
</span>
<div className="flex flex-wrap items-center gap-2">
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button onClick={() => setOpen(false)}>Export</Button>
<Button variant="solid" onClick={() => setOpen(false)}>
Update status
</Button>
</div>
</div>
</ActionBar>
</div>
);
}Close With Escape
Preview
Dark
import { useState } from 'react';
import ActionBar from '@/components/ui/ActionBar';
import Button from '@/components/ui/Button';
export default function EscapeDemo() {
const [open, setOpen] = useState(false);
return (
<div className="flex min-h-72 w-full flex-col items-center justify-center gap-4 p-4 text-center">
<Button variant="solid" onClick={() => setOpen(true)}>
Open action bar
</Button>
<p className="text-sm text-muted-foreground">
Press Escape while the bar is open to close it.
</p>
<ActionBar open={open} onOpenChange={setOpen} shouldCloseOnEsc>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<span className="font-medium">
<span className="font-semibold text-foreground">1 item</span>{' '}
selected
</span>
<div className="flex items-center gap-2">
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="solid" onClick={() => setOpen(false)}>
Apply
</Button>
</div>
</div>
</ActionBar>
</div>
);
}Selection
Preview
Dark
import { useState } from 'react';
import ActionBar from '@/components/ui/ActionBar';
import Button from '@/components/ui/Button';
import Checkbox from '@/components/ui/Checkbox';
export default function SelectionDemo() {
const [selected, setSelected] = useState<string[]>(['selection-a']);
return (
<div className="flex w-full justify-center p-4">
<Checkbox.Group
className="flex-col mt-8"
value={selected}
onChange={(nextValue) => setSelected(nextValue as string[])}
>
<Checkbox value="selection-a">Selection A</Checkbox>
<Checkbox value="selection-b">Selection B</Checkbox>
<Checkbox value="selection-c">Selection C</Checkbox>
</Checkbox.Group>
<ActionBar open={selected.length > 0}>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<span className="font-medium">
<span className="font-semibold text-foreground">
{selected.length} items
</span>{' '}
selected
</span>
<div className="flex items-center gap-2">
<Button onClick={() => setSelected([])}>Clear all</Button>
</div>
</div>
</ActionBar>
</div>
);
}API
ActionBar accepts the props below plus native props for the root div.
| Prop | Description | Type | Default |
|---|---|---|---|
children | Content rendered inside the action bar. | ReactNode | - |
className | Class names applied to the positioned action bar wrapper. | string | - |
contentClassName | Class names applied to the inner action bar surface. | string | - |
onOpenChange | Called when the floating interaction layer requests an open-state change. | ActionBarOpenChangeHandler | - |
open | Controls whether the action bar is visible. | boolean | - |
ref | Ref forwarded to the inner action bar content element. | Ref<HTMLDivElement> | - |
shouldCloseOnEsc | Allows Escape to request closing through onOpenChange. | boolean | false |
width | Width of the action bar in pixels. Falls back to auto width when the viewport is narrower. | number | 480 |
...nativeProps | Native props for the positioned action bar wrapper. | NativeActionBarProps | - |
Types
type ActionBarOpenChangeHandler = (open: boolean) => void;
type NativeActionBarProps = Omit<
ComponentProps<'div'>,
| 'children'
| 'className'
| 'contentClassName'
| 'onOpenChange'
| 'open'
| 'ref'
| 'shouldCloseOnEsc'
| 'width'
>;