TableFlip v1.4.1 Released
TableFlip v1.4.1 passed App Store review and is now released:
- Improved: CSV importing and exporting compatibility: better handling of Excel export data, and quoted cell contents.
- Fixed: Edit > Copy Row didn’t copy the selected row properly.
- Fixed: Crash when copying a CSV-imported table as LaTeX or Markdown.
- Changed: Notifications are now off by default.
In my original draft for this post I wrote this:
Direct customers got the update on Monday already.
This plan was somewhat sort of holding up, but it was actually last week that I intended to publish it. And direct customers need to download the latest version manually, because I broke the auto-updater in the previous version.
It turned out I screwed up the transition to Sparkle 2.x in TableFlip and forgot to configure how the installer XPC helper is supposed to run. TableFlip is actually my only sandboxed app, so the process is a bit different, and I didn’t realize that until last week.
Everything’s been fixed and tested to the best of my new-found knowledge.
This is always an embarrasing moment.
It’s not the first time in the past 10 years I broke an updater. I believe it was the WordCounter which under some still unknown-to-me circumstances would not download the Sparkle updater feed at all. That was 6+ years ago, so I do hope that these users eventually downloaded a newer version manually. – Opposed to the TableFlip fiasco of this week, without the update feed downloading at all, there’s no way to show messages to existing users. There is a way to communicate with existing customers, of course: since I have their email addresses from their order, I can reach out if needed.
PSA: FileManager.trashItem (Maybe) Uses NSFileCoordinator Under the Hood Automatically, So You Shouldn't to Prevent Deadlocks
Users of The Archive and Dropbox have reported issues with deleting files in their Dropbox-managed folders in the past weeks: the app would beachball forever. Apparently, Dropbox’s recent migration from ~/Dropbox
to ~/Library/CloudStorage
affects this. I had the occasional Google Drive user in the past months report similar issues but couldn’t make much sense of it – until now.
iCloud Drive works fine, by the way. So only 3rd party providers are affected?
The short version is this:
When you use FileManager.trashItem(at:resultingItemURL:)
, the FileManager
maybe automatically coordinates the trash operation (which is a file move/rename) on its own, so you could deadlock your app by coordinating access to the same URL twice (the outer call never relinquishes the “lock”; this, at least, is my impression after following Soroush Khanlou’s experiments from 2019).
Why “maybe”? Because I couldn’t reproduce the internal method calls from another dev’s explanation (see below).
Update 2023-03-02: Dimitar Nestorov of MusicBar suggested to use regular expression breakpoints: breakpoint set -r '\[NSFileCoordinator .*\]$'
– and I could see that no NSFileCoordinator
method was called during the trashItem
call. So it’s still a mystery why this doesn’t work.
Detailed Problem Description
To reproduce the problem: Wrap calls to FileManager.trashItem
in NSFileCoordinator.coordinate
blocks and use a Dropbox-managed folder.
It appears like you should not coordinate trashing files since macOS 10.15: In the Apple Developer forums, people have reported that since macOS Catalina (10.15), wrapping a trashing operation in a coordinate
block doesn’t work anymore for them, even though it worked before. One dev’s stack trace inspection brought up that trashing internally coordinates file access, too.
I failed to make a symbolic breakpoint on various NSFileCoordinator
methods trigger during the call to trashItem
, so I cannot verify the claims from the dev forums.
The reason for this could be that I’m not on iOS, and/or not using NSDocument
-based APIs.
I’m also not fast enough to disassemble anything and check what happens there, so if you know a way to inspect this further, please do tell!
Either way, stopping to wrap the call to FileManager.trashItem(at:resultingItemURL:)
in a coordinated file access block fixed the deadlock, so the explanation makes sense, even though I cannot find an automatic call to NSFileCoordinator
, yet.
Since removing a file in a coordination block works fine, I’m sticking with a fallback of this form for macOS 10.15+ (and keep a call to both inside the coordinate
block for older macOS’s):
do {
try fileManager.trashItem(at: url, resultingItemURL: nil)
} catch {
log.error("Could not trash item. Resorting to permanent deletion.\n(\(error))")
try NSFileCoordinator().coordinate(writingItemAt: url, options: .forDeleting) { url in
do {
try fileManager.removeItem(at: url)
} catch {
log.error("Could not delete item directly, either.\n(\(error))")
throw error
}
}
}
The Result
-based coordinate call is implemented here:
extension NSFileCoordinator {
/// Result-based wrapper around the default `coordinate` implementation.
/// - returns: `.success(())` when the writing was completed without error. Forwards the error throws by `writer`
/// or the error from the base `NSFileCoordinator.coordinate` implementation.
private func coordinate(writingItemAt url: URL,
options: NSFileCoordinator.WritingOptions = [],
byAccessor writer: (URL) throws -> Void)
-> Result<Void, Error> {
var coordinatorError: NSError?
var blockResult: Result<Void, Error>?
self.coordinate(writingItemAt: url, options: options, error: &coordinatorError) { url in
do {
try writer(url)
blockResult = .success(())
} catch let error {
blockResult = .failure(error)
}
}
return coordinatorError.map { Result<Void, Error>(error: $0 as Swift.Error) }
?? blockResult
?? .failure("Unhandled case: NSFileCoordinator.coordinate did not fail and write block did never run")
}
/// Throwing wrapper around the default `coordinate` implementation.
/// - throws: Forwards the error throws by `writer` or the error from the base `NSFileCoordinator.coordinate` implementation.
func coordinate(writingItemAt url: URL,
options: NSFileCoordinator.WritingOptions = [],
byAccessor writer: (URL) throws -> Void) throws {
return try coordinate(writingItemAt: url, options: options, byAccessor: writer).get()
}
}
Xcode Requires A Lot of Data for Swift Package Resolution

