Window groups and tabs
Group tiled windows into one slot and switch between them
A window group occupies one tile in any layout. Its indicator bar shows one segment per available member; the focused member fills the tile. When focus leaves the group, its most recently focused member stays shown.
Bindings
These examples are optional. Add the bindings you want to your configuration:
sweets.bind("MOD+G", "group_toggle")
sweets.bind("MOD+Comma", "consume_or_expel_left")
sweets.bind("MOD+Period", "consume_or_expel_right")
sweets.bind("MOD+Tab", "tab", "next")
sweets.bind("MOD+Shift+Tab", "tab", "prev")
sweets.bind("MOD+Alt+Left", "group_join_left")
sweets.bind("MOD+Alt+Right", "group_join_right")
sweets.bind("MOD+Alt+Up", "group_join_up")
sweets.bind("MOD+Alt+Down", "group_join_down")
sweets.bind("MOD+Alt+G", "group_leave")
sweets.bind("MOD+Alt+Tab", "tab_move", "next")
sweets.bind("MOD+Alt+Shift+Tab", "tab_move", "prev")
-- Select a participating member by its one-based position:
-- sweets.bind("MOD+1", "tab", 1)| Action | Result |
|---|---|
group_toggle | Turn a tile into a group of one, or dissolve it while keeping order; on a scrolling strip, toggle the complete column between tabs and a stack |
group_join_left, group_join_right, group_join_up, group_join_down | Join the neighboring tile in that direction; two ordinary tiles become a group. The joining window stays focused and enters just after the target's shown member |
group_leave | Take the focused member out and place it immediately after the group |
tab, "next" or "prev" | Cycle available members, wrapping at either end |
tab, positive integer | Select that available member; an out-of-range position does nothing |
tab_move, "next" or "prev" | Reorder the focused member inside its group |
Group actions apply to tiled windows. They do nothing while the focused window is maximized or fullscreen. Windowed fullscreen keeps the bar and allows tab switching. Ordinary focus actions can reveal a hidden member too.
Click a segment to focus it. Middle-click requests a polite close and respects close protection. A discrete wheel scroll over the bar cycles members; touchpad scrolling does not. Bar buttons are consumed, so they do not reach a window or start a resize grab. Drag a tab off the bar to move that member alone. Hovering the bar does not change focus.
sweets.tabs
sweets.tabs({
new_window = "tile",
height = 24,
gap = 4,
corner_radius = 4,
hide_when_single = false,
font = "Sans 10",
align = "left",
icons = true,
text = {
focused = "#ffffff", active = "#ffffff",
inactive = "#ffffff", urgent = "#ffffff",
},
colors = {
focused = "#c27aff",
active = "#333338",
inactive = "#282828",
urgent = "#f7768e",
},
})| Field | Type | Default | Description |
|---|---|---|---|
new_window | string | "tile" | "group" joins a new tiled window to the focused group |
height | integer | 24 | Bar height in logical pixels, 1 to 128 |
gap | integer | 4 | Space between bar and window, 0 to 64 |
corner_radius | integer | 4 | Segment corner radius in logical pixels, 0 to 64 |
hide_when_single | boolean | false | Hide the bar and reclaim its space when only one member is available |
font | string | "Sans 10" | Pango font description, nonempty and at most 256 bytes |
align | string | "left" | Label alignment: "left" or "center" |
icons | boolean | true | Show retained window icons when available |
text.focused, text.active, text.inactive, text.urgent | color | "#ffffff" | Per-state text colors, as #RRGGBB or #RRGGBBAA |
colors.focused | paint | "#c27aff" | Shown member of the focused group |
colors.active | paint | "#333338" | Shown member of an unfocused group |
colors.inactive | paint | "#282828" | Hidden member |
colors.urgent | paint | "#f7768e" | Unfocused member requesting attention |
Paints accept the same solid colors, alpha, and gradient tables as window borders. Paint changes use the configured border animation. Height, gap, and singleton visibility changes resize the tile through the normal presentation transaction. A tiny tile reduces the bar and gap to leave at least one pixel of window space.
Tabs display single-line window titles and available window icons. Long titles
end with an ellipsis. An empty title falls back to the application ID, then
Window. Text stays at its font size and is cropped during resize animations.
Title and style changes keep the previous pixels until their replacement is
ready. The bar follows the window through movement and resize animations.
An urgent tab uses the urgent paint; focusing it clears urgency. Output captures include the bar. If any group member is blocked from that capture, even a hidden or minimized member, the bar omits every title and icon. Single-window captures contain the window alone. Tab switches have no separate animation. If a client needs a new buffer, the previous member and its highlight stay visible until the replacement is ready.
Lifecycle and movement
Minimizing a member keeps it in the group but omits its segment until restore. Closing a member removes it and shows a survivor. Maximizing or fullscreening a member hides the bar until the cover ends.
Floating a member takes it out of the group. Tiling it again rejoins the remembered group if that group still exists on the original workspace. Unmapping and remapping a window does not retain membership.
Directional moves, output moves, workspace moves and special-workspace moves
carry the complete group, including minimized members. MOD+left dragging the
window body also carries the group. Its order and shown member stay intact.
Use group_leave or drag a tab off the bar to move one member alone. Drop a
tiled or floating window on another group's bar to join it. Dropping a group
on a bar joins all its members.
An IPC move naming a window ID moves only that window and takes it out of its group. The remaining members stay together.
Smart gaps and smart borders count a group as one tile.
Scrolling columns
On the scrolling layout, group_toggle groups every window in the focused
column. Toggle again to restore its stack in the same column. Member order and
column width stay the same; minimized members remain attached.
A lone window's consume_or_expel_left/right action joins the neighboring
column. If that column is tabbed, it joins the group. From a column with several
members, the action takes the focused window or tab out into a new column on
that side. The remaining tabs stay together.
On a right or left strip, focus_up/down walks tab order without wrapping.
On a down or up strip, use focus_left/right. At either end, the configured
cross-monitor focus policy applies. The explicit tab next/prev action still
wraps. Along the strip, focus and camera reveal continue to work on columns.
With movement animations enabled, hidden members fade into the shared slot as the bar appears. Untoggling brings them back into their stack positions. Chrome follows its window and stays clipped when the column or workspace slides past an output edge.
Open new windows as tabs
sweets.tabs({ new_window = "group" })
sweets.window_rule({ app_id = "mpv", group = false })New tiled windows join the focused group on their destination workspace,
immediately after its shown tab. Without a focused group, they open as ordinary tiles.
group = true enables joining for one rule even when new_window is "tile".
Dialogs, transients, floating and fullscreen or maximized openings stay separate. Explicit workspace, monitor, special-workspace or pin placement takes precedence. A joining window adopts the group's shared column width.
Reload affects later openings. Existing windows keep their opening decision,
including across remaps; a vanished target falls back to an ordinary tile.
Use sweets msg window-rule ID to inspect the retained preference and target.
Waybar module
The optional custom module shows the focused group's titles in tab order. The shown tab is bold and bracketed; minimized tabs are struck through. Wheel up selects the previous available tab; wheel down selects the next.
From the Sweets source directory, copy the helper:
mkdir -p ~/.config/waybar
cp scripts/sweets-waybar-tabs ~/.config/waybar/Python 3 is required. Merge config/waybar/tabs.jsonc into your Waybar
configuration and add "custom/sweets-tabs" to a module list.
Merge config/waybar/tabs.css into your stylesheet, then restart Waybar.
"custom/sweets-tabs": {
"exec": "python3 ~/.config/waybar/sweets-waybar-tabs",
"return-type": "json",
"format": "{text}",
"escape": false,
"hide-empty-text": true,
"restart-interval": 2,
"exec-on-event": false,
"on-scroll-up": "python3 ~/.config/waybar/sweets-waybar-tabs --previous",
"on-scroll-down": "python3 ~/.config/waybar/sweets-waybar-tabs --next"
}The module updates from IPC events and reconnects after a disconnect. It hides when focus leaves a group or the session locks. Maximized and fullscreen windows refuse wheel cycling.
This custom module is one label; individual titles are not clickable.
Keep escape: false: the helper escapes window titles before adding markup.
For troubleshooting, run the helper with --once in your Sweets session.