ActionBar

ActionBar keeps contextual actions visible when people select items in a list, table, calendar, or similar surface.

Use it for common bulk actions like edit, delete, merge, export, or more options without moving people away from their current task.

Available from eds-core/1.43.0

Quick Start

Installation
npm install @adaptavant/eds-core
Import
import { ActionBar } from '@adaptavant/eds-core';

Usage

Use ActionBar when one or more items are selected and contextual actions should remain available while the user continues scanning the surface.

Strategy :
Customer listSelect rows to show the action bar and update the selection count.
const [selectedIds, setSelectedIds] = React.useState([]);
const [strategy, setStrategy] = React.useState("absolute");
const { isMobile } = useResponsiveLayout();

const customers = [
  { id: "ava", name: "Ava Johnson", email: "ava@example.com" },
  { id: "ben", name: "Ben Carter", email: "ben@example.com" },
  { id: "mia", name: "Mia Patel", email: "mia@example.com" },
];

const toggleSelection = (id) => {
  setSelectedIds((current) =>
    current.includes(id)
      ? current.filter((selectedId) => selectedId !== id)
      : [...current, id]
  );
};

React.useLayoutEffect(() => {
  setSelectedIds(["ava"]);
}, []);

return (
  <Box className="mx-auto w-full">
    <Box className="mb-3 flex items-center gap-2">
      <Text className="text-body-14 font-strong text-primary mr-2 ml-1">Strategy :</Text>
      <Radio label="Absolute" value="absolute" checked={strategy === "absolute"} onChange={() => setStrategy("absolute")}/>
      <Radio label="Fixed" value="fixed" checked={strategy === "fixed"} onChange={() => setStrategy("fixed")}/>
    </Box>
    <Box
      className="relative min-h-80 rounded-16px border border-neutral-tertiary bg-canvas p-6 pb-24"
    >
      <Text className="text-heading-16">Customer list</Text>
      <Text className="mt-2 text-body-14 text-secondary">
        Select rows to show the action bar and update the selection count.
      </Text>
      <Box className="mt-4 flex flex-col gap-3">
        {customers.map((customer) => (
          <Checkbox
            checked={selectedIds.includes(customer.id)}
            key={customer.id}
            label={
              <Box className="flex flex-col">
                <Text className="text-body-14">{customer.name}</Text>
                <Text className="text-body-12 text-secondary">
                  {customer.email}
                </Text>
              </Box>
            }
            onChange={() => toggleSelection(customer.id)}
          />
        ))}
      </Box>

      <ActionBar
        aria-label="Customer bulk actions"
        closeButtonProps={{ onClick: () => setSelectedIds([]) }}
        label={`${selectedIds.length} customer${
          selectedIds.length === 1 ? "" : "s"
        } selected`}
        open={selectedIds.length > 0}
        strategy={strategy}
      >
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <>
                      <DropdownMenuItem railStart={<EditIcon />}>
                        Edit
                      </DropdownMenuItem>
                      <DropdownMenuItem railStart={<DeleteIcon />}>
                        Delete
                      </DropdownMenuItem>
                    </>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
  </Box>
);

Placement

Set placement for the side where the ActionBar appears.

Strategy :
Placement :
Customer list
const [selectedIds, setSelectedIds] = React.useState([]);
const [placement,setPlacement] = React.useState('bottom')
const [strategy, setStrategy] = React.useState("absolute");
const { isMobile } = useResponsiveLayout();

const customers = [
  { id: "ava", name: "Ava Johnson", email: "ava@example.com" },
  { id: "ben", name: "Ben Carter", email: "ben@example.com" },
  { id: "mia", name: "Mia Patel", email: "mia@example.com" },
];

const toggleSelection = (id) => {
  setSelectedIds((current) =>
    current.includes(id)
      ? current.filter((selectedId) => selectedId !== id)
      : [...current, id]
  );
};

React.useLayoutEffect(()=>{
  setSelectedIds(["ava"])
},[])

