Sweets
Configuration

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)
ActionResult
group_toggleTurn 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_downJoin 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_leaveTake the focused member out and place it immediately after the group
tab, "next" or "prev"Cycle available members, wrapping at either end
tab, positive integerSelect 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",
  },
})
FieldTypeDefaultDescription
new_windowstring"tile""group" joins a new tiled window to the focused group
heightinteger24Bar height in logical pixels, 1 to 128
gapinteger4Space between bar and window, 0 to 64
corner_radiusinteger4Segment corner radius in logical pixels, 0 to 64
hide_when_singlebooleanfalseHide the bar and reclaim its space when only one member is available
fontstring"Sans 10"Pango font description, nonempty and at most 256 bytes
alignstring"left"Label alignment: "left" or "center"
iconsbooleantrueShow retained window icons when available
text.focused, text.active, text.inactive, text.urgentcolor"#ffffff"Per-state text colors, as #RRGGBB or #RRGGBBAA
colors.focusedpaint"#c27aff"Shown member of the focused group
colors.activepaint"#333338"Shown member of an unfocused group
colors.inactivepaint"#282828"Hidden member
colors.urgentpaint"#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.

On this page