Claude
Skills
Sign in
Back

quickshell

Included with Lifetime
$97 forever

Use this skill when writing, reviewing, or debugging Quickshell configurations (QML files for desktop shell UI on Wayland/Hyprland). Triggers on: QML files with Quickshell imports, shell.qml entry points, PanelWindow or FloatingWindow usage, Quickshell service integration (PipeWire, MPRIS, notifications, Hyprland IPC), Wayland layer-shell or session-lock code, custom bar/panel/widget/dock/OSD/lockscreen/launcher development, or any question about building a desktop shell with Quickshell on Hyprland.

Design

What this skill does


# Quickshell Development

## Overview

Quickshell is a Qt6/QML desktop shell toolkit that Claude does not have reliable training data for. This skill provides the complete API reference, architectural patterns, gotchas, and pointers to local reference repositories needed to write correct Quickshell QML. Without it, Claude will hallucinate Quickshell types, confuse it with other shell frameworks, or miss critical patterns like PwObjectTracker and the Variants multi-monitor pattern.

Build desktop shells (bars, panels, docks, lockscreens, launchers, OSDs, dashboards, notification daemons) using the Quickshell framework on Wayland compositors, primarily Hyprland.

## What Quickshell Is

Quickshell is a Qt6/QML-based shell toolkit. You write declarative QML that Quickshell renders as Wayland surfaces (panels, overlays, floating windows). It live-reloads on file save. Config lives at `~/.config/quickshell/shell.qml` by default.

Key differentiators from other shell frameworks (AGS, EWW):
- Native Qt6/QML with full QtQuick — not a custom DSL
- Reactive property bindings, not imperative updates
- Live reload with state preservation via the Reloadable system
- Direct Wayland protocol integration (layer-shell, session-lock, screencopy)
- Deep Hyprland IPC integration as first-class QML types

## Core Architecture

### Entry Point

Every shell starts with a `ShellRoot` in `shell.qml`:

```qml
import Quickshell

ShellRoot {
    // Non-visual root container for all shell objects
    // settings.watchFiles: true  — enables live reload on file change
}
```

### Window Types

| Type | Use | Key Properties |
|------|-----|----------------|
| `PanelWindow` | Bars, panels, widgets anchored to screen edges | `anchors.{top,bottom,left,right}`, `height`/`width`, `exclusiveZone`, `screen` |
| `FloatingWindow` | Standard desktop windows, settings UIs | Standard Qt window properties |
| `WlSessionLockSurface` | Lock screen surfaces | Used inside `WlSessionLock` |

### Multi-Monitor Pattern (critical)

**Always** use `Variants` with `Quickshell.screens` for per-monitor windows:

```qml
Variants {
    model: Quickshell.screens

    PanelWindow {
        property var modelData
        screen: modelData
        anchors { top: true; left: true; right: true }
        height: 30
    }
}
```

This is reactive — windows create/destroy as monitors connect/disconnect. Never hardcode screens.

### Non-Visual Containers

- `Scope` — groups non-visual children (Process, Timer, Connections). Use when extracting components to separate files
- `Singleton` (with `pragma Singleton`) — global shared state accessible from any file. Use for services and shared data
- `ShellRoot` — the outermost Scope; properties defined here are accessible without an id from nested scopes

### Component Organization

```
~/.config/quickshell/
├── shell.qml          # Entry point (ShellRoot)
├── modules/           # Major UI subsystems (bar/, dashboard/, lock/, etc.)
├── components/        # Reusable UI components
├── services/          # Singleton services (Audio, Network, Hypr, etc.)
├── config/            # Configuration system
└── utils/             # Utility singletons
```

Uppercase QML filenames become types automatically. `Bar.qml` becomes `Bar {}`. Import subdirectories with `import "modules/bar"`.

## QML Patterns for Quickshell

See `references/qml-patterns.md` for the complete pattern library with code examples.

Key patterns:
1. **Reactive bindings** — `text: Time.time` auto-updates when `Time.time` changes
2. **Signal connections** — `Connections { target: X; function onSignal() {...} }` or arrow: `onRead: data => clock.text = data`
3. **LazyLoader** — `LazyLoader { active: condition; PanelWindow { ... } }` for memory-efficient ephemeral UI
4. **Process execution** — `Process { command: ["cmd"]; stdout: SplitParser { onRead: data => prop = data } }`
5. **Timers** — `Timer { interval: 1000; running: true; repeat: true; onTriggered: ... }`
6. **Required properties in delegates** — `required property PwLinkGroup modelData` in Repeater delegates
7. **Optional chaining** — `sink?.audio?.volume ?? 0` for nullable service objects
8. **Click-through windows** — `mask: Region {}` makes a window transparent to input