return (
  <Box className="w-full">
    <Box className="mb-3 flex items-center gap-5 flex-wrap">
      <Box className="flex items-center gap-2">
        <Text className="text-body-14 font-strong text-primary mr-2 ml-1">Strategy :</Text>
        <Radio label="Absolute" value="absolute" checked={strategy === "absolute"} onChange={() => setStrategy("absolute")}/>
        <Radio label="Fixed" value="fixed" checked={strategy === "fixed"} onChange={() => setStrategy("fixed")}/>
      </Box>
      <Box className="flex items-center gap-2">
        <Text className="text-body-14 font-strong text-primary mr-2 ml-1">Placement :</Text>
        <Radio label="Top" value="top" checked={"top" === placement} onChange={() => setPlacement("top")}/>
        <Radio label="Bottom" value="bottom" checked={"bottom" === placement} onChange={() => setPlacement("bottom")}/>
      </Box>
    </Box>
    <Box
      className="relative min-h-80 rounded-16px border border-neutral-tertiary bg-canvas p-6 py-16"
    >
      <Text className="text-heading-16">Customer list</Text>
      <Box className="mt-4 flex flex-col gap-3">
        {customers.map((customer) => (
          <Checkbox
            checked={selectedIds.includes(customer.id)}
            key={customer.id}
            label={
              <Box className="flex flex-col">
                <Text className="text-body-14">{customer.name}</Text>
                <Text className="text-body-12 text-secondary">
                  {customer.email}
                </Text>
              </Box>
            }
            onChange={() => toggleSelection(customer.id)}
          />
        ))}
      </Box>

      <ActionBar
        aria-label="Customer bulk actions"
        closeButtonProps={{ onClick: () => setSelectedIds([]) }}
        label={`${selectedIds.length} customer${selectedIds.length === 1 ? "" : "s"} selected`}
        open={selectedIds.length > 0}
        placement={placement}
        strategy={strategy}
      >
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <>
                      <DropdownMenuItem railStart={<EditIcon />}>
                        Edit
                      </DropdownMenuItem>
                      <DropdownMenuItem railStart={<DeleteIcon />}>
                        Delete
                      </DropdownMenuItem>
                    </>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
  </Box>
);

Size

Compare size="standard" and size="large" in same open state

Large

Standard

const [open, setOpen] = React.useState(false);
const { isMobile } = useResponsiveLayout();

React.useLayoutEffect(() => {
  setOpen(true);
}, []);

return (
  <Box className="w-full flex flex-col gap-3">
    <Heading as="h4" className="pl-2">Large</Heading>
    <Box
      className="relative rounded-8px border border-neutral-tertiary bg-canvas min-h-20 w-full"
    >
      <ActionBar
        aria-live="off"
        closeButtonProps={{ onClick: () => setOpen(false) }}
        label={`Size Large`}
        open={open}
        size={"large"}
        strategy="absolute"
      >
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <>
                      <DropdownMenuItem railStart={<EditIcon />}>
                        Edit
                      </DropdownMenuItem>
                      <DropdownMenuItem railStart={<DeleteIcon />}>
                        Delete
                      </DropdownMenuItem>
                    </>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
    <Heading as="h4" className="pl-2">Standard</Heading>
    <Box
      className="relative rounded-8px border border-neutral-tertiary bg-canvas min-h-[72px] w-full"
    >
      <ActionBar
        aria-live="off"
        closeButtonProps={{ onClick: () => setOpen(false) }}
        label={`Size Standard`}
        open={open}
        size={"standard"}
        strategy="absolute"
      >
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <>
                      <DropdownMenuItem railStart={<EditIcon />}>
                        Edit
                      </DropdownMenuItem>
                      <DropdownMenuItem railStart={<DeleteIcon />}>
                        Delete
                      </DropdownMenuItem>
                    </>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
  </Box>
);

Variant

Compare variant="inverse" and variant="neutral" in same open state

Inverse

Neutral

