# FloatingButton: a SwiftUI expandable menu with two layout modes

> A small SwiftUI package from Exyte that renders a main button with a fan of submenu buttons, either in a straight line or around a circle, with every placement, spacing and animation property exposed as a chained modifier. The trade-off is the AnyView requirement, which erases the type of the buttons you pass in.

**exyte/FloatingButton** — Easily customizable floating button menu created with SwiftUI

- Repository: https://github.com/exyte/FloatingButton
- Stars: 1,270 · Forks: 84
- Language: Swift
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/exyte-floatingbutton

## What the package is, and the AnyView tax

FloatingButton is an easily customizable floating button menu created with SwiftUI, published by Exyte as a Swift package. The repository is small: a `Package.swift`, a `Sources/` directory, a `FloatingButtonExample/` directory and a `LICENSE`.

The usage sequence in the README is short. You create a main button view and a number of submenu buttons, and the first instruction is that both should be cast to the `AnyView` type. Then you pass both to the `FloatingButton` constructor:

```swift
FloatingButton(mainButtonView: mainButton, buttons: buttons)
```

That cast is the price of admission, and it is worth being clear about it. `AnyView` erases the concrete type of a SwiftUI view, which means the compiler can no longer tell you that you passed the wrong thing, and you lose the ability to read the submenu button's type back out. For a component that a designer will restyle repeatedly with different button subviews, the flexibility is convenient. For a codebase that leans on SwiftUI's type inference and compile-time checks, it is a step backwards.

There is also an optional binding that determines whether the menu is currently open, which the README says you can use to close the menu on any submenu button tap, for example:

```swift
FloatingButton(mainButtonView: mainButton, buttons: buttons, isOpen: $isOpen)
```

Without the binding the component manages its own open state, and with it the state moves out to your view, which is what you want if the menu should also close when something else happens.

## Straight and circle are separate modes, not one layout

The menu type is chosen by chaining a modifier, `.straight()` or `.circle()`, and the two modes have entirely separate option sets.

The straight menu has two options of its own. `direction` is the position of the submenu buttons relative to the main menu button, and `alignment` is the alignment of the submenu buttons relative to the main button. Those two together describe a line of buttons fanning out in a direction with an alignment along that line.

The circle menu has four. `startAngle` and `endAngle` bound the arc, `radius` is the distance between the centre of the main button and the centres of the submenu buttons, and `layoutDirection` changes the direction the buttons are laid out in, from the start angle to the end angle.

The README gives both forms in one example:

```swift
FloatingButton(mainButtonView: mainButton, buttons: textButtons)
    .straight()
    .direction(.top)
    .alignment(.left)
    .spacing(10)
    .initialOffset(x: -1000)
    .animation(.spring())

FloatingButton(mainButtonView: mainButton2, buttons: buttonsImage.dropLast())
    .circle()
    .startAngle(3/2 * .pi)
    .endAngle(2 * .pi)
    .radius(70)
    .layoutDirection(.counterClockwise)
```

Note that the chaining is free-form: the README says to chain whatever you like afterwards, and the example mixes layout options with animation and offset options. That is the design, a single chain of modifiers that ends in a configured view, and the component itself holds no opinion about how many buttons you give it or what they contain.

The second example also shows `buttonsImage.dropLast()`, so the array of submenu buttons is an ordinary Swift array you can slice and transform before handing over.

## The universal options and what they actually control

The options that apply to both menu types are grouped together in the README, and they are mostly about the transition between closed and open rather than about layout.

`spacing` is the space between the submenu buttons. `initialScaling` is the size multiplier for the submenu buttons while the menu is closed, `initialOffset` is their offset while closed, and `initialOpacity` is their opacity while closed. Those three together are the starting state the animation departs from, which is why the example uses `.initialOffset(x: -1000)`, an offset large enough to put the buttons off screen before the animation runs.

`animation` takes a custom SwiftUI animation such as `Animation.easeInOut()` or `Animation.spring()`. `delays` controls the delay for each submenu button's animation start, and it accepts either an array of delays, one for each element, or a `delayDelta`, in which case the same delay applies to every element. The per-element array is what produces a staggered fan, and it is the option that turns a mechanical reveal into something that looks designed.

`inverseZIndex` inverts the zIndex of the main button and the children, and the README suggests using it if you have a negative spacing and want to change the order. That is a fix for a specific interaction between the two options rather than a general feature.

Two options exist to observe the result rather than to configure it. `wholeMenuSize` takes a CGSize binding so you get updates of the menu's size, which includes the main button frame and the frames of all the elements. `menuButtonsSize` takes a CGSize binding for the combined size of the menu elements alone. They are for a layout that has to react to how big the menu ended up, which is exactly the case where the menu is placed over a scrolling view or a resizable container.

## Why the menu lives in a ZStack and not an overlay

One implementation note in the README is easy to skim and important to understand.

The `mainZStackAlignment` option is documented with the explanation that the main button and the submenu buttons are contained in one ZStack, not an overlay, so the menu has a correct size, and that this parameter changes the ZStack's alignment.

The reasoning is sound and specific to SwiftUI. An overlay does not contribute to the size of its container, so a menu built as an overlay reports a size covering only the main button, and anything that lays out around it, or that animates based on its bounds, gets the wrong geometry. A ZStack sizes itself to its largest child, so with the submenu buttons placed at their expanded positions the menu reports the full extent.

