Propel

Icon Split Button

A split button whose main action is icon-only, for toolbars and other dense chrome.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconSplitButton } from "@makeplane/propel/components/icon-split-button";
import { Menu, MenuContent, MenuItem } from "@makeplane/propel/components/menu";
import { AddOutline } from "@makeplane/propel/icons";

export default function BasicDemo() {
  return (
    <Menu>
      <IconSplitButton
        variant="primary"
        size="md"
        icon={<Icon icon={AddOutline} />}
        aria-label="Create work item"
      />
      <MenuContent align="end">
        <MenuItem label="Create from template" />
        <MenuItem label="Import work items" />
      </MenuContent>
    </Menu>
  );
}

Installation

import { IconSplitButton } from "@makeplane/propel/components/icon-split-button";

Usage

IconSplitButton is SplitButton with an icon-only main segment. It ships no menu of its own — render it as a Menu’s child, with the surface as a sibling MenuContent. Clicking the main segment fires onClick; the chevron opens the menu.

Both segments are icon-only, so both need an accessible name: aria-label names the main action and menuLabel names the chevron.

import { Icon } from "@makeplane/propel/components/icon";
import { IconSplitButton } from "@makeplane/propel/components/icon-split-button";
import { Menu, MenuContent, MenuItem } from "@makeplane/propel/components/menu";
import { AddOutline } from "@makeplane/propel/icons";

export default function BasicDemo() {
  return (
    <Menu>
      <IconSplitButton
        variant="primary"
        size="md"
        icon={<Icon icon={AddOutline} />}
        aria-label="Create work item"
      />
      <MenuContent align="end">
        <MenuItem label="Create from template" />
        <MenuItem label="Import work items" />
      </MenuContent>
    </Menu>
  );
}

Choosing between the two

Reach for IconSplitButton only where a glyph alone names the action unambiguously — a toolbar +, say. An icon-only main segment gives no way to tell what a plain click will do without hovering for the tooltip, so anywhere that matters, keep the text label and use SplitButton.

If every path through the control goes through the menu — the glyph only names a category and there is no default action worth firing on its own — this is the wrong component. That is a plain menu trigger, not a split button, and it should not carry a divider.

Examples

Variants

Primary lays the segments out as separate pills; secondary connects them with a divider. There is no tertiary or ghost split button, and no danger palette — both segments are neutral.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconSplitButton } from "@makeplane/propel/components/icon-split-button";
import { Menu, MenuContent, MenuItem } from "@makeplane/propel/components/menu";
import { AddOutline } from "@makeplane/propel/icons";

export default function VariantsDemo() {
  return (
    <div className="flex flex-wrap items-center gap-6">
      {(["primary", "secondary"] as const).map((variant) => (
        <Menu key={variant}>
          <IconSplitButton
            variant={variant}
            size="md"
            icon={<Icon icon={AddOutline} />}
            aria-label={`Create work item (${variant})`}
            menuLabel={`More options (${variant})`}
          />
          <MenuContent align="end">
            <MenuItem label="Secondary action" />
          </MenuContent>
        </Menu>
      ))}
    </div>
  );
}

Sizes

One shared size sizes both segments — sm 24 / md 28 / lg 32 px. Both segments are the same square box, so the control measures exactly twice its rung height, plus the frame’s 1px gap on primary. There is no xs: the segments are contiguous, so the 20px rung’s synthesized pointer target would overlap its neighbour. See Sizing for the full ladder.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconSplitButton } from "@makeplane/propel/components/icon-split-button";
import { Menu, MenuContent, MenuItem } from "@makeplane/propel/components/menu";
import { AddOutline } from "@makeplane/propel/icons";

export default function SizesDemo() {
  return (
    <div className="flex flex-col items-start gap-6">
      {(["sm", "md", "lg"] as const).map((size) => (
        <Menu key={size}>
          <IconSplitButton
            variant="primary"
            size={size}
            icon={<Icon icon={AddOutline} />}
            aria-label={`Create work item (${size})`}
            menuLabel={`More options (${size})`}
          />
          <MenuContent align="end">
            <MenuItem label="Secondary action" />
          </MenuContent>
        </Menu>
      ))}
    </div>
  );
}

Loading

loading shows a spinner on the main segment (soft-disabled: still focusable, aria-busy) and disables the menu segment so the pending action can’t fork.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconSplitButton } from "@makeplane/propel/components/icon-split-button";
import { Menu, MenuContent, MenuItem } from "@makeplane/propel/components/menu";
import { AddOutline } from "@makeplane/propel/icons";

export default function LoadingDemo() {
  return (
    <Menu>
      <IconSplitButton
        variant="primary"
        size="md"
        icon={<Icon icon={AddOutline} />}
        aria-label="Create work item"
        loading
      />
      <MenuContent align="end">
        <MenuItem label="Create from template" />
      </MenuContent>
    </Menu>
  );
}

Disabled

disabled disables both segments.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconSplitButton } from "@makeplane/propel/components/icon-split-button";
import { Menu, MenuContent, MenuItem } from "@makeplane/propel/components/menu";
import { AddOutline } from "@makeplane/propel/icons";

export default function DisabledDemo() {
  return (
    <Menu>
      <IconSplitButton
        variant="primary"
        size="md"
        icon={<Icon icon={AddOutline} />}
        aria-label="Create work item"
        disabled
      />
      <MenuContent align="end">
        <MenuItem label="Create from template" />
      </MenuContent>
    </Menu>
  );
}

API Reference

IconSplitButton

The icon-only split button (Figma "Icon Split button", 9441:1252): two square IconButtons in the shared SplitButton frame — a main action segment plus a chevron segment grafted as the surrounding menu's MenuTrigger. Both segments are the same square box at every rung (Figma measures 24+24, 28+28, 32+32; primary adds the frame's 1px gap, secondary runs them flush), so neither takes a width override. Neutral only, like SplitButton — no danger/error variant. Use it over SplitButton only where a glyph alone names the action unambiguously (a toolbar +, say). An icon-only main segment gives no way to tell what a plain click will do without hovering; anywhere that matters, keep the text label and use SplitButton. It ships no menu of its own — render it as a Menu's child, with the surface as a sibling MenuContent: ``tsx <Menu> <IconSplitButton variant="primary" size="md" icon={<Icon icon={Plus} />} aria-label="Create" /> <MenuContent align="end"> <MenuItem label="Create from template" /> </MenuContent> </Menu>;

PropTypeDefaultDescription
variant(required)"primary" | "secondary"—
size(required)"sm" | "md" | "lg"—
icon(required)ReactNode—The main segment's icon element, usually <Icon icon={...} />.
aria-labelstring—Defines a string value that labels the current element. Required: the main segment is icon-only, so it has no visible text to name it. @see aria-labelledby.
onClickMouseEventHandler<HTMLDivElement> | MouseEventHandler<HTMLButtonElement>—Click handler of the main action segment.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.
loadingbooleanfalseShows a spinner on the main segment and makes both segments non-interactive (the main segment stays focusable, IconButton's soft-disabled loading state).
disabledboolean—Disables both segments.
type"button" | "submit" | "reset"buttonThe main segment's form behavior.
menuLabelstringMore optionsAccessible name of the menu-opening segment.