const [open, setOpen] = React.useState(false);
const { isMobile } = useResponsiveLayout();

React.useLayoutEffect(() => {
  setOpen(true);
}, []);

return (
  <Box className="w-full flex flex-col gap-3">
    <Heading as="h4" className="pl-2">Inverse</Heading>
    <Box
      className="relative rounded-8px border border-neutral-tertiary bg-canvas min-h-[72px] w-full"
    >
      <ActionBar
        aria-live="off"
        closeButtonProps={{ onClick: () => setOpen(false) }}
        label={`Variant Inverse`}
        open={open}
        strategy="absolute"
        variant="inverse"
      >
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <>
                      <DropdownMenuItem railStart={<EditIcon />}>
                        Edit
                      </DropdownMenuItem>
                      <DropdownMenuItem railStart={<DeleteIcon />}>
                        Delete
                      </DropdownMenuItem>
                    </>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
    <Heading as="h4" className="pl-2">Neutral</Heading>
    <Box
      className="relative rounded-8px border border-neutral-tertiary bg-canvas min-h-[72px] w-full"
    >
      <ActionBar
        aria-live="off"
        closeButtonProps={{ onClick: () => setOpen(false) }}
        label={`Variant Neutral`}
        open={open}
        strategy="absolute"
        variant="neutral"
      >
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <>
                      <DropdownMenuItem railStart={<EditIcon />}>
                        Edit
                      </DropdownMenuItem>
                      <DropdownMenuItem railStart={<DeleteIcon />}>
                        Delete
                      </DropdownMenuItem>
                    </>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
  </Box>
);

Mobile Usage

You can place a DropdownMenu trigger inside ActionBar for grouped actions. This is useful when secondary actions should stay available without taking too much horizontal space, especially on mobile. This matches the story pattern: keep the list-selection flow, then spread triggerProps onto ActionBarIconButton.

Strategy :
Customer listGroup secondary actions in a menu when the bar has limited space.
This is especially useful on mobile, where horizontal space is tighter. Keep most important action visible in bar, then move secondary actions into overflow menu so same actions stay available without making bar feel crowded. In this example, `useResponsiveLayout` moves `Edit` and `Delete` into the menu on mobile without responsive CSS classes.
const [selectedIds, setSelectedIds] = React.useState([]);
const [strategy, setStrategy] = React.useState("absolute");
const { isMobile } = useResponsiveLayout();

const customers = [
  { id: "ava", name: "Ava Johnson", email: "ava@example.com" },
  { id: "ben", name: "Ben Carter", email: "ben@example.com" },
  { id: "mia", name: "Mia Patel", email: "mia@example.com" },
];

const toggleSelection = (id) => {
  setSelectedIds((current) =>
    current.includes(id)
      ? current.filter((selectedId) => selectedId !== id)
      : [...current, id]
  );
};

React.useLayoutEffect(()=>{
  setSelectedIds(["ava"])
},[])