## Available Modules

See `references/modules-api.md` for the full module reference with all types and properties.

### Core
- **Quickshell** — ShellRoot, PanelWindow, FloatingWindow, Variants, Scope, Singleton, LazyLoader, Region, Quickshell.screens, Quickshell.iconPath(), Quickshell.env()
- **Quickshell.Io** — Process, SplitParser, Socket, SocketServer, FileView, IpcHandler, JsonAdapter
- **Quickshell.Widgets** — IconImage, ClippingRectangle, WrapperRectangle

### Wayland
- **Quickshell.Wayland** — WlrLayershell (WlrLayer.Overlay/Top/Bottom/Background), WlSessionLock, WlSessionLockSurface, idle inhibit/notify, screencopy, background effects
- **Quickshell.Hyprland** — Hyprland (singleton: monitors, workspaces, toplevels, focusedWorkspace, focusedMonitor, activeToplevel), HyprlandFocusGrab, CustomShortcut, dispatch()

### Services
- **Quickshell.Services.Pipewire** — Pipewire.defaultAudioSink/Source, PwNode (.audio.volume, .audio.muted), PwObjectTracker, PwNodeLinkTracker
- **Quickshell.Services.Mpris** — Mpris (singleton: players), MprisPlayer
- **Quickshell.Services.Notifications** — NotificationServer, Notification
- **Quickshell.Services.Pam** — PamContext, PamResult
- **Quickshell.Services.UPower** — UPower (singleton: devices)
- **Quickshell.Services.Polkit** — PolkitAgent
- **Quickshell.Services.Greetd** — Greetd

### Hardware
- **Quickshell.Bluetooth** — Bluetooth (singleton: adapters, devices)
- **Quickshell.Networking** — NetworkManager (singleton: devices, connections)

## Hyprland Integration

### Accessing Hyprland State

```qml
import Quickshell.Hyprland

// All reactive — auto-update on compositor changes
Hyprland.monitors          // ObjectModel<HyprlandMonitor>
Hyprland.workspaces        // ObjectModel<HyprlandWorkspace>
Hyprland.toplevels         // ObjectModel<HyprlandToplevel>
Hyprland.focusedMonitor    // HyprlandMonitor
Hyprland.focusedWorkspace  // HyprlandWorkspace
Hyprland.activeToplevel    // HyprlandToplevel

// Dispatch commands
Hyprland.dispatch("workspace 3")
Hyprland.dispatch("movetoworkspace 5")
```

### Layer Shell Positioning

```qml
PanelWindow {
    WlrLayershell.layer: WlrLayer.Top      // Top, Bottom, Overlay, Background
    WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive  // for modals
    exclusiveZone: 0                        // 0 = don't reserve space
    exclusionMode: ExclusionMode.Ignore     // ignore other panels' zones
}
```

### Focus Grab (for drawers/popups)

```qml
HyprlandFocusGrab {
    id: focusGrab
    active: drawerVisible
    windows: [drawerWindow]
    onCleared: drawerVisible = false  // clicked outside
}
```

### IPC: Triggering Shell Actions from Hyprland Keybinds

Two mechanisms to wire Hyprland keybinds to shell actions:

**1. Native global shortcuts (preferred, lower latency):**
```conf
# hyprland.conf
bind = Super, A, global, quickshell:sidebarToggle
bind = Super, Tab, global, quickshell:overviewToggle
```
Maps to `CustomShortcut { name: "sidebarToggle"; onPressed: ... }` in QML.

**2. IPC via CLI (resilient, works across instances):**
```conf
bind = , XF86MonBrightnessUp, exec, qs ipc call brightness increment
bind = Super, Super_L, exec, qs ipc call launcher toggle
```

Define targets in QML with `IpcHandler`:
```qml
IpcHandler {
    target: "brightness"
    function increment(): void { ... }
    function decrement(): void { ... }
    function get(): real { return currentBrightness }
}
```

CLI usage: `qs ipc show` (list targets), `qs ipc call <target> <func> [args]`

### Launching Applications

Use `DesktopEntry.command` (not `.execString`) with `Quickshell.execDetached()`:
```qml
function launch(entry: DesktopEntry): void {
    Quickshell.execDetached({
        command: entry.command,
        workingDirectory: entry

Related in Design