ActionBar

A fixed-position bottom bar for bulk selection actions

Usage

ActionBar is a fixed-position bottom bar that appears when items are selected, providing contextual bulk actions. It is built using Affix, Transition and Paper components.

Content inside ActionBar is composed freely using existing Mantine components (Button, ActionIcon, Group, Text, etc.). Use ActionBar.Divider to add a vertical divider and ActionBar.CloseButton to add a close button that triggers the onClose callback.

Element positionElement nameSymbolAtomic mass
6CarbonC12.011
7NitrogenN14.007
39YttriumY88.906
56BariumBa137.33
58CeriumCe140.12
import { useState } from 'react';
import { ActionBar, Button, Checkbox, Table, Text } from '@mantine/core';

const elements = [
  { position: 6, mass: 12.011, symbol: 'C', name: 'Carbon' },
  { position: 7, mass: 14.007, symbol: 'N', name: 'Nitrogen' },
  { position: 39, mass: 88.906, symbol: 'Y', name: 'Yttrium' },
  { position: 56, mass: 137.33, symbol: 'Ba', name: 'Barium' },
  { position: 58, mass: 140.12, symbol: 'Ce', name: 'Cerium' },
];

function Demo() {
  const [selection, setSelection] = useState<number[]>([]);

  const toggleRow = (position: number) =>
    setSelection((current) =>
      current.includes(position)
        ? current.filter((item) => item !== position)
        : [...current, position]
    );

  const toggleAll = () =>
    setSelection((current) =>
      current.length === elements.length ? [] : elements.map((element) => element.position)
    );

  const rows = elements.map((element) => (
    <Table.Tr
      key={element.position}
      bg={selection.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined}
    >
      <Table.Td>
        <Checkbox
          aria-label="Select row"
          checked={selection.includes(element.position)}
          onChange={() => toggleRow(element.position)}
        />
      </Table.Td>
      <Table.Td>{element.position}</Table.Td>
      <Table.Td>{element.name}</Table.Td>
      <Table.Td>{element.symbol}</Table.Td>
      <Table.Td>{element.mass}</Table.Td>
    </Table.Tr>
  ));

  return (
    <>
      <Table>
        <Table.Thead>
          <Table.Tr>
            <Table.Th>
              <Checkbox
                aria-label="Select all"
                checked={selection.length === elements.length}
                indeterminate={selection.length > 0 && selection.length !== elements.length}
                onChange={toggleAll}
              />
            </Table.Th>
            <Table.Th>Element position</Table.Th>
            <Table.Th>Element name</Table.Th>
            <Table.Th>Symbol</Table.Th>
            <Table.Th>Atomic mass</Table.Th>
          </Table.Tr>
        </Table.Thead>
        <Table.Tbody>{rows}</Table.Tbody>
      </Table>

      <ActionBar opened={selection.length > 0} onClose={() => setSelection([])} shadow="md">
        <Text size="sm">{selection.length} selected</Text>
        <ActionBar.Divider />
        <Button variant="default" size="compact-sm">
          Delete
        </Button>
        <Button variant="default" size="compact-sm">
          Move
        </Button>
        <Button variant="default" size="compact-sm">
          Archive
        </Button>
        <ActionBar.CloseButton />
      </ActionBar>
    </>
  );
}

ActionBar root has role="group" and the divider has role="separator" so that assistive technologies announce the controls as one bar. Set aria-label to describe what the actions apply to.

With ActionIcon buttons

Element positionElement nameSymbolAtomic mass
6CarbonC12.011
7NitrogenN14.007
39YttriumY88.906
56BariumBa137.33
58CeriumCe140.12
import { useState } from 'react';
import { ArchiveIcon, FolderIcon, TrashIcon } from '@phosphor-icons/react';
import { ActionBar, ActionIcon, Checkbox, Group, Table, Text, Tooltip } from '@mantine/core';

const elements = [
  { position: 6, mass: 12.011, symbol: 'C', name: 'Carbon' },
  { position: 7, mass: 14.007, symbol: 'N', name: 'Nitrogen' },
  { position: 39, mass: 88.906, symbol: 'Y', name: 'Yttrium' },
  { position: 56, mass: 137.33, symbol: 'Ba', name: 'Barium' },
  { position: 58, mass: 140.12, symbol: 'Ce', name: 'Cerium' },
];