return (
  <Box className="mx-auto max-w-5xl">
    <Box className="mb-3 flex items-center gap-2">
      <Text className="text-body-14 font-strong text-primary mr-2 ml-1">Strategy :</Text>
      <Radio label="Absolute" value="absolute" checked={strategy === "absolute"} onChange={() => setStrategy("absolute")}/>
      <Radio label="Fixed" value="fixed" checked={strategy === "fixed"} onChange={() => setStrategy("fixed")}/>
    </Box>
    <Box
      className="relative min-h-80 rounded-16px border border-neutral-tertiary bg-canvas p-6 pb-24"
    >
      <Text className="text-heading-16">Customer list</Text>
      <Text className="mt-2 text-body-14 text-secondary">
        Group secondary actions in a menu when the bar has limited space.
        <br/>
        This is especially useful on mobile, where horizontal space is tighter.
        Keep most important action visible in bar, then move secondary actions
        into overflow menu so same actions stay available without making bar
        feel crowded. In this example, `useResponsiveLayout` moves `Edit` and
        `Delete` into the menu on mobile without responsive CSS classes.
      </Text>
      <Box className="mt-4 flex flex-col gap-3">
        {customers.map((customer) => (
          <Checkbox
            checked={selectedIds.includes(customer.id)}
            key={customer.id}
            label={
              <Box className="flex flex-col">
                <Text className="text-body-14">{customer.name}</Text>
                <Text className="text-body-12 text-secondary">
                  {customer.email}
                </Text>
              </Box>
            }
            onChange={() => toggleSelection(customer.id)}
          />
        ))}
      </Box>

      <ActionBar
        aria-label="Customer bulk actions"
        closeButtonProps={{ onClick: () => setSelectedIds([]) }}
        label={`${selectedIds.length} customer${selectedIds.length === 1 ? "" : "s"} selected`}
        open={selectedIds.length > 0}
        strategy={strategy}
      >
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More options"
                icon={MoreIcon}
                {...triggerProps}
              />

              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <>
                      <DropdownMenuItem railStart={<EditIcon />}>
                        Edit
                      </DropdownMenuItem>
                      <DropdownMenuItem railStart={<DeleteIcon />}>
                        Delete
                      </DropdownMenuItem>
                    </>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
  </Box>
);

Invoke ActionBar

You can invoke ActionBar directly from a button. In this pattern, omit the label and show only the actions.

Strategy :
Button triggerUse a custom trigger when the action bar should open independently of row selection.
const [open, setOpen] = React.useState(false);
const [strategy, setStrategy] = React.useState("absolute");
const { isMobile } = useResponsiveLayout();

return (
  <Box className="mx-auto max-w-5xl">
    <Box className="mb-3 flex items-center gap-2">
      <Text className="text-body-14 font-strong text-primary mr-2 ml-1">Strategy :</Text>
      <Radio label="Absolute" value="absolute" checked={strategy === "absolute"} onChange={() => setStrategy("absolute")}/>
      <Radio label="Fixed" value="fixed" checked={strategy === "fixed"} onChange={() => setStrategy("fixed")}/>
    </Box>
    <Box
      className="relative min-h-80 rounded-16px border border-neutral-tertiary bg-canvas p-6 pb-24"
    >
      <Text className="text-heading-16">Button trigger</Text>
      <Text className="mt-2 text-body-14 text-secondary">
        Use a custom trigger when the action bar should open independently of row selection.
      </Text>
      <Button
        className="mt-6"
        onClick={() => {
          setOpen(true);
        }}
      >
        Invoke action bar
      </Button>

      <ActionBar
        closeButtonProps={{ onClick: () => setOpen(false) }}
        open={open}
        strategy={strategy}
      >
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <>
                      <DropdownMenuItem railStart={<EditIcon />}>
                        Edit
                      </DropdownMenuItem>
                      <DropdownMenuItem railStart={<DeleteIcon />}>
                        Delete
                      </DropdownMenuItem>
                    </>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
  </Box>
);

Custom Offset

Use offset when the ActionBar should clear fixed or container-level UI, such as a footer or pagination area. Offset values are pixels.

Customer listOffset keeps the action bar above the reserved footer area.
Footer area reserved for pagination or summary controls.
const [selectedIds, setSelectedIds] = React.useState([]);
const { isMobile } = useResponsiveLayout();

const customers = [
  { id: "ava", name: "Ava Johnson", email: "ava@example.com" },
  { id: "ben", name: "Ben Carter", email: "ben@example.com" },
  { id: "mia", name: "Mia Patel", email: "mia@example.com" },
];

const toggleSelection = (id) => {
  setSelectedIds((current) =>
    current.includes(id)
      ? current.filter((selectedId) => selectedId !== id)
      : [...current, id]
  );
};

React.useLayoutEffect(() => {
  setSelectedIds(["ava"]);
}, []);

