A notch that earns its place

Dynamic Island is a monochrome overlay for Hyprland, built on Quickshell. Collapsed it is a compact pill. Hover it—or choose click-to-open—and it morphs into media controls, a live spectrum analyser and three live meters.

your local time

that's not a screenshot — hover it, like you would the real one

What it is

One always-on-top surface pinned to the top edge of the screen. In hover mode it grows under the pointer and collapses 90 ms after the pointer leaves. Prefer a still desktop? Disable hover, use the player-only pill, and click empty pill space only when the full panel is wanted.

Notifications, incoming calls, and microphone or camera activity take the surface over on their own and hand it back when they are done. Everything is drawn in greyscale, with the sole exception of the green and red on a call's answer and decline buttons — the two controls where guessing wrong actually costs you something. It's the only colour on this entire page too.

Changelog · latest

What changed most recently

Every release is written down as it lands — what changed, why, and what it looks like.

Every release, newest first →

Every state

One surface, eleven shapes. Captured live, and shown here the way it actually behaves — a rail of states, not a scroll of unrelated pictures. The interface ships in English and Turkish and switches between them at runtime — every string lives in Strings.qml, so a third language is one branch per line away.

The collapsed pill showing cover art, track title, pixel clock, battery and device indicators
Cover, title, pixel clock, battery and the mic/camera privacy dots. The spectrum ribbon rides the top edge while something plays.

What is in it

MPRIS media
Cover art, scrubbing, shuffle and repeat, previous/play/next. Transport buttons apply optimistically, so the panel reacts on click instead of waiting for the next poll.
Spectrum analyser
Real band data from cava, mirrored around the centre and drawn across the collapsed pill too. Live follows cava frame-for-frame; Wave and Calm reshape it and provide a synthetic fallback.
Settings and themes
Seven themes — Black, Umbra, Gray, White, Gold, Amber and Red — with contrast-safe text, plus live controls for clock style, language, hover behaviour, mini-player mode and animation visibility. The settings window wears whichever you pick, and crossfades between palettes rather than cutting.
Player-only pill
Disable hover expansion and the collapsed island can keep only cover, title and previous/play/next. Clicking its empty surface opens the full panel.
YouTube artwork
Watch, Music, Shorts, Live, Embed and youtu.be URLs resolve to quality-checked thumbnails. Tiny placeholders are rejected and successful images are cached locally.
Pixel-art clock
A 5×7 glyph table rendered to a canvas, with vertical digit rolls and Turkish Ç/Ğ/İ/Ö/Ş/Ü included.
Time tools
Timer, stopwatch with laps, focus/break cycle and alarm, sharing one stage and one set of controls. Countdowns tick per second; the stopwatch is elapsed-time based, because it is the one readout showing hundredths, where drift would show. Completion changes the island's shape and plays a chime, and stays up until it is actually answered — a timer that finished while nobody was watching has not been acknowledged. A tool left running keeps a capsule on the collapsed pill, amber and quickening inside the last minute.
Synced lyrics
MPRIS has a lyrics field that essentially nothing fills in — Spotify reports it as an empty string — so lines come from LRCLIB, which needs no account or key. Fetched once per track and cached on disk, misses included, so a track without lyrics is not looked up again on every play. Nothing is fetched on the poll path.
Calls
An incoming call is recognised from the notification's own accept/decline actions, and answering or declining invokes those actions rather than faking input at the app. Whether a call is live is inferred from PipeWire instead — an app holding a playback and a capture stream at once is, by construction, mid-call — which is what makes the timer honest and covers calls answered on another device.
Inline reply
Notifications carrying an inline-reply action get a field and a send button, wired to the freedesktop reply signal.
English and Turkish
Switchable from a chip in the panel or over IPC, and remembered across restarts. With no saved choice it follows QS_ISLAND_LANG, then the session locale. Strings live in one file rather than inline, so nothing silently stays untranslated.
Device indicators
PipeWire microphone and V4L2 camera use raise a card the moment either starts or stops.
Meters
Volume, brightness and microphone gain as draggable segment bars, over wpctl and brightnessctl.
Per-app volume
One fader per app that makes noise, grouped by app rather than by stream — a browser with four tabs is one row.
Up next
The queue the player reports, as a panel you can read. Marked experimental because most players never report one.
Battery
Level, charge state and time-to-empty from sysfs and UPower, tucked into the collapsed pill.
Stays out of the way
Hides completely when the focused window goes fullscreen, and returns when it leaves.

Install

shell

$ git clone https://github.com/lunanoir21/quickshell-dynamic-island.git
$ cd quickshell-dynamic-island
$ chmod +x backend.sh
$ quickshell -p ./Main.qml

# or drop it into an existing config and instantiate
# DynamicIslandModule.DynamicIslandHost {}

Needs Quickshell 0.3+, a wlroots compositor with layer-shell, jq and a Nerd Font. playerctl, wpctl, brightnessctl, cava, curl (lyrics lyrics and browser thumbnails), pactl (call detection) and upower are all optional — anything missing leaves its section empty rather than failing.

Space play / pause ←→ previous / next Esc close

Two rules it is built on

Looping animations never bind straight to a visual property. Each one drives a resettable number and returns it to a known resting value when it stops. Writing SequentialAnimation on opacity with infinite loops freezes the property wherever the loop happened to be when its condition went false, which leaves half-faded rings and stalled bars on screen.

A Behavior is only for smoothing discrete input. It smooths cava's 30 Hz samples. Put one on a value that already changes every frame and it just retargets the animation before it can go anywhere, flattening the spectrum into a motionless row of stubs.