This picture shows one of the weird annoyances with Xcode and Swift packages.
One package resolution step swallowed 45MB of my data.
You’ve likely heard it elsewhere: when iOS developers need to work with Swift packages and Xcode but have a shoddy internet connection, tough luck! Xcode will fail to build because it’s “Resolving Packages” step inevitably fails.
Today, our internet was out for the first half of the day, so I had to tether to my iPad to actually get Xcode to work at all. Rebasing was the death blow to the package cache; but even after this initial package fetching, I managed to amass a throughput of almost 200MB in maybe 3 hours of development work.
I would like to think that a SwiftPM-managed project, where you could check in the .swiftpm/
directory if you need, would perform better in that regard.
Does Xcode not grab a shallow clone, maybe?
The packages in this project:
Sparkle
sindresorhus/Preferences
glebd/CocoaFob
- 9 packages of my own making with almost no commit history worth mentioning
I could see how fetching the whole of Sparkle can amount to more data than the others.
Getting to Know Jetpack Compose by Comparing Concepts with SwiftUI (Markus Müller's CocoaHeads Leipzig Presentation)
Markus Müller (@m_mlr) recently did a presentation at CocoaHeads Leipzig about Jetpack Compose. – Thanks for the inspiring presentation, Markus! I learned a lot.
Update 2023-01-24: Check out the sample project on GitHub!
The similarities are interesting, but the differences in the build tools was also striking. The JetBrains IDE did a great job at hiding noise and presenting useful information, especially when it comes to locating and then auto-import
ing the various modules that comprise a modern Android app written in Kotlin and using Jetpack Compose.
I took the chance to try a Boox Nova Air 2 (affiliate link) for monochrome, multi-layered “sketchnotes” (more on that in a future post):

My personal highlights follow – please bear in mind this is all hearsay and based on a presentation + looking at some sample code a couple months ago, so I’m by no means an expert! The purpose of this list is to mention important concepts and provide links for further reading.
- To make UI components remember their state, you need to look into “state hoisting” for view serialization
-
Scary term for a simple concept:
In Jetpack Compose, state hoisting means moving the state of a composable to its parent (caller). It helps us to reuse the composables. (Source)
This is actually not dis-similar to how a SwiftUI view has a
@State
, and its children only get the derived observables. - On (very) old Android API versions, text fields by default used to forget their contents when you rotated the devices vigorously. Ouch.
- Hoisting via a “remember Saveable” is similar to
Codable
in Swift. But applied to UI components. - Unidirectional flow approaches (think: Redux, ReSwift, The Composable Architecture/TCA) can help remove the problem for you. If the component isn’t the state’s source of truth, you can’t lose its state, right? (This could actually be an over-simplification, I don’t know from experience.)
-
- Where SwiftUI implements view modifiers as chained method calls, figuring out which modifier works where through the power of protocols, Jetpack Compose’s components provide run-of-the-mill injectable
Modifier
objects.- Modifiers in SwiftUI chain on the view object;
- Modifiers in Jetpack Compose chain the view input.
- Potential upside: no surprises when you change the modifier order. You can lose the type information of
SwiftUI.Text
-specific modifiers when you use general-purposeSwiftUI.View
modifiers in some circumstances. That’s what I’m thinking of.
-
StateFlow
andMutableStateFlow
unify the concepts and benefits that SwiftUI gets from Combine publishers and structured concurrency (async/await). – Don’t ask me how, that’s just what I took note of :) -
Theming is interesting! The
App
object is initialized inside theTheme
:Theme { App { ... } }
That sounds odd at first, but it makes sense that an app lives in the context of a global app theme. Theme changes affect all of the app. We don’t have that sort of theming in iOS and instead tweak the default look and feel of components with e.g. (shared) view modifiers. As an upside, SwiftUI view modifiers can affect a whole view sub-tree; it looked like you need to tell some Compose view components how to theme themselves a bit more when you want to deviate from the theme’s defaults.
-
The SwiftUI documentation pales in comparison. Apple docs imporoved in recent years, but it’s not fair how much better the Jetpack docs are.
- The Jetpack Compose documentation is live-filterable,
- a lot of pages come with sample project links,
- the sample projects reside on GitHub,
- and a huge focus is to get the idea across in a way that a new developer would understand it.
- High-level overviews and samples take precedence over (rather boring) API method listings.
- Jetpack Compose is an external library with a dedicated life-cycle, not tied to Android OS version upgrades. I wish SwiftUI were that flexible.
Dictionary.init to Make Use of Swift.Identifiable
In my conceptual post about equality vs identity, I mentioned Helge Heß’ advice to use a Dictionary
with an ID as key, and the identified object as value when you would (wrongly) reach for Set
and Hashable
.
I found a code snippet for this from late last year just for this!
The initializer Dictionary.init(_:uniquingKeysWith:)
takes a sequence of (Key, Value)
tuples and a closure to resolve duplicate key conflicts.
The following code makes use of the Identifiable
protocol to infer the Key
to be the ID
. The call site becomes:
let foos: [Foo] = ...
let dict: [Foo.ID : Foo] =
Dictionary(foos, uniquingKeysWith: { _, last in last })
You could also argue for uniquing with { first, _ in first }
to discard subsequent objects with the same ID and keep the initial one instead.
And here’s the declaration:
extension Dictionary
where Value: Identifiable, Value.ID == Key {
@inlinable init<S>(
_ elements: S,
uniquingKeysWith combine: (Value, Value) throws -> Value
) rethrows where S : Sequence, S.Element == Value {
try self.init(
elements.map { ($0.id, $0) },
uniquingKeysWith: combine)
}
}
The Difference between Entity and Value Object, and How They Relate to Swift's Identifiable and Equatable Protocols
Helge Heß recently posted on Mastodon that he “still find[s] it disturbing how many #SwiftLang devs implement Equatable
as something that breaks the Equatable contract, just to please some API requiring it. Feels like they usually should implement Identifiable
and build on top of that instead.”
An interesting Swift piece he shared is to not slap Hashable
onto a type just to reap the benefits of Set
logic and uniqueness in collections. This usually starts by conforming to Hashable
but then only pick one or a few properties for the hash computation. That’s basically a code smell. Consider to use an identifier that’s hashable instead if you want to “hash by ID” anyway:
struct Foo: Identifiable {
let id: UUID
let otherPropertyYouWantToIgnore: String = ...
// ...
}
// Set<Foo> then becomes:
var foos: [Foo.ID : Foo] = [...]
Never thought of that, so kudos!
Update 2023-01-18: Here’s a Dictionary.init
extension to simplify getting these instead of Set
s.
But I really want to bring attention to the abuse of Equatable
he mentioned.
To me, it looks like users of the Swift language may be conflating the following:
-
Value Object and value type (e.g.
struct
andenum
), and -
Entity and reference type (e.g.
class
andactor
).
The capitalized ones are concepts – the rest is a language construct. (I encountered the most succinct and sensible definitions of these concepts in Domain-Driven Design, but they are older.)
Entities have some kind of identity over time. In practice, this is often accompanyied by some kind of persistence. You fetch the same SQL database table row 10 times, but it’s supposed to be the same thing, no matter if the type of the resulting object in main memory has a reference type or value type. This applies to e.g. customers and products, or posts and tweets.
Value Objects, on the other hand, are considered to be equal in virtue of their data. If you have two Value Objects in your app’s memory with the same attributes (in Swift parlance: properties), then they are the same thing. This is used for stuff like money/currency, time, addresses, and names.
In Swift, i.e. in code and not in land of concepts, you can get “identity” of two objects by using the Identifiable
protocol, no matter if it’s used on a struct
or class
.
You make different objects of the same type equal via the Equatable
protocol, also no matter if used on a struct
or class
.
We had Value Objects in Objective-C, a language without the value types we know from Swift, by overriding isEqual:
to make the NSObject
subclasses compare their objects not by memory address, but by their properties. In other words, we’d ignore if two references were identical objects in memory or not.
We also had Entities in Objective-C. You could get by with memory address chcks, but if you were serious, you had some database anyway and thus would have access to an Entity ID of sorts. Could be an auto-incrementing SQLite table id, for example.
Conversely, you can have struct
s (a value type) represent Value Objects or Entities.
struct PersonName: Equatable {
let firstName: String
let middleName: String?
let lastName: String
}
struct Person: Identifiable {
let id: Int
let name: PersonName
}
With this definition, it’s tempting to slap Equatable
onto Person
as well because you want to test if personA
and personB
are different. The equality operator ==
feels like a sensible choice, so you’d start with this:
extension Person: Equatable {
static func == (lhs: Person, rhs: Person) -> Bool {
lhs.id == rhs.id
}
}
This correctly omits the name
from the equality test.
It (probably incorrectly) also makes two instances of Person
equatable – for the convenience of comparing two objects – while it’d have sufficed to use this in call sites:
personA.id == personB.id
This does the same thing but does drive home that you’re dealing with Entities that define same-ness via their ID and ignore the attributes.
Conforming to Equatable
arguable improves the ergonomics when you type comparisons:
personA.id == personB.id
// vs
personA == personB
But the second approach also leaves to guesswork what kind of domain concept you’re trying to express. And then you cannot check if the attributes of the Person
instances are also equal.
That’s the delineation of “equal” vs “identical”. Equal in terms of data, identical in terms of it being an Entity.
Let’s get back to the false conflation of “Value Object” and “value type”: once you find yourself using Equatable
(or Hashable
) in a struct
(a value type) and then also limit it to a small subset of properties to express when two instances should be considered identical, you maybe (ab-)using equatability to express identity. Try to use Identifiable
instead.
If you care about modeling a rich domain, consider these rules of thumb:
- Express identity/same-ness of an Entity via the
Identifiable
protocol; - Express same-ness of Value Objects via their data using the
Equatable
protocol; - Instead of (ab-)using
Hashable
to getSet
semantics for cheap uniqueness on a subset of properties, use a hashable identifier instead. ReplaceSet<Foo>
withDictionary<Foo.ID, Foo>
.
By the way, when Swift value objects came along and it became so simple to implement Equatable
by selecting just the identifier, I fell for this exact trap. There’s still code in my apps that abuses the ==
operator when I should’ve compared their identifiers. So thanks to Helge for bringing clarity by talking about the more recent addition of Identifiable
to the Swift language a bit more!
woolsweater's SF Symbols Modeline
After discovering how to use SF Symbols in Emacs on Mac, folks on Reddit shared links where Josh Caswell (known by his handle “woolsweater”) did stuff like this ages ago.

