Show HN: Bonsplit – tabs and splits for native macOS apps
### 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
)
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")
)
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")
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)
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)
### 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
BonsplitDelegate
Implement this protocol to receive callbacks about tab bar events. All methods have default implementations and are optional.
Tab Callbacks
Pane Callbacks
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.
| Mode | Memory | State | Use Case |
|---|---|---|---|
.recreateOnSwitch |
Low | None | Simple content |
.keepAllAlive |
Higher | Full | Complex views, forms |
New Tab Position
Controls where new tabs are inserted in the tab list.
| Mode | Behavior |
|---|---|
.current |
Insert after currently focused tab, or at end if none |
.end |
Always 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
