Menu

iOS 16.4+iPadOS 16.4+Not available on macOS

Displays a list of actions or options revealed from a button or custom trigger.

FLXMenu variants
import SwiftUIstruct MenuUsageExample: View {    var body: some View {        VStack(spacing: FLXSpacing.lg) {            FLXMenu("Actions", systemImage: "ellipsis.circle", variant: .primary) {                Button("Rename", systemImage: "pencil") {}                Button("Duplicate", systemImage: "doc.on.doc") {}                Divider()                Button("Delete", systemImage: "trash", role: .destructive) {}            }            FLXMenu("Actions", systemImage: "ellipsis.circle", variant: .secondary) {                Button("Rename", systemImage: "pencil") {}                Button("Delete", systemImage: "trash", role: .destructive) {}            }            .controlSize(.small)            FLXMenu("Actions", systemImage: "ellipsis.circle", variant: .outline) {                Button("Rename", systemImage: "pencil") {}                Button("Delete", systemImage: "trash", role: .destructive) {}            }            .controlSize(.large)        }        .padding(FLXSpacing.xl)    }}

Installation

Requires the design system foundation (tokens and theme), plus FLXButton.

pnpm dlx shadcn@latest add https://swiftui.flexnative.com/r/ios-menu.json

Usage

FLXMenu("Actions", systemImage: "ellipsis.circle") {    Button("Rename", systemImage: "pencil") {}    Button("Delete", systemImage: "trash", role: .destructive) {}}

Content

FLXMenu wraps the native Menu, so the system renders its content. Build it from Button, Divider, Section, a nested Menu for a submenu, or Toggle for options that shouldn't dismiss the menu.

FLXMenu("File", systemImage: "doc") {    Section("Actions") {        Button("Rename", systemImage: "pencil") {}        Button("Duplicate", systemImage: "doc.on.doc") {}    }    Menu("Share") {        Button("Messages", systemImage: "message") {}        Button("Mail", systemImage: "envelope") {}    }    Divider()    Button("Delete", systemImage: "trash", role: .destructive) {}}FLXMenu("Filter", systemImage: "line.3.horizontal.decrease.circle", variant: .outline) {    Toggle("Sort ascending", isOn: $sortAscending)    Toggle("Show archived", isOn: $showArchived)}.menuActionDismissBehavior(.disabled)

Custom label triggers

Use the content/label initializer for an icon-only trigger or a custom trigger like an avatar. No FLXButtonStyle is applied, so style the label yourself:

FLXMenu custom label triggers
FLXMenu {  Button("Rename", systemImage: "pencil") {}  Button("Delete", systemImage: "trash", role: .destructive) {}} label: {  Image(systemName: "ellipsis")}.buttonStyle(FLXButtonStyle(variant: .ghost)).controlSize(.small).accessibilityLabel("More options")FLXMenu {  Button("View profile") {}  Button("Sign out", role: .destructive) {}} label: {  FLXAvatar(size: .small) {      FLXAvatarFallback("FM")  }}

Variants

Five variants, set via variant:. Defaults to .secondary and styles the trigger like FLXButton.

FLXMenu variants
FLXMenu("Actions", systemImage: "ellipsis.circle", variant: .primary) {  Button("Item") {}}FLXMenu("Actions", systemImage: "ellipsis.circle", variant: .outline) {  Button("Item") {}}FLXMenu("Actions", systemImage: "ellipsis.circle", variant: .secondary) {  Button("Item") {}}FLXMenu("Actions", systemImage: "ellipsis.circle", variant: .ghost) {  Button("Item") {}}FLXMenu("Actions", systemImage: "ellipsis.circle", variant: .link) {  Button("Item") {}}

Sizes

Set via .controlSize(_:), like native controls.

FLXMenu sizes
FLXMenu("Actions", systemImage: "ellipsis.circle") {  Button("Item") {}}.controlSize(.small)FLXMenu("Actions", systemImage: "ellipsis.circle") {  Button("Item") {}}FLXMenu("Actions", systemImage: "ellipsis.circle") {  Button("Item") {}}.controlSize(.large)

For an icon-only trigger, use custom label triggers.

Custom styling

There's no FLXMenuStyle: MenuStyle doesn't expose the menu's content. The trigger uses FLXButtonStyle and accepts the same .flxButtonStyle(_:) override as FLXButton:

FLXMenu custom styling
struct FLXPillButtonStyle: ButtonStyle {  func makeBody(configuration: Configuration) -> some View {      configuration.label          .flxFont(.body, weight: .medium)          .padding(.horizontal, FLXSpacing.xl)          .padding(.vertical, FLXSpacing.md)          .foregroundStyle(.white)          .background(.blue.gradient)          .clipShape(Capsule())          .opacity(configuration.isPressed ? FLXOpacity.pressed : 1)  }}HStack(spacing: FLXSpacing.md) {  FLXMenu("Actions", systemImage: "ellipsis.circle") {      Button("Item") {}  }  FLXMenu("Actions", systemImage: "ellipsis.circle") {      Button("Item") {}  }  .flxButtonStyle(FLXPillButtonStyle())}

API Reference

FLXMenu

init(    @ViewBuilder content: @escaping () -> Content,    @ViewBuilder label: @escaping () -> Label)init(    _ titleKey: LocalizedStringKey,    variant: FLXButtonVariant = .secondary,    @ViewBuilder content: @escaping () -> Content)init(    _ titleResource: LocalizedStringResource,    variant: FLXButtonVariant = .secondary,    @ViewBuilder content: @escaping () -> Content)init<S: StringProtocol>(    _ title: S,    variant: FLXButtonVariant = .secondary,    @ViewBuilder content: @escaping () -> Content)init(    _ titleKey: LocalizedStringKey,    systemImage: String,    variant: FLXButtonVariant = .secondary,    @ViewBuilder content: @escaping () -> Content)init(    _ titleResource: LocalizedStringResource,    systemImage: String,    variant: FLXButtonVariant = .secondary,    @ViewBuilder content: @escaping () -> Content)init<S: StringProtocol>(    _ title: S,    systemImage: String,    variant: FLXButtonVariant = .secondary,    @ViewBuilder content: @escaping () -> Content)

Variants

CaseDescription
primaryFilled with theme.colors.primary.
outlineTransparent, bordered.
secondaryFilled with theme.colors.secondary.
ghostTransparent, no border.
linkText-only, styled like a hyperlink.

For more information about FLXMenu props, see the Apple documentation.