I owe fraserscottmorrison an apology before I owe him a thank you. He filed two issues on DesignFoundation, a SwiftUI design system I open-sourced a few months back, on the evening of July 31st, about an hour apart. I didn't actually open either one until today, going through a backlog I'd let pile up. Two and a half weeks is a long time to leave someone's first contribution to your project sitting unread.
Once I did open them, I was glad I hadn't just glanced and closed them. Most first-time issues on a young open source project are either "how do I install this" or a feature request so vague you don't know where to start. These weren't either.
The issue itself
Here's what he wrote, more or less word for word:
Im using DF for a personal project where I don't have the time to write a design system, so thanks for sharing your library. At work I created our own design system, yours is better in many ways, but where I like my work one better is that Views use standard controls eg SwiftUI's Button and our design system has custom viewmodifiers to brand them. Not only will it apply the primary colours but will also apply padding etc. The big advantage is you get all the built in behaviour of the system control eg Buttons can have icons etc. I just found out your DFButton doesn't allow an icon which prompted this idea.
He included a code sketch too, something like a Button().branded(.primary) pattern using a ViewModifier. Not pseudo code copied from somewhere. Something he'd actually shipped at his day job, describing exactly why it worked better than what he'd found in DesignFoundation.
That's a genuinely good bug report, and it's worth saying why, because most issues aren't like this.
What makes this a good first issue
He didn't ask me to build something new. He pointed at something DesignFoundation already had (a button styling system) and showed me it was one architectural decision away from doing what he needed. He gave a concrete symptom (no icon support on DFButton) instead of an abstract complaint ("the API isn't flexible enough"). And he included his own working example from a different codebase, which meant I wasn't guessing at what "better" would look like to him.
Compare that to the issues I usually get, which read more like "can this support X" with no example, no reproduction, and no sense of whether the person has thought about how X would actually compose with the rest of the library. Those take three or four rounds of back and forth just to figure out what's actually being asked for. This one took one read.
There's also something specific about a first issue that makes it worth extra attention. Someone's first interaction with your project sets the tone for whether they file a second one, or a tenth one, or just quietly forks it and moves on. I've been on the other side of that. I remember filing an issue on a Rust crate years ago, getting a two-word dismissive reply, and never bothering to report anything to that maintainer again, even when I found real bugs later. That memory was in the back of my head the whole time I was reading fraserscottmorrison's report.
What I found when I actually opened the code
Before responding, I went and read DFButton.swift properly instead of assuming I knew what was in there. That habit has saved me more than once. It turned out the architecture was almost exactly right already.
DFButton was internally bridging into a real SwiftUI ButtonStyle, which is the correct way to get pressed state and disabled state for free instead of hand rolling gesture recognizers (a mistake I'd actually made and fixed in an earlier commit on the same file, for what it's worth). Every built in style, .filled, .outlined, .ghost, .tinted, .glass, just applied padding, color, and background to whatever content it was handed. Nothing in there assumed the content was plain text.
The actual bug was one line. The internal bridge was rebuilding the button's label from scratch as Text(label) instead of using the button's real rendered content. That's the whole reason icons didn't work: the real content, whatever you passed in, from an icon-bearing Label to a custom layout, was being thrown away and replaced with a bare string every single time.
Once I saw that, the fix wasn't really a redesign. It was making the internal bridge public, fixing that one line so it reads the button's actual content instead of reconstructing it, and adding a small .df(_:role:) convenience so you can write it the way SwiftUI itself would:
Button {
save()
} label: {
Label("Save", systemImage: "checkmark")
}
.buttonStyle(.df(.outlined, role: .destructive))
DFButton("Save") { } still works exactly like before for the plain string case. This was additive, not a rewrite, which is its own kind of relief when you're the only person maintaining the thing.
Where I pushed back on my own idea
The issue title was actually broader than just buttons, it asked whether every custom control in the library should work this way instead of being a from scratch SwiftUI view. My first instinct was to say yes and go rebuild the toggle and checkbox the same way.
I'm glad I checked before doing that. DFToggle's default style already wraps a native SwiftUI Toggle, so it was already doing what the issue asked for. And DFCheckbox doesn't have a real native SwiftUI control to wrap in the first place, since there's no cross platform checkbox in the framework. Rebuilding it "the native way" would have meant building the exact same custom drawing that already existed, just with extra ceremony around it. Sometimes the right answer to "should this be more like X" is "part of it already is, and the rest genuinely can't be."
The part I actually enjoyed
The button issue wasn't even the first one he'd filed that evening. About an hour before it, he'd already reported a second bug: DFCard blocking scroll inside a VStack. He'd diagnosed that one too, correctly guessing that a simultaneousGesture with a zero distance drag gesture was the culprit, attached to every card whether or not it had a tap action.
He was right about that one as well. It turned out to be the same root cause pattern as the button fix: a gesture attached unconditionally regardless of whether the component actually needs it, silently swallowing input meant for a parent view. I fixed both the same day I finally read them, tested, shipped, tagged as 1.3.1.
What stuck with me about that second issue wasn't the bug itself. It was the small note he added the next minute: "ps Im trying your library in a new app I've just started working on, so far so good apart from this one bug." That's someone actually building something with a project I made in my spare time, hitting friction, diagnosing it competently, and telling me about it instead of just working around it or abandoning ship. Two and a half weeks of silence from me, and he hadn't filed a third issue complaining that nobody was listening. That's worth noticing.
I won't pretend every issue lands like this. Plenty of feedback on open source projects is a one line complaint with no context, and you learn to ask clarifying questions without getting defensive about it. But when someone hands you two real examples, a working mental model of why their version is better, and then waits patiently for you to get your act together, that's worth a real reply, a real fix, and honestly, a public apology along with the thank you.
Where this fits in the bigger picture
DesignFoundation is the free, MIT licensed layer: token based theming, style swappable components, full Swift 6 concurrency safety, extracted from real production iOS work rather than built as a demo. Fixes like this one make that free core more correct, which matters regardless of what you build on top of it.
DesignFoundationPro is the paid layer built directly on top of that same core: complete production screens, drop in UI blocks, and full navigation shells for the kinds of apps people actually ship (auth flows, dashboards, e-commerce, settings, and more), so you're not hand assembling every screen from primitives yourself. Every bug fix and every architectural improvement to the free library flows straight through into Pro, since Pro is built with it, not around it. This week's fix is a small example: any Pro screen using a branded button with an icon benefits from the exact same change.
If you're building a SwiftUI app and tired of either hand rolling your own design system or fighting a component library that fights you back, take a look at the docs and the GitHub repo. And if you find something that bugs you, file the issue. Bring an example if you've got one. It genuinely helps, and there's a decent chance it gets fixed before the day is out.
Top comments (0)