ActionBar

free

ActionBar 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 ActionBar

Examples

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.

PropDescriptionTypeDefault
childrenContent rendered inside the action bar.ReactNode-
classNameClass names applied to the positioned action bar wrapper.string-
contentClassNameClass names applied to the inner action bar surface.string-
onOpenChangeCalled when the floating interaction layer requests an open-state change.ActionBarOpenChangeHandler-
openControls whether the action bar is visible.boolean-
refRef forwarded to the inner action bar content element.Ref<HTMLDivElement>-
shouldCloseOnEscAllows Escape to request closing through onOpenChange.booleanfalse
widthWidth of the action bar in pixels. Falls back to auto width when the viewport is narrower.number480
...nativePropsNative 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'
>;