Live widgets, app groups and folders in a strip that sits beside Apple's Dock, or stands in for it.
A real shelf, running. System activity, an hourly forecast and a note on the left, apps on the right, sized and placed from the real Dock's own settings.
Docket exists because of Dockset, a genuinely lovely macOS app built and sold by an independent developer. Dockset is the original: it is the app that worked out what a Dock shelf should feel like, and it is polished, supported and worth paying for. If you want the real article, go and buy it. That is the version with a person behind it who will answer your email.
Docket is an independent reimplementation, written from scratch as a homage and a way of learning how something like this is actually built. It is a hobby project with rough edges. It is not affiliated with, endorsed by, sponsored by or connected to Dockset, it is not "the free version" of it, and it is not trying to replace or compete with it. No Dockset code, artwork, screenshots, copy or branding is used here.
The full attribution, including the two specific design decisions this project learned by looking at Dockset, is in NOTICE.md.
Widgets, then the grip that resizes the shelf, then apps. Every widget in this shot is a live reading taken at the moment of capture.
Click a widget and a panel opens, anchored to the tile it came from with a tail pointing back at it. Twenty-three of the twenty-four widget kinds have one. Dismiss with a click outside, Escape, or Command-W.
Every panel below is a real view of the real thing, not a mockup. Each caption says what its panel is showing, and says so plainly where that is the widget library's sample data rather than a live reading.
![]() Clock Live. This machine's own clock, its time zone and the day's date. |
![]() World Clock Live. The real overlap between here and Tokyo, worked out by the zone's own rules. |
![]() Time Progress Live. How far through the day, the month and the year it had got by then. |
![]() Weather Live. A real MET Norway forecast for Oslo, hour by hour for twelve hours. |
![]() Stock Live. A real quote, fetched from the network when the panel opened. |
![]() Watchlist Live. Several symbols read off the same single fetch, shown as one list. |
![]() Focus Timer Live, and genuinely running. Start it and stop it from the panel itself. |
![]() Countdown Live. A real deadline, with the time left counted against the clock. |
![]() Stopwatch Live and running. The elapsed figure is real, not a posed number. |
![]() Alarm Live. The time remaining is computed against the real system clock. |
![]() Hydration Live, with nothing logged yet, which is the state a new widget sits in. |
![]() Sticky Note Typed in place. The words are the widget's own content, not a reading. |
![]() System Activity Live CPU, memory and disk, with the graph a genuinely sampled minute. |
![]() Network Activity Sample data. The sampler is real, but an idle machine draws a flat line that shows nothing. |
![]() Battery Sample data. A live read would list this machine's own accessories by name. |
![]() AirDrop Live. Read straight from sharingd's own discoverability setting.
|
![]() Stripe The empty state, on purpose. This build ships no client, so it says so. See Known limits. |
Calendar, Reminders and Now Playing have full panels in the app and are not pictured here. All three need a consent grant that belongs to a bundled app, and the capture tool is not one, so their panels would show a permission line and nothing else.
Every choice that changes how something looks is shown as a picture of the result: the setup, the shelf's material over a scrap of wallpaper, its edge, the appearance. Layouts are listed with the icons of what is in them.
|
|
- Follows your real Dock instead of copying it. Size, edge, magnification
and auto-hide are read live from
com.apple.dock, and the shelf subscribes to thecom.apple.dock.prefchangednotification the Dock posts when any of them change. Drag the size slider in System Settings and the shelf moves with it. Pinned apps are mirrored the same way. Resize the shelf by dragging and only the size stops following; the edge and the hiding still do. - Twenty-four widget kinds, twenty of which draw a live tile today: clock, world clock, stopwatch, focus timer, time progress, countdown, alarm, hydration, sticky notes, calendar, reminders, battery, system activity, network, AirDrop, stocks, watchlist, weather, Shortcut and Now Playing. Twenty-three open a detail panel. They are usable, not just readable: timers start and stop, notes are typed in place, the alarm toggles, a shortcut runs, and the metric and symbol tiles page through what they show.
- App groups. Drop one icon onto another and hold, the way iOS makes a folder. Tinted, named, with a 2x2 preview grid in a single Dock slot, opening into a panel you can drag icons back out of. A group left holding one item gives way to that icon.
- And the rest. Overflow scrolling with the trackpad, drag to reorder
anywhere in the row, the launch bounce, folders and files alongside apps, and
saved layouts that can be written back to Apple's real Dock: only
persistent-apps, never your stacks or recents, with verification and a rollback if the Dock comes back wrong.
Two decisions that were not obvious
Magnification uses Apple's own curve. Geometry.influence is a raised-cosine
falloff, (1 + cos(πd/r)) / 2: 1.0 under the pointer, 0 at the radius, and, the
part that matters, zero slope at both ends, so there is no visible seam where
the effect stops. A linear ramp makes neighbours lurch as the pointer crosses
them. A gaussian never quite reaches zero, so icons three tiles away drift for no
reason. Peak and radius come from your own largesize and tilesize rather than
from a constant.
Now Playing scripts the players, because the framework is gated. The obvious
route is MediaRemote, and it has been entitlement-gated since macOS 15.4: it
loads, the symbols resolve, and it returns nothing to an unsigned caller. So
Docket asks the players directly over Apple Events. Spotify and Apple Music
first, then whatever a scriptable browser tab is playing (Safari, Brave, Helium
and Dia; Firefox ships no scripting dictionary at all, so there is nothing there
to address). Browsers are addressed by bundle identifier, never by name, because
application "Brave Browser" can resolve to a fork that inherited the
LaunchServices name. Nothing here ever launches a player: an app that is not
already running is skipped.
A browser tab gets a picture from wherever it has one. Any site is read, not a list of known ones, and plenty of players publish no artwork at all: Netflix's has no media session, no poster and no preview image. So the picture comes from the first of the page's session artwork, its video poster, its link-preview image, the public copy of the page (which is how Netflix gets the show's name and poster), a still of the video where it is not DRM, and last the site's own icon. A page with no session only counts once its video is audible and has been played, so a muted hero loop is never mistaken for something playing.
Grab the latest .zip from the
Releases page, unzip it and
move Docket.app to /Applications.
Builds are unsigned and not notarised, because notarisation needs a paid Apple Developer account. macOS 26 will refuse the first launch with a dialog saying it cannot verify the app is free of malware. The way through:
- Double-click the app, get the refusal, dismiss it.
- System Settings > Privacy & Security > Security, where there is now an Open Anyway button for Docket.
- Click it, authenticate, launch again and confirm.
Control-click > Open stopped working for this in Sequoia and is not the route on
macOS 26. From a terminal instead:
xattr -dr com.apple.quarantine /Applications/Docket.app.
On first run, look up. Docket is an LSUIElement accessory app: no Dock tile
and no window of its own. Its only permanent UI is the status item in the menu
bar, which is where settings, the widget library and the profile switcher live.
Requires macOS 26 and Xcode. The Xcode project is generated from project.yml
and is not in the tree:
brew install xcodegen
xcodegen generate
xcodebuild -scheme Docket -configuration Release buildproject.yml pins a specific signing identity and team. Replace
CODE_SIGN_IDENTITY and DEVELOPMENT_TEAM with your own, or build ad-hoc with
CODE_SIGN_IDENTITY: "-", noting that macOS will not offer location consent to
an ad-hoc binary at all, because there is no stable identity to attach the grant
to. CONTRIBUTING.md has the details.
347 tests, and almost all of them exist because something broke.
xcodegen generate
xcodebuild -scheme DocketTests -configuration Debug testThe coordinate conversions have the most coverage, because that was the most repeated defect: the shelf sits centred inside a panel longer than itself, and converting between a screen position and a position along the row needs that inset. The hover label pointed at the wrong icon, then the drop gap opened away from the pointer, then the two drifted apart again. The suite asserts the round trip, so a slot centre taken out and brought back must return that slot.
InteractionTests deliver real NSEvents to a window. The worst defects there
were all "the click never arrived", and none were visible in the layout maths: an
empty content shape removed every widget's controls from hit testing, a
borderless panel could not take a keystroke, and a non-key window swallowed the
first press. No assertion on a value catches any of that.
How the source is laid out
Sources/
App/ AppState, the single source of truth: persisted state, the one
1s tick every time-based widget reads, and the only path that
writes Apple's Dock. Plus the entry point and window owners.
Core/ Pure value types and maths. Geometry (layout, magnification,
springs), Models, Persistence, and the Dock tile encoding. No
AppKit state, so it is the part the tests compile directly.
DockPanel/ The shelf: the NSPanel and its controller, the SwiftUI row,
tiles, drag and drop, group windows, tooltips, the vibrancy
background, and the scroll relay.
Widgets/ The catalog, the tile for each kind (Kinds/), the detail panel
for each kind (Details/), and the shared chrome.
Services/ Everything that talks outward: system metrics, battery, network,
weather, stocks, music, browsers, the app catalog, location, and
the com.apple.dock reader.
MenuBar/ The status item, its menu, and the settings surfaces.
NativeDock/ The actor that reads and writes Apple's real Dock.
How a click on a widget reaches its panel:
flowchart LR
A[Click on a tile] --> B[DockShelfView<br/>activate]
B --> C{WidgetDetail.exists<br/>for this kind?}
C -->|no| D[Open the widget's app<br/>or Settings pane]
C -->|yes| E[WidgetDetailWindow.toggle<br/>instance, context, anchor]
E --> F[NSPanel positioned against<br/>the tile, clamped to screen]
F --> G[WidgetDetailChrome<br/>popover material + tail]
G --> H[The kind's own Detail view]
E -.->|outside click / Esc / Cmd-W| I[close]
One structural rule worth knowing: types that need testing live in files the
test target compiles. DocketTests builds Sources/Core plus four named files
rather than hosting the app, which is why KeyablePanel lives in
DockLayout.swift. A break in Sources/App, Sources/Widgets or
Sources/DockPanel will never reach the suite, so CI builds the app and runs the
tests as two separate steps.
- Four widget kinds cannot be added. AI usage, Stripe, Paddle and Shopify are declared kinds the library does not offer, so none of them can be put on a shelf in this build. Stripe, Paddle and Shopify do have working detail panels behind them; AI usage does not. All four fall back to a labelled "not yet" tile, which nothing currently reaches.
- The revenue widgets have no service behind them. Stripe, Paddle and Shopify are all network-backed and this build ships no client for any of them. Their panel deliberately states that it is unconnected rather than drawing a plausible figure. A made-up amount under a real account name is indistinguishable from a working widget, and that is the one failure a money readout cannot afford. The panel pictured above is that empty state.
- Location consent is not reachable. The weather widget asks CoreLocation for
your city; macOS does not present the prompt to an accessory app with no
ordinary window, and promoting the app to
.regularfirst, which is what makes the Apple Events prompt appear, does not change it. Set a city in Settings > Widgets instead. - Apple Events need consent. Reading Spotify, Music or a browser tab requires approving Docket under Privacy & Security > Automation, and the browser fallback additionally needs "Allow JavaScript from Apple Events" in that browser's Develop menu.
- Removal uses a deprecated API on purpose.
NSAnimationEffectis the Dock's own poof, and Apple withdrew it suggesting a cursor as the replacement. It still renders, and a real poof beats a hand-drawn puff of smoke. - Builds are unsigned. Every download walks the Privacy & Security path above. Building from source with your own certificate is the way around it.
Bug reports and pull requests are welcome. CONTRIBUTING.md covers the build, the signing situation, what the test target can and cannot see, and the house rules, the main one being that comments explain why, never what.
- Dockset, the original, and the reason this exists. Please go and buy it. Full attribution in NOTICE.md.
- Weather data from MET Norway, used under CC BY 4.0.
- Built with XcodeGen.
- No bundled dependencies. Everything else is Apple's own frameworks.
MIT. Third-party attributions and trademark notices are in NOTICE.md.



















