Components

ActionMenu

The gesture-driven menu behind the Create picker and album options.

iOS
Android
Web
import ActionMenuContextMenu from "swshd/ActionMenu/Context";

For a single action, use Button.

Each shell is a default export from its own entry point. The Context popover is self-contained on both platforms; the Create menu's web card becomes a bottom sheet below sheetBelow, and on native Create is only the trigger's gesture host — the app draws the sheet under a shared provider and overlay.

Album settings
import ActionMenuContextMenu from "swshd/ActionMenu/Context";
import ActionMenuCreateMenu from "swshd/ActionMenu/Create";

Open each menu, then pick a row, press Escape, or click the backdrop.

The parts compose inside a shell's children:

Group

A section of rows with an optional uppercase header.

Label

The group's title row, joining the entrance cascade.

Item

One selectable row. The create variant draws a disc, title, and subtitle; the context variant draws a big-text action row.

ScrollArea

Web only. Wraps a run of rows inside the card: those rows scroll while rows before or after it stay pinned, and its own maxHeight caps the run. On native it renders its children inline.

Data-shaped menus map their key to the row's id:

{group.items.map(({ key, ...item }) => (
  <ActionMenuItem key={key} id={key} {...item} />
))}

On web a click opens the card; on native a press-and-drag lands on the row. Selection is deferred until the exit animation completes, so an action that re-renders the menu cannot cut the close short. On web both cards enter over 220 ms; the Context card exits on the same transition, and the Create card exits faster, over 140 ms. Native cards use springs. On web, maxHeight scrolls the rows inside the card.

A few props are internal-facing: the shell's surface supplies paddingLeft/paddingRight, discSize, index, and stagger. backgroundColor, textColor, headerColor, iconColor, and highlightColor are legacy palette overrides for the app's themed menus. Leave them unset.

<ActionMenuContextMenu
  renderChildren={({ isOpen, toggle, gestureControlled }) => (
    <ButtonRoot iconOnly variant="ghost"
      accessibilityLabel="Options" onPress={gestureControlled ? undefined : toggle}>
      <ButtonIcon Icon={isOpen ? IconClose : IconEllipsis} />
    </ButtonRoot>
  )}
>
  <ActionMenuGroup>
    <ActionMenuLabel>Album settings</ActionMenuLabel>
    <ActionMenuItem id="joiners" label="Joiners" Icon={IconPeople} onSelect={onJoiners} />
  </ActionMenuGroup>
</ActionMenuContextMenu>

Roles

The card is role="menu"; rows are menuitem, or menuitemradio with aria-checked when selected is set.

Keyboard

Escape closes an open menu.

Trigger

The trigger's accessible name is the consumer's — the render prop owns its button, so pass an accessibilityLabel.

Each shell's props:

ActionMenuContextMenu

PropTypeDefaultDescription
renderChildren(state: ActionMenuRenderState) => ReactNode—The trigger. Receives isOpen, toggle, close, and gestureControlled (native drag).
anchorActionMenuAnchor{ horizontal: "right", vertical: "below" }Corner the popover pins to and grows from.
align"left" | "right"—Row content and group header alignment. Defaults to the anchor's horizontal side.
widthnumber—Explicit menu width. Omitted, the card hugs its content plus edgePadding on the side opposite align.
edgePaddingnumberACTION_MENU_DEFAULT_EDGE_PADDINGPadding on the side opposite align when width is unset.
edgeDetectionbooleantrueFlip the requested anchor axis when the card would overflow the viewport.
maxHeightnumber—Web only. Caps the card's height; past it the rows scroll inside the card.
onOpen() => void—Fires on every open, including the native drag.
containerStyleStyleProp<ViewStyle>—The host element.
surfaceStyleStyleProp<ViewStyle>—The popover surface.
childrenReactNode—The menu's rows, composed of Group / Label / Item.

ActionMenuCreateMenu

PropTypeDefaultDescription
renderChildren(state: ActionMenuRenderState) => ReactNode—The trigger. On native gestureControlled is true and the Pan owns press/drag.
placement"above" | "below"—Web only. Which side of the trigger the card opens on.
align"left" | "right""right"Web only. Which trigger edge the card pins to.
sheetBelownumber—Web only. Below this viewport width the card renders as a bottom sheet. Off when omitted.
maxHeightnumber—Web only. Caps the card's height.
containerStyleStyleProp<ViewStyle>—The host element.
childrenReactNode—The rows. Ignored on native — the app draws the sheet.

ActionMenuItem

PropTypeDefaultDescription
idstring—Row identity for drag hit-testing and selection (key is React-reserved).
labelstring—The row's text.
IconIconComponent—A glyph from swshd/icons.
onSelect() => void—Fired on selection — deferred until the menu's exit animation completes.
subtitlestring—Secondary line — only the create variant's row body renders it.
leadingReactElement—Rendered in the create row's disc slot instead of Icon.
accessoryReactElement—Rendered under the title in the subtitle slot when there is no subtitle.
selectedboolean—Marks the current choice: a trailing check and aria-checked.
destructiveboolean—Danger ink for delete/leave actions.
disabledboolean—Blocks selection.
variant"create" | "context"—Which row body to draw. Defaults to the surface's variant, then "context".
align"left" | "right"—Row content alignment. Context body only; defaults to "right".