function Demo() {
  const [selection, setSelection] = useState<number[]>([]);

  const toggleRow = (position: number) =>
    setSelection((current) =>
      current.includes(position)
        ? current.filter((item) => item !== position)
        : [...current, position]
    );

  const toggleAll = () =>
    setSelection((current) =>
      current.length === elements.length ? [] : elements.map((element) => element.position)
    );

  const rows = elements.map((element) => (
    <Table.Tr
      key={element.position}
      bg={selection.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined}
    >
      <Table.Td>
        <Checkbox
          aria-label="Select row"
          checked={selection.includes(element.position)}
          onChange={() => toggleRow(element.position)}
        />
      </Table.Td>
      <Table.Td>{element.position}</Table.Td>
      <Table.Td>{element.name}</Table.Td>
      <Table.Td>{element.symbol}</Table.Td>
      <Table.Td>{element.mass}</Table.Td>
    </Table.Tr>
  ));

  return (
    <>
      <Table>
        <Table.Thead>
          <Table.Tr>
            <Table.Th>
              <Checkbox
                aria-label="Select all"
                checked={selection.length === elements.length}
                indeterminate={selection.length > 0 && selection.length !== elements.length}
                onChange={toggleAll}
              />
            </Table.Th>
            <Table.Th>Element position</Table.Th>
            <Table.Th>Element name</Table.Th>
            <Table.Th>Symbol</Table.Th>
            <Table.Th>Atomic mass</Table.Th>
          </Table.Tr>
        </Table.Thead>
        <Table.Tbody>{rows}</Table.Tbody>
      </Table>

      <ActionBar opened={selection.length > 0} onClose={() => setSelection([])}>
        <Text size="sm" fw={500}>{selection.length} rows selected</Text>
        <ActionBar.Divider />
        <Tooltip.Group openDelay={300} closeDelay={100}>
          <Group gap="xs">
            <Tooltip label="Delete">
              <ActionIcon variant="default" color="red" size="lg">
                <TrashIcon size={16} />
              </ActionIcon>
            </Tooltip>
            <Tooltip label="Move to folder">
              <ActionIcon variant="default" size="lg">
                <FolderIcon size={16} />
              </ActionIcon>
            </Tooltip>
            <Tooltip label="Archive">
              <ActionIcon variant="default" size="lg">
                <ArchiveIcon size={16} />
              </ActionIcon>
            </Tooltip>
          </Group>
        </Tooltip.Group>
        <ActionBar.Divider />
        <ActionBar.CloseButton />
      </ActionBar>
    </>
  );
}

Placement

By default, ActionBar is centered at the bottom of the screen. You can change the position with the position prop. When using non-centered positions, set styles={{ root: { marginInline: 0 } }} to remove the auto centering.

import { useDisclosure } from '@mantine/hooks';
import { ActionBar, Button, Group, Text } from '@mantine/core';

function Demo() {
  const [center, centerHandlers] = useDisclosure(false);
  const [left, leftHandlers] = useDisclosure(false);
  const [right, rightHandlers] = useDisclosure(false);

  return (
    <>
      <Group justify="center">
        <Button onClick={centerHandlers.toggle}>Center (default)</Button>
        <Button onClick={leftHandlers.toggle}>Left</Button>
        <Button onClick={rightHandlers.toggle}>Right</Button>
      </Group>

      <ActionBar opened={center} onClose={centerHandlers.close}>
        <Text size="sm">Center position (default)</Text>
        <ActionBar.CloseButton />
      </ActionBar>

      <ActionBar
        opened={left}
        onClose={leftHandlers.close}
        position={{ bottom: 30, left: 30 }}
        styles={{ root: { marginInline: 0 } }}
      >
        <Text size="sm">Left position</Text>
        <ActionBar.CloseButton />
      </ActionBar>

      <ActionBar
        opened={right}
        onClose={rightHandlers.close}
        position={{ bottom: 30, right: 30 }}
        styles={{ root: { marginInline: 0 } }}
      >
        <Text size="sm">Right position</Text>
        <ActionBar.CloseButton />
      </ActionBar>
    </>
  );
}

Close on Escape

By default, ActionBar does not close when Escape key is pressed. Set closeOnEscape to enable this behavior:

import { useDisclosure } from '@mantine/hooks';
import { ActionBar, Button, Group, Text } from '@mantine/core';

function Demo() {
  const [opened, { toggle, close }] = useDisclosure(false);

  return (
    <>
      <Group justify="center">
        <Button onClick={toggle}>Toggle action bar</Button>
      </Group>

      <ActionBar opened={opened} onClose={close} closeOnEscape>
        <Text size="sm">Press Escape to close this bar</Text>
        <ActionBar.Divider />
        <Button variant="default" size="compact-sm" onClick={close}>
          Close
        </Button>
      </ActionBar>
    </>
  );
}

Transitions

ActionBar is animated with the Transition component, { transition: 'pop', duration: 200 } by default. Use transitionProps to change the transition, its duration and timing function:

import { ActionBar, Button } from '@mantine/core';

function Demo() {
  return (
    <ActionBar opened transitionProps={{ transition: 'slide-up', duration: 300 }}>
      <Button>Delete</Button>
    </ActionBar>
  );
}

keepMounted

By default, the action bar is removed from the DOM when it is closed. Set keepMounted to keep it mounted and hide it with display: none instead – for example, if the bar contains form state that must be preserved between openings:

import { ActionBar, Button } from '@mantine/core';

function Demo() {
  return (
    <ActionBar opened={false} keepMounted>
      <Button>Delete</Button>
    </ActionBar>
  );
}

Rendering in a portal

ActionBar is rendered inside Portal by default – this way it is not affected by overflow and transform styles of its parent elements. Set withinPortal={false} to render it in place, and use portalProps to pass props down to the Portal component.