A lightweight macOS menu bar utility for picking and converting colors. It lives as an icon in the status bar, doesn't take up space in the Dock, and keeps a history of colors with conversion into 19 formats.
Download on the Mac App Store →
- Pick any pixel on screen using the system color sampler (
NSColorSampler) - Paste a color value in any supported format and convert it to another — converted entries are marked with a badge in the history
- History of colors, persisted between app launches
- Display format is chosen per history entry (a picker in each row), not globally for the whole list
- Copy a color's value by clicking the row or a dedicated button
- Clipboard is checked when the window opens: a recognized color value is prefilled into the input field automatically, anything else is ignored
- Newly added entries flash briefly so they're easy to spot
- Configurable default format for new entries, persisted across launches
- Resizable window; right-click the menu bar icon for a quick menu (Open / Quit), plus a standard App Menu with Quit (⌘Q)
| Category | Formats |
|---|---|
| Web / CSS | HEX, RGB, RGBA, HSL, HWB, OKLCH, CSS Variable |
| Color models | HSBA, CMYK, RGB Float (0-1) |
| Swift / Apple | SwiftUI Color, UIColor, UIColor (Objective-C syntax) |
| Numeric | Hex (no #), Hex Integer |
| Other developer-friendly formats | Kotlin-style, XML-style, Java AWT, and more |
Full list — ColorFormatType.swift. Every format works in both directions:
as display output, and as input the parser recognizes.
- macOS 13.0 (Ventura) or later — minimum deployment target due to modern AppKit APIs used
- Xcode 15+ (only needed if building from source — see below)
Easiest: download from the Mac App Store.
From source:
- Clone the repository:
git clone https://github.com/sashavoloshanov/CustomColorMaster.git
- Open
CustomColorMaster.xcodeprojin Xcode. - Cmd+R.
The app icon (an eyedropper) will appear in the menu bar. The app doesn't
show up in the Dock or Cmd+Tab — that's intentional (LSUIElement = YES).
- Click the eyedropper icon in the menu bar → the window opens.
- Choose Color → the window hides, the cursor turns into the system loupe → click any pixel on screen to capture its color.
- The window reappears automatically, with the new color at the top.
- Paste a color value into the input field at the top of the window (⌘V, the paste button, or right-click → Paste). If your clipboard already held a color when you opened the window, it's prefilled for you.
- Pick the target format in the dropdown next to the field.
- Convert → the result is added to the history, marked with a badge showing it came from manual input rather than the screen.
Values that aren't recognizable as colors are rejected rather than pasted, so unrelated clipboard text never ends up in the field.
Each row shows a swatch, the value in that row's current format, a dropdown to change the format for just that entry, and a copy button — clicking anywhere on the row copies too. Right-click a row to delete it, or use Clear to wipe the whole history.
The window's close and minimize buttons both just put the window away; bring it back by clicking the menu bar icon. To quit, right-click the icon and choose Quit Custom Color Master, or press ⌘Q while the app is active.
CustomColorMaster/
├── CustomColorMasterApp.swift — entry point, just an AppDelegate, no SwiftUI window scenes
├── AppDelegate.swift — builds the App Menu (Quit, Edit), creates StatusBarController
├── StatusBarController.swift — NSStatusItem, window visibility, Space handling, NSColorSampler
├── MainWindowController.swift — builds a fresh NSWindow on every open (see below)
├── AppSettings.swift — @Published defaultFormat, persisted via UserDefaults
├── Models/
│ ├── ColorFormatType.swift — enum of all formats + NSColor → text conversion
│ └── PickedColor.swift — a history entry; format and origin are stored on the entry
├── Services/
│ ├── ColorHistoryStore.swift — @Published history array, auto-saves to a JSON file
│ ├── ColorParser.swift — text → NSColor, the inverse of ColorFormatType
│ └── ColorSamplerService.swift — thin wrapper around NSColorSampler
└── Views/
├── ColorHistoryListView.swift — window content: header, input section, history list
├── ColorInputView.swift — paste field, target format picker, Convert button
├── ColorRowView.swift — a single row: swatch + value + format picker + copy
└── NativeTextField.swift — AppKit-backed text field with layout-independent shortcuts
The first version used SwiftUI's MenuBarExtra(.window). The problem:
MenuBarExtra automatically closes its window as soon as the app resigns
active — which is exactly what NSColorSampler does the moment it's
invoked. Result: the window closed (and even interrupted the sampler
itself) before the user could click a pixel.
The fix is a plain NSStatusItem plus a manually managed NSWindow:
StatusBarController hides the window before starting NSColorSampler
and shows it again once the sample completes.
Reusing one NSWindow turned out to be unworkable across Spaces: the
window server keeps a permanent Space assignment for a window, so showing
it again would drag the user back to whichever Space it was first opened
on. MainWindowController therefore destroys and rebuilds the window each
time it's shown — a brand-new window has no prior assignment and simply
appears where the user currently is. The window size is carried over so
resizing isn't lost. Switching Spaces hides the window, so the next click
on the menu bar icon always opens it fresh on the current Space.
NSTextField shortcuts are handled in performKeyEquivalent(with:) and
matched on event.keyCode rather than charactersIgnoringModifiers. The
latter returns the character for the current keyboard layout, so on a
Cyrillic layout ⌘V arrives as "м" and never matches a "v" comparison —
key codes are layout-independent and always match.
Color history is stored as a JSON file:
~/Library/Application Support/<bundle id>/colorHistory.json
Writes are debounced (0.3s), and history is capped at 200 entries
(ColorHistoryStore.historyLimit, easy to change or remove).
The default format for new entries is stored separately in UserDefaults
(AppSettings).
PickedColor.format is a plain model field. Changing the format on one
row (ColorHistoryStore.updateFormat(id:to:)) affects only that entry and
is persisted with it in the JSON file — the rest of the history keeps its
own formats. PickedColor.isConverted works the same way, recording
whether an entry came from the screen or from manual input.
Uses NSColorSampler, no additional permissions required.