That has a direct consequence for the `wholeMenuSize` and `menuButtonsSize` bindings, which exist because the size is meaningful. If you place a floating menu inside a container, that container will be at least as large as the expanded menu unless you constrain it, which is a real behaviour to account for when the menu is used in a tight layout.

The `inverseZIndex` option is the companion to this. Because the main button and the children share one stacking context, the draw order is the z-index order, and with a negative spacing the geometric order and the draw order can disagree. Inverting the z-index lets you resolve that.

## Installation through Swift Package Manager, and the CocoaPods deprecation

The package is distributed through Swift Package Manager, and the dependency declaration is:

```swift
dependencies: [
    .package(url: "https://github.com/exyte/FloatingButton.git")
]
```

The README header also carries a Swift Package Index badge for versions and platforms, and an SPM compatible badge. The installation section, however, documents only the Swift Package Manager route.

The CocoaPods situation is stated in a badge rather than in prose. The pod badge reads deprecated after 1.3.0, which means the CocoaPods distribution of this package stopped at version 1.3.0 and that a project still integrating through a Podfile is pinned to an old release with no further updates. The README's own installation section points only at Swift Package Manager, so the practical reading is that SPM is the supported route and CocoaPods is a legacy one.

The platform requirements are explicit:

* iOS 14.0 or later, macOS 11.0 or later, watchOS 7.0 or later.
* Xcode 12 or newer.

The watchOS target is worth noting. A floating button menu is not an obvious watchOS citizen, so the requirement is likely a consequence of the SwiftUI APIs the package uses rather than a design decision, but it is what the README states, and it means the package can be linked into a watchOS target if you want it to be.

To try the examples, the instructions are to clone the repository, open `FloatingButtonExample.xcodeproj` in Xcode, and try it.

## Where FloatingButton is the wrong tool

The first wrong case is a project that needs compile-time type safety in its view tree, because the constructor's `AnyView` requirement erases the types of both the main button and every submenu button. The compiler can no longer catch a wrong view passed in, and nothing can read the button back out by type. For a component that is dropped into ten different screens with ten different button types, that is a cost that accumulates quietly.

The second wrong case is a project that just wants a system menu. iOS and macOS ship a menu affordance already, and a standard menu handles accessibility, dynamic type and platform conventions that a custom SwiftUI component has to reimplement. FloatingButton is a visual component with an animation model, and the README describes no accessibility behaviour, no VoiceOver handling, no dynamic type support and no keyboard focus handling. If your product has to pass an accessibility review, that work is on you.

The third is a project that needs a menu whose items are grouped, labelled or scrollable. The component takes a flat array of buttons and fans them out. A submenu inside a submenu is not something the layout model supports, and a large number of items on a circle either overlaps or requires a very large radius.

The fourth is a team that prefers UIKit for the control. A floating action button is a classic UIKit component, and if your codebase is not SwiftUI there is nothing here to use.

What the package does offer is precise control over a visual detail that is otherwise fiddly, with a small surface area, a single dependency-free source directory, and modifiers that make the closed state and the animation explicit rather than implicit.

## Conclusion

FloatingButton suits a SwiftUI team that needs a floating action menu with a custom look and wants control over each submenu button's offset, delay and animation rather than the stock system menu, and that is building for iOS 14 or later, macOS 11 or later, or watchOS 7 or later. It does not suit a project that wants type safety in its view tree, because the constructor requires both the main button and the submenu buttons to be cast to `AnyView`, and it does not suit a codebase still on CocoaPods, since the badge marks that route deprecated after 1.3.0. Before adopting it, confirm the deployment targets match your minimum versions, and read the `wholeMenuSize` and `menuButtonsSize` bindings if you lay the menu out over other content, since the menu is sized by a ZStack rather than an overlay.

## FAQ

### How do I add FloatingButton to a Swift project?

Add it as a Swift Package Manager dependency with `.package(url: "https://github.com/exyte/FloatingButton.git")`. The README documents only the Swift Package Manager route, and the CocoaPods badge marks that distribution deprecated after 1.3.0.

### What are the platform requirements for FloatingButton?

iOS 14.0 or newer, macOS 11.0 or newer, watchOS 7.0 or newer, and Xcode 12 or newer.

### How do I close the FloatingButton menu when a submenu button is tapped?

Pass a binding for the open state to the constructor, for example `FloatingButton(mainButtonView: mainButton, buttons: buttons, isOpen: $isOpen)`, and set it to false in the button's action.

### How do I make the submenu buttons appear one after another?

Use the `delays` option, passing an array with one delay per element, or pass `delayDelta` to apply the same delay to every element.

### What is the difference between the straight and circle menu modes?

The straight menu has `direction` and `alignment` options for placing the submenu buttons relative to the main button. The circle menu has `startAngle`, `endAngle`, `radius` and `layoutDirection` options for placing them around an arc.

## Sources

- [exyte/FloatingButton on GitHub](https://github.com/exyte/FloatingButton)
- [Issues](https://github.com/exyte/FloatingButton/issues)
- [License: MIT](https://github.com/exyte/FloatingButton/blob/main/LICENSE)
- [README](https://github.com/exyte/FloatingButton/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/exyte-floatingbutton