return (
  <Box className="mx-auto max-w-5xl">
    <Box className="relative min-h-96 overflow-hidden rounded-16px border border-neutral-tertiary bg-canvas p-6 pb-36">
      <Text className="text-heading-16">Customer list</Text>
      <Text className="mt-2 text-body-14 text-secondary">
        Offset keeps the action bar above the reserved footer area.
      </Text>
      <Box className="mt-4 flex flex-col gap-3">
        {customers.map((customer) => (
          <Checkbox
            checked={selectedIds.includes(customer.id)}
            key={customer.id}
            label={
              <Box className="flex flex-col">
                <Text className="text-body-14">{customer.name}</Text>
                <Text className="text-body-12 text-secondary">
                  {customer.email}
                </Text>
              </Box>
            }
            onChange={() => toggleSelection(customer.id)}
          />
        ))}
      </Box>

      <Box className="absolute inset-x-0 bottom-0 border-t border-neutral-tertiary px-6 py-4">
        <Text className="text-body-12 text-secondary">
          Footer area reserved for pagination or summary controls.
        </Text>
      </Box>

      <ActionBar
        aria-label="Customer bulk actions"
        closeButtonProps={{ onClick: () => setSelectedIds([]) }}
        label={`${selectedIds.length} customer${selectedIds.length === 1 ? "" : "s"} selected`}
        offset={80}
        open={selectedIds.length > 0}
        placement="bottom"
        strategy="absolute"
      >
        <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
        {isMobile ? null : (
          <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <DropdownMenuItem railStart={<DeleteIcon />}>
                      Delete
                    </DropdownMenuItem>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
  </Box>
);

Accessibility Guidelines

ActionBar accessibility depends on a clear toolbar name and useful announcements when the visible label changes.

Assistive Labels

Use aria-label when the default Bulk actions label does not describe the toolbar clearly enough. For example, use aria-label="Customer bulk actions" when the ActionBar only applies to selected customers.

Assistive Announcements

Use aria-live to control how updates to the ActionBar label are announced. ActionBar applies aria-live to the visible label, not to the whole toolbar. See MDN guidance for choosing an aria-live value.

  • Write self-contained labels, such as "3 customers selected", so screen reader users hear both the changed value and its context.
  • Keep the default aria-live="polite" for normal selection-count changes. This announces updates at the next suitable pause without interrupting current screen reader speech.
  • Use aria-live="off" when the same change is already announced by a toast, status message, or focus change. This prevents duplicate announcements.
  • Use aria-live="assertive" only for urgent, time-sensitive updates that the user must hear immediately. Assertive announcements can interrupt screen reader speech.

Announce changes that affect selected item count, available actions, completion status, waiting state, or errors. Do not announce decorative, layout, placement, offset, or responsive changes.

API Reference

ActionBar

PropDefaultDescription
open_boolean
Controls whether the ActionBar is rendered.
children_ReactNode
Actions rendered inside the ActionBar. Use ActionBarButton, ActionBarIconButton, ActionBarSeparator, or other interactive controls such as a DropdownMenu.
label?_string
Optional selection label, such as 3 customers.
closeButtonProps?_{ label?: string; onClick?: () => void }
Providing this prop renders the close button. label defaults to 'Close action bar'. Use onClick to handle close-button clicks or Escape dismissal.
variant?'inverse''neutral' | 'inverse'
Visual variant for the ActionBar surface.
size?'standard''standard' | 'large'
Size for the ActionBar.
placement?'bottom''top' | 'bottom'
Side where the ActionBar appears. Fixed strategy uses the viewport edge; absolute strategy uses the nearest positioned ancestor.
offset?16number
Distance in pixels from the placement edge. Fixed strategy adds the matching safe-area inset.
strategy?'fixed''fixed' | 'absolute'
CSS positioning strategy for the ActionBar root. Use absolute inside a container with position: relative.
shouldUsePortal?falseboolean
Controls whether the ActionBar is rendered in the default EDS portal target. This only changes where the bar mounts; strategy controls positioning.
aria-label?'Bulk actions'string
Accessible label for the ActionBar.
aria-live'polite''off' | 'polite' | 'assertive'
Announces label updates to assistive technologies. Use polite for non-urgent updates and assertive for time-sensitive updates.
id?_string
Unique identifier for the ActionBar root element.

ActionBarButton

PropDefaultDescription
children_ReactNode
Button label content.
iconStart?_(props: IconProps) => ReactNode
Icon rendered before the label.
iconEnd?_(props: IconProps) => ReactNode
Icon rendered after the label.
onClick?_(event: MouseEvent) => void
Callback invoked when the button is clicked.
isDisabled?falseboolean
Marks the button as disabled for assistive technology and prevents click behavior.
isLoading?falseboolean
Shows a loading indicator and prevents click behavior.

ActionBarIconButton

PropDefaultDescription
aria-label_string
Required accessible label for the icon-only action.
icon_(props: IconProps) => ReactNode
Icon rendered inside the button.
onClick?_(event: MouseEvent) => void
Callback invoked when the icon button is clicked.
isDisabled?falseboolean
Marks the icon button as disabled for assistive technology and prevents click behavior.
isLoading?falseboolean
Shows a loading indicator and prevents click behavior.

ActionBarSeparator

PropDefaultDescription
id?_string
Optional id for the separator element.
className?_string
Custom class applied to the separator.
style?_CSSProperties
Custom style applied to the separator.

Style API

Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.

Please refer to the Style API documentation for more insights.

ActionBar parts

Strategy :
Styled customer listThis example exaggerates each ActionBar part so the Style API is easier to inspect.
const [selectedIds, setSelectedIds] = React.useState([]);
const [strategy, setStrategy] = React.useState("absolute");
const { isMobile } = useResponsiveLayout();

const customers = [
  { id: "ava", name: "Ava Johnson", email: "ava@example.com" },
  { id: "ben", name: "Ben Carter", email: "ben@example.com" },
  { id: "mia", name: "Mia Patel", email: "mia@example.com" },
];

const toggleSelection = (id) => {
  setSelectedIds((current) =>
    current.includes(id)
      ? current.filter((selectedId) => selectedId !== id)
      : [...current, id]
  );
};

React.useLayoutEffect(()=>{
  setSelectedIds(["ava"])
},[])

return (
  <Box className="mx-auto max-w-5xl">
    <Box className="mb-3 flex items-center justify-end gap-2">
      <Text className="text-body-14 font-strong text-primary mr-2 ml-1">Strategy :</Text>
      <Radio label="Absolute" value="absolute" checked={strategy === "absolute"} onChange={() => setStrategy("absolute")}/>
      <Radio label="Fixed" value="fixed" checked={strategy === "fixed"} onChange={() => setStrategy("fixed")}/>
    </Box>
    <Box
      className="relative min-h-80 rounded-16px border border-neutral-tertiary bg-canvas p-6 pb-28"
    >
      <Text className="text-heading-16">Styled customer list</Text>
      <Text className="mt-2 text-body-14 text-secondary">
        This example exaggerates each ActionBar part so the Style API is easier to inspect.
      </Text>
      <Box className="mt-4 flex flex-col gap-3">
        {customers.map((customer) => (
          <Checkbox
            checked={selectedIds.includes(customer.id)}
            key={customer.id}
            label={
              <Box className="flex flex-col">
                <Text className="text-body-14">{customer.name}</Text>
                <Text className="text-body-12 text-secondary">
                  {customer.email}
                </Text>
              </Box>
            }
            onChange={() => toggleSelection(customer.id)}
          />
        ))}
      </Box>

      <ActionBar
        aria-label="Customer bulk actions"
        classNames={{
          root: "px-6",
          toolbarContainer: "rounded-16px border-2 border-primary bg-blue-500 text-warning-onPrimary shadow-[0_16px_40px_rgba(15,23,42,0.28)]",
          closeButton: "bg-critical text-critical-onPrimary ring-2 ring-white/40",
          label: "rounded-8px bg-black px-3 py-1 font-stronger uppercase tracking-wide",
          separator: "h-6 bg-primary",
          inlineStartContainer: "gap-3 rounded-12px bg-black/10 py-2 pl-3 pr-2",
          actions: "gap-2 rounded-12px bg-white/10 px-2 py-2",
        }}
        label={`${selectedIds.length} customer${selectedIds.length === 1 ? "" : "s"} selected`}
        closeButtonProps={{ onClick: () => setSelectedIds([]) }}
        open={selectedIds.length > 0}
        strategy={strategy}
      >
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            <ActionBarSeparator />
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            <ActionBarSeparator />
            <ActionBarButton iconStart={DeleteIcon}>Delete</ActionBarButton>
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <>
                      <DropdownMenuItem railStart={<EditIcon />}>
                        Edit
                      </DropdownMenuItem>
                      <DropdownMenuItem railStart={<DeleteIcon />}>
                        Delete
                      </DropdownMenuItem>
                    </>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
  </Box>
);
PartDescription
rootOuter positioning wrapper. Useful for changing spacing or placement behavior around the bar.
toolbarContainerMain ActionBar surface. Use it for background, border, radius, and shadow changes.
closeButtonClose IconButton that invokes closeButtonProps.onClick.
labelOptional selection label. Useful when the label needs stronger emphasis than the default text styling.
separatorDefault separator slot rendered by ActionBar between the label and action group. Use ActionBarSeparator to add more dividers between consumer-rendered actions.
inlineStartContainerInner group that wraps the close button, label, and separator.
actionsContainer for the action buttons and menu triggers.

ActionBarButton parts

ActionBarButton renders a Button and does not add new style parts.

Use the same styling props as Button, including className, classNames, style, and styles.

Its size and variant are inherited from the parent ActionBar.

ActionBarIconButton parts

ActionBarIconButton renders an IconButton and does not add new style parts.

Use the same styling props as IconButton, including className, classNames, style, and styles.

Its size and variant are inherited from the parent ActionBar.

ActionBarSeparator parts

const [open, setOpen] = React.useState(false);
const { isMobile } = useResponsiveLayout();

React.useLayoutEffect(() => {
  setOpen(true);
}, []);

return (
  <Box className="w-full flex flex-col gap-3">
    <Box
      className="relative rounded-8px border border-neutral-tertiary bg-canvas min-h-[72px] w-full"
    >
      <ActionBar open strategy="absolute">
        <ActionBarButton iconStart={MergeIcon}>Merge</ActionBarButton>
        {isMobile ? null : (
          <>
            {/* Styled ActionBarSeparator */}
            <ActionBarSeparator
              className="h-8 bg-critical rounded-full"
              style={{ width: "3px" }}
            />
            <ActionBarButton iconStart={EditIcon}>Edit</ActionBarButton>
            {/* Styled ActionBarSeparator */}
            <ActionBarSeparator
              className="h-8 rounded-full bg-[green]"
              style={{ width: "3px" }}
            />
          </>
        )}
        <DropdownMenu popoverOffset={8} popoverPlacement="bottom-end">
          {({ triggerProps }) => (
            <>
              <ActionBarIconButton
                aria-label="More actions"
                icon={MoreIcon}
                {...triggerProps}
              />
              <DropdownMenuPopover>
                <DropdownMenuList>
                  {isMobile ? (
                    <DropdownMenuItem railStart={<EditIcon />}>
                      Edit
                    </DropdownMenuItem>
                  ) : null}
                  <DropdownMenuItem railStart={<InformationIcon />}>
                    View details
                  </DropdownMenuItem>
                  <DropdownMenuItem railStart={<ExportIcon />}>
                    Export data
                  </DropdownMenuItem>
                </DropdownMenuList>
              </DropdownMenuPopover>
            </>
          )}
        </DropdownMenu>
      </ActionBar>
    </Box>
  </Box>
);

No parts available. Only root.