Bonsplit is a custom tab bar and layout split library for macOS apps. Enjoy out of the box 120fps animations, drag-and-drop reordering, SwiftUI support & keyboard navigation.

.package(url: "https://github.com/almonk/bonsplit.git", from: "1.1.1")

### Features

Configurable & Observable

Create Tabs

Create tabs with optional icons and dirty indicators. Target specific panes or use the focused pane.

let tabId = controller.createTab(
title: "Document.swift",
icon: "swift",
isDirty: false,
inPane: paneId
)

Split Panes

Split any pane horizontally or vertically. New panes are empty by default, giving you full control.

// Split focused pane horizontally
let newPaneId = controller.splitPane(
orientation: .horizontal
)
// Split with a tab already in the new pane
controller.splitPane(
orientation: .vertical,
withTab: Tab(title: "New", icon: "doc")
)

Update Tab State

Update tab properties at any time. Changes animate smoothly.

// Mark document as modified
controller.updateTab(tabId, isDirty: true)
// Rename tab
controller.updateTab(tabId, title: "NewName.swift")
// Change icon
controller.updateTab(tabId, icon: "doc.text")

Navigate Focus

Programmatically navigate between panes using directional navigation.

// Move focus between panes
controller.navigateFocus(direction: .left)
controller.navigateFocus(direction: .right)
controller.navigateFocus(direction: .up)
controller.navigateFocus(direction: .down)
// Or focus a specific pane
controller.focusPane(paneId)

Geometry Sync

Two-way synchronization with external programs. Query geometry, receive change notifications, and update dividers programmatically.

// Query current geometry with pixel coordinates
let snapshot = controller.layoutSnapshot()
for pane in snapshot.panes {
print(pane.frame.width, pane.frame.height)
}
// Set divider position from external source
controller.setDividerPosition(0.3, forSplit: splitId, fromExternal: true)

### Read this, agents...

API Reference

Complete reference for all Bonsplit classes, methods, and configuration options.

BonsplitController

The main controller for managing tabs and panes. Create an instance and pass it to BonsplitView.

Tab Operations
Split Operations
Focus Management
Query Methods
Geometry & Synchronization

BonsplitDelegate

Implement this protocol to receive callbacks about tab bar events. All methods have default implementations and are optional.

Tab Callbacks
Pane Callbacks
Geometry Callbacks

Geometry Types

Types used for geometry queries and external synchronization. All types are Codable and Sendable for easy serialization and thread safety.

LayoutSnapshot

A complete snapshot of the current layout with pixel coordinates.

public struct LayoutSnapshot: Codable, Sendable {
public let containerFrame: PixelRect // Container bounds in screen coords
public let panes: [PaneGeometry] // All pane geometries
public let focusedPaneId: String? // Currently focused pane
public let timestamp: TimeInterval // Snapshot time
}

PaneGeometry

Geometry information for a single pane.

public struct PaneGeometry: Codable, Sendable {
public let paneId: String // Pane UUID string
public let frame: PixelRect // Pixel coordinates
public let selectedTabId: String? // Selected tab UUID
public let tabIds: [String] // All tab UUIDs
}

PixelRect

A rectangle in pixel coordinates for external consumption.

public struct PixelRect: Codable, Sendable {
public let x: Double
public let y: Double
public let width: Double
public let height: Double
}

ExternalTreeNode

Recursive tree representation of the split hierarchy.

public enum ExternalTreeNode: Codable, Sendable {
case pane(ExternalPaneNode) // Leaf node: a pane
case split(ExternalSplitNode) // Branch node: a split with two children
}

ExternalSplitNode

A split node in the tree with orientation and divider position.

public struct ExternalSplitNode: Codable, Sendable {
public let id: String // Split UUID string
public let orientation: String // "horizontal" or "vertical"
public let dividerPosition: Double // 0.0-1.0
public let first: ExternalTreeNode // First child
public let second: ExternalTreeNode // Second child
}

ExternalPaneNode

A pane node in the tree with frame and tab information.

public struct ExternalPaneNode: Codable, Sendable {
public let id: String // Pane UUID string
public let frame: PixelRect // Pixel coordinates
public let tabs: [ExternalTab] // Tab info
public let selectedTabId: String? // Selected tab UUID
}

ExternalTab

Tab information for external consumption.

public struct ExternalTab: Codable, Sendable {
public let id: String // Tab UUID string
public let title: String // Tab title
}

BonsplitConfiguration

Configure behavior and appearance. Pass to BonsplitController on initialization.

allowSplits
Bool

Enable split buttons and drag-to-split

Default: true

allowCloseTabs
Bool

Show close buttons on tabs

Default: true

allowCloseLastPane
Bool

Allow closing the last remaining pane

Default: false

allowTabReordering
Bool

Enable drag-to-reorder tabs within a pane

Default: true

allowCrossPaneTabMove
Bool

Enable moving tabs between panes via drag

Default: true

autoCloseEmptyPanes
Bool

Automatically close panes when their last tab is closed

Default: true

contentViewLifecycle
ContentViewLifecycle

How tab content views are managed when switching tabs

Default: .recreateOnSwitch

newTabPosition
NewTabPosition

Where new tabs are inserted in the tab list

Default: .current

Example
let config = BonsplitConfiguration(
allowSplits: true,
allowCloseTabs: true,
allowCloseLastPane: false,
autoCloseEmptyPanes: true,
contentViewLifecycle: .keepAllAlive,
newTabPosition: .current
)
let controller = BonsplitController(configuration: config)
Content View Lifecycle

Controls how tab content views are managed when switching between tabs.

ModeMemoryStateUse Case
.recreateOnSwitchLowNoneSimple content
.keepAllAliveHigherFullComplex views, forms
New Tab Position

Controls where new tabs are inserted in the tab list.

ModeBehavior
.currentInsert after currently focused tab, or at end if none
.endAlways insert at the end of the tab list
Appearance
tabBarHeight
CGFloat

Height of the tab bar

Default: 33

tabMinWidth
CGFloat

Minimum width of a tab

Default: 140

tabMaxWidth
CGFloat

Maximum width of a tab

Default: 220

tabSpacing
CGFloat

Spacing between tabs

Default: 0

minimumPaneWidth
CGFloat

Minimum width of a pane

Default: 100

minimumPaneHeight
CGFloat

Minimum height of a pane

Default: 100

showSplitButtons
Bool

Show split buttons in the tab bar

Default: true

animationDuration
Double

Duration of animations in seconds

Default: 0.15

enableAnimations
Bool

Enable or disable all animations

Default: true

Presets
.default
BonsplitConfiguration

Default configuration with all features enabled

.singlePane
BonsplitConfiguration

Single pane mode with splits disabled

.readOnly
BonsplitConfiguration

Read-only mode with all modifications disabled