Check out his DOOM Emacs compatible, doom-modeline
based SF Symbols iconification in sfsymbols-modeline.el.
Use San Francisco Font for SF Symbols Everywhere in Emacs
Today I learned that you can tell Emacs to use fontsets (or just “fonts”) for specific character ranges. Thanks to Alan at idiocy.org for the explanation!
His example is this:
(set-fontset-font t '(?😊 . ?😎) "Segoe UI Emoji")
Now instead of Emoji, which work fine in Emacs for me out of the box, I want SF Symbols. Yes, again.
SF Symbols are in the Unicode private use area-b, at the very end of the Unicode table, U+100000..U+10FFFD
.
I have no clue how to get the first and last of them; so I just map the whole private use area-b to the San Francisco font SF Pro Display, which shows SF Symbols just nicely.
To replace the Emoji characters from Alan’s example with these un-typable unicode characters, rely on their hex code range: insert-char
accepts the hexadecimal codes directly even though the characters are not part of the preview. So use M-x insert-char 100000
and M-x insert-char 10FFFD
.
The result (you can copy-paste, but won’t see the symbols here):
(set-fontset-font t '(? . ?) "SF Pro Display")
And that’s it!
I can now revert the font overrides in my tab bar modifications.
And as a bonus, I now actually see the symbols in the Elisp code (which was previously not possible at all):

So wherever I paste a SF Symbol, I now see the symbol. Mode-line, tab-bar, code, text, it doesn’t matter.
Update 2023-01-08: Álvaro Ramírez uses a fallback font instead. So no need to specify the character range. This might suffice, too, if you don’t mind other glyphs being taken from the fallback font in case your main font misses some arcane special character. Check out Álvaro’s post for a GIF demo, too.
Display a Random Inspiring Quote for Journaling or Shell Prompts
The odd title gives it away – I don’t have a good use-case for this :)
Dr. Drang shared how he displayed one random line of “Oblique Strategies” in his email signatures many years ago.
The “Oblique Strategies” can be found here: http://www.rtqe.net/ObliqueStrategies/Edition1-3.html
They look like this:
Abandon normal instruments Accept advice Accretion A line has two sides Allow an easement (an easement is the abandonment of a stricture) Always first steps
“Fortunes” or inspirational “quotes for thought” can work as journaling prompts. It’s also possible to start a new shell session with this “fortune cookie” format as a greeting or Message of the Day.
I’ve formatted the file thus:
- Treat
#
at the beginning of a line to be a comment (referencing the source(s)) - Assuming
~/fortunes.txt
as the location
Bash script, e.g. for MotD updates
Bash is simple. Filter out comments, then use shuf
to get 1 random line:
$ grep -e "^[^#]" ~/fortunes.txt | shuf -n 1
That prints any line from the file. Easy. Use cron
to replace the Message of the Day in /etc/motd
and then you have an inspirational greeting in the shell.
Elisp random fortune function for journaling
To get a random prompt in my Emacs org-mode journaling templates was a bit more involved. Instead of piping in the shell, I’m using “the Emacs way”, i.e. applying modifications to invisible, temporary buffers with the file contents:
(defun ct/random-fortune (&optional file)
"From a fortunes file, produce a random line.
Prints the line to the minibuffer when called interactively,
returns the line otherwise.
When FILE is not nil, read that file. Otherwise, defaults to '~/fortunes.txt'.
Ignores lines that start with a hash \"#\"."
(interactive)
(let (($random-line (with-temp-buffer
(insert-file-contents "~/fortunes.txt")
(keep-lines "^[^#]")
(goto-line (1+ (random (count-lines (point-min) (point-max)))))
(buffer-substring-no-properties (line-beginning-position) (line-end-position)))))
;; 22.4 Distinguish Interactive Calls <https://www.gnu.org/software/emacs/manual/html_node/elisp/Distinguish-Interactive.html>
(if (called-interactively-p 'any)
(message "%s" $random-line)
$random-line)))
This either “"”inspires””” me interactively, by showing the line at the bottom of the window, or when called programmatically, it returns the string for insertion into the meditative journaling template.
That all presupposes I actually have such a template or perform a practice like this, but in reality, this was just a short and fun thing to do between holidays, really.
Maybe your journaling practice benefits from this, though!
Migrating to modus-themes v4 and Going Through the Changes
New year marks the day Protesilaos releases modus-themes v4 into the wild; and my package update from MELPA already ingested preliminary changes on Friday Dec 30th (much to my chagrin, because I initially wanted to do something else than fiddling with my Emacs setup) that were absolutely not backwards compatible.
While I migrated my configuration over, I reported some issues, which were promptly fixed, and after waiting for a couple days, this post is what’s left of my v3-to-v4 migration without any temporary workarounds.
A lot of the old configuration options are gone; instead of many defvar
s (i.e. “state” that affects theme loading) to affect e.g. the color of code blocks, the selection region, or buffer fringes, we’re now interacting with a large option list instead and declare what we want (i.e. one data source for the truth about the theme). That change means we all need to figure out which old setting now are affected by which names color, and then sprinkle some hooks on top to use named colors for our own purposes.
This is not a migration guide by any means, but it could help you understand how the themes now work by following along. The v4 documentation is thorough, but for a first read, you need to keep a lot of stuff in your head. I hope this eases that a bit.
New themes, and how to load them
The modus-themes package comes with 6 themes:
- Light
modus-operandi
modus-operandi-deuteranopia
modus-operandi-tinted
- Dark
modus-vivendi
modus-vivendi-deuteranopia
modus-vivendi-tinted
I used to have the deuteranopia flag enabled, so I’m now picking the base theme modus-operandi-deuteranopia
. Same if you were >50% using tinted variants: use modus-operandi-tinted
. If in doubt, start with the default.
To declare that I want to use the deuteranopia-friendly dark and light variant e.g. when I use
(setq modus-themes-to-toggle '(modus-operandi-deuteranopia modus-vivendi-deuteranopia))
Before loading the theme (via built-in load-theme
, which replaces the old modus-themes-load-theme
that did some extra work), you need to set all your configuration options so they are available during the loading process. We’ll check these out in a minute, but this here is ultimately the last line in my theme config:
;; Replaces a call to (modus-themes-load-themes)
(load-theme (car modus-themes-to-toggle) t t)
Note that I could just as well write (load-theme 'modus-operandi-deuteranopia)
, but this way I can treat the modus-themes-to-toggle
variable as the source of truth. Hashtag DRY.
Heads up: While the load-theme
call above works anytime after I’ve confirmed the theme, it refuses to load the theme for the first time. I noticed that it wouldn’t load after a fresh start of Emacs. So I ran (load-theme 'modus-operandi-deuteranopia)
once and gave my approval; same for the vivendi variant. Then all is good. (I also had no luck with :no-confirm
parameter, by the way.)
So that’s the basic wireframe for how I load and configure the theme. With my actual use-package
call, this is the structrue:
(use-package modus-themes
:init
(setq modus-themes-to-toggle '(modus-operandi-deuteranopia modus-vivendi-deuteranopia))
;; ... setting all variable that need to
;; be present before loading the theme ...
:config
(load-theme (car modus-themes-to-toggle) t t)
:bind ("<f5>" . modus-themes-toggle))
Next, we’ll fill out the placeholder I put in the :init
section’s comments.
Configuration options
The simple configuration options for me that I kept and still like:
(setq modus-themes-italic-constructs t
modus-themes-bold-constructs t
modus-themes-variable-pitch-ui t
modus-themes-mixed-fonts t)
Most of the work is done by palettes and their overrides, that we’ll look at for the rest of this post. These affect all colors; but to affect bold constructs and font sizes, there are still a couple more variables that inform theme loading. These are listed as 5. Customization options in the docs. The ones I use are:
;; Color customizations
(setq modus-themes-prompts '(bold))
(setq modus-themes-completions nil)
(setq modus-themes-org-blocks 'gray-background)
;; Font sizes for titles and headings, including org
(setq modus-themes-headings '((1 . (light variable-pitch 1.5))
(agenda-date . (1.3))
(agenda-structure . (variable-pitch light 1.8))
(t . (medium))))
Only these simple variable settings did survive; the rest is trickier, because we need to understand the new (and very flexible and quite amazing) palettes.
Palette overrides
The “more power to the user” release comes with some cost: you need to understand how the new palettes work. Theres a whole section in the docs, 5.11 Palette Overrides: for each of the 6 themes, theres a -palette-overrides
variable you can use to inject your own styles that, well, override the default settings of the respective theme. The actual code that merges overrides with the defaults is this:
;; modus-operandi-deuteranopia-theme.el
(modus-themes-theme modus-operandi-deuteranopia
modus-operandi-deuteranopia-palette
modus-operandi-deuteranopia-palette-overrides)
Think of palettes are lists of color names and colors. If you peek into the modus-operandi-deuteranopia-theme.el
code, you’ll see it boils down to a very long list like this:
;; modus-operandi-deuteranopia-theme.el
(defconst modus-operandi-deuteranopia-palette
'((bg-main "#ffffff")
(bg-dim "#f0f0f0")
;; ...
))
The good news is that there are still many named colors, like bg-main
. These semantic colors can be used to style other pieces of Emacs to have the modus colors.
The bad news is getting to them is a bit more cumbersome if you need their values, but it works well and consistently. I’ll show some applications of this below.
Another upside is that some “settings” are part of this list, too, usually further down, and they can reference the more basic color definitions. One example is (fg-link blue-warmer)
which declared the link foreground color by referencing the blue-warmer
color setting from elsewhere in the file. This replaces old settings like (setq modus-themes-links '(neutral underline))
.
I didn’t use many of these, but I noticed the deprecation warnings to guide me through the process.
It’s hard to say in retrospect (as e.g. Tony Zorman pointed out) what e.g. the ‘neutrality’ setting used to do. You need to look up the hex colors and find close matches, is my guess. (After Tony’s email exchange, the v4 release candidate improved a lot, so your personal migration might go much smoother.)
For example, if you don’t like the theme/palette’s default link color and want something with less contrast, override fg-link
with blue-faint
instead of blue-warmer
, maybe.
So how do you do that?
If you want global overrides like the old modus-themes-links
setting, you use the shared palette overrides, called modus-themes-common-palette-overrides
. These should not use absolute hex colors but reference named colors instead. That way, you get the proper “faint blue” for the dark and light theme.
(setq modus-themes-common-palette-overrides
'(;; Globally use faint instead of warm blue for links
(fg-link blue-faint)
;; ... more overrides here ...
))
If you want to tweak just one particular palette, you choose one from the 6 palette override options like I mentioned above.
Here’s an example to change the inactive background color of the light deuteranopia theme:
(customize-set-variable 'modus-operandi-deuteranopia-palette-overrides
'(
;; More subtle gray for the inactive window and modeline
(bg-inactive "#efefef")
))
Setting a light gray like "#efefef"
wouldn’t work for the modus-vivendi variants.
Notice how I also actually use customize-set-variable
, not setq
: the variable modus-themes-custom-auto-reload
is enabled by default, which means when I change the palette via customize-set-variable
, the theme auto-reloads and shows the changes quickly. This is great to experiment with colors, so I recommend you use that, too.
At this point, we know how to
- pick the toggle-able themes, i.e. which dark and light one
F5 should switch between; - override color “pointers” (like
fg-link
) by referencing other named colors in the “common” palette; - override colors with absolute hex codes.
You can also use override presets (5.11.1 Palette override presets) to get back e.g. an overall “faint” look and feel. The documentation explains how to use that and even how to inherit from it while setting your own colors. Armed with that knowledge, my actual common palette settings were the following (“were”, because I removed inheriting from -faint
eventually to give the new colors a real chance):
(customize-set-variable
'modus-themes-common-palette-overrides
`(
;; Make the mode-line borderless and stand out less
(bg-mode-line-active bg-inactive)
(fg-mode-line-active fg-main)
(bg-mode-line-inactive bg-inactive)
(fg-mode-line-active fg-dim)
(border-mode-line-active bg-main)
(border-mode-line-inactive bg-inactive)
;; Inherit rest from faint style
,@modus-themes-preset-overrides-faint))
I’ll explain my choice for the mode-line color overrides in the next section.
Depending on when you read this, this will just work – on 2022-12-30 I reported an issue that the overrides wouldn’t merge properly. See there for a quick fix; I didn’t bother to put it here again because it was already fixed by Prot within the hour, but YMMV when you try to do crazy things with the themes.
Working with modus-themes color values
There’s no convenient way to get a single named color from the themes at the moment. The modus-themes-with-colors
macro is clever, but at the time of writing, the documentation is sparse and only shows how you can use it to produce a list, i.e. use the macro for reading the variable names. On the mailing list, Prot shared more examples and eventually included mode-line color overrides in the docs based on our email exchange, which I believe serves as a better example.
To understand the modus-themes-with-colors
macro, you need to feel somewhat comfortable with Elisp macros because you’ll be using lists eventually and need to escape variable names for the colors inside these.
Check out these links if you need a refresher for the backquoted `(...)
list form and how the comma splicing works within macro expressions:
- Emacs Lisp Manual, 10.4 Backquote: https://www.gnu.org/software/emacs/manual/html_node/elisp/Backquote.html
- https://lisp-journey.gitlab.io/blog/common-lisp-macros-by-example-tutorial/
With that, you can use theme color variables like so:
(modus-themes-with-colors
(set-face-attribute 'tab-bar nil :background bg-main))
The macro makes all theme variables like bg-main
available as locally bound variables.
The actual use within backquoted lists is a bit different, as you’ll see next.
Overriding the mode-line even more
Above, I shared my mode line color settings: commonly, I use the background color in the inactive window (to blend it in), and a light contrast in the active window. I tweaked the bg-inactive
color of the light theme to be even less dark, so it’s a more subtle tonal contrast. (And IMHO less ugly.)
These changes aren’t all, though. I also apply a box to the modeline to make it appear larger (and add whitespace around the text). The box should have the appropriate color to appear like an extension of the background, so I need to reference the mode-line background colors of the theme when adding a box.

With an improved implementation suggested by Prot, I’m now using this:
(defun ct/modus-themes-customize-mode-line ()
"Apply padding to mode-line via box that has the background color"
(modus-themes-with-colors
(custom-set-faces
`(mode-line ((,c :box (:line-width 10 :color ,bg-mode-line-active))))
`(mode-line-inactive ((,c :box (:line-width 10 :color ,bg-mode-line-inactive)))))))
(add-hook 'modus-themes-after-load-theme-hook #'ct/modus-themes-customize-mode-line)
You might wonder what ,c
means: it inlines the variable c
’s value. What is c
, then? Well, it’s locally bound in the modus-themes-theme
function, and its value is '((class color) (min-colors 256))
. This is actually scoping when the theme’s colors are applied: in a color environment with at least 256 colors available. There are no constructs in the modus-themes for e.g. terminals that only support 16 colors, but Emacs’s faces do support settings that are scoped to different environments (with t
as the fallback). So this tells Emacs to only use these faces if it makes sense, and as users we need to remember to prepend this scoping variable.
Overriding tab bar faces and colors
With that tool as a good stand-in for the old modus-themes-color
function, I could rewrite my tab-bar overrides to this form:
(defun ct/modus-themes-tab-bar-colors ()
"Override tab faces to have even less variety"
(modus-themes-with-colors
(custom-set-faces
`(tab-bar ((,c
:height 0.8
:background ,bg-main
:box nil)))
`(tab-bar-tab ((,c
:background ,bg-main
:underline (:color ,blue-intense :style line)
:box (:line-width 2 :style flat-button))))
`(tab-bar-tab-inactive ((,c
:background ,bg-main
:box (:line-width 2 :style flat-button)))))))
(add-hook 'modus-themes-after-load-theme-hook #'ct/modus-themes-tab-bar-colors)
If you recall my previous tab-bar related posts, you will notice I am now referencing the faces 'tab-bar
etc. directly instead of changing modus-*
tab bar colors.
There used to be custom modus-themes faces like modus-themes-tab-backdrop
. These are now gone in v4. In effect, they’re “inlined” when the merged palette specificiations are applied by the modus-themes-theme
call which I already showed above. It maps over a list of face names and applies their style; for tabs, this is what’s going on:
;; modus-themes.el
(defconst modus-themes-faces
'(
;; ...
`(tab-bar ((,c :inherit modus-themes-ui-variable-pitch :background ,bg-tab-bar)))
`(tab-bar-tab-group-current ((,c :inherit bold :background ,bg-tab-current :box (:line-width -2 :color ,bg-tab-current) :foreground ,fg-alt)))
`(tab-bar-tab-group-inactive ((,c :background ,bg-tab-bar :box (:line-width -2 :color ,bg-tab-bar) :foreground ,fg-alt)))
;; ...
)
"Face specs for use with `modus-themes-theme'.")
A downside of v4 in its current form is that I couldn’t find a way to change the :box
. So directly overriding the actual faces via set-face-attribute
was my best idea; and Prot’s suggestion to use custom-set-faces
made this even better. (The default application of boxes to the tab bar is “opt-out”, one could say, and requires more cumbersome overrides.)
So while I could use the palette variables to change the color of tabs, I couldn’t remove the box, and I personally opted for doing all overrides in my custom function instead of setting the palette colors for bg-tab-bar
and bg-tab-curent
first, and then still override the box declaration later. This way, it’s all in one place and easy to revert. But that’ just my opinion.
In summary
Printed below is my current configuration of the theme itself in full. On top, there are the hook-based overrides for a couple of things: the mode-line, the tab-bar, the mlscroll indicator, and neotree fonts. I’m also using the latest HEAD revision from Prot’s git repository instead of the latest MELPA release because things are moving so fast, but commented-out the :load-path
option to share it here.
(use-package modus-themes
;; :load-path "~/.emacs.d/src/modus-themes"
:ensure
:demand
:init
(require 'modus-themes)
(setq modus-themes-to-toggle '(modus-operandi-deuteranopia modus-vivendi-deuteranopia))
(setq modus-themes-italic-constructs t
modus-themes-bold-constructs t
modus-themes-variable-pitch-ui t
modus-themes-mixed-fonts t)
;; Color customizations
(setq modus-themes-prompts '(bold))
(setq modus-themes-completions nil)
(setq modus-themes-org-blocks 'tinted-background) ;'gray-background)
;; Font sizes for titles and headings, including org
(setq modus-themes-headings '((1 . (light variable-pitch 1.5))
(agenda-date . (1.3))
(agenda-structure . (variable-pitch light 1.8))
(t . (medium))))
;; Theme overrides
(customize-set-variable 'modus-themes-common-palette-overrides
`(
;; Make the mode-line borderless
(bg-mode-line-active bg-inactive)
(fg-mode-line-active fg-main)
(bg-mode-line-inactive bg-inactive)
(fg-mode-line-active fg-dim)
(border-mode-line-active bg-inactive)
(border-mode-line-inactive bg-main)
;; macOS Selection colors
(bg-region "mac:selectedTextBackgroundColor")
(fg-region "mac:selectedTextColor")
))
(customize-set-variable 'modus-operandi-deuteranopia-palette-overrides
`(
;; More subtle gray for the inactive window and modeline
(bg-inactive "#efefef")))
(customize-set-variable 'modus-vivendi-deuteranopia-palette-overrides
`(
;; More subtle gray for the inactive window and modeline
(bg-inactive "#202020")))
;; Quick fix: Don't load immediately, but during after-init-hook so all other modifications further down can be prepared
(defun ct/modus-themes-init ()
(load-theme (car modus-themes-to-toggle)))
:bind ("<f5>" . modus-themes-toggle)
:hook (after-init . ct/modus-themes-init))