Testing recording buttons in Mac apps. Why they are not as simple as they look.
Hey all, I'm building a transcription app for Mac, and one of the main elements of the app is the recording button: it starts or stops recording when you press it. It looks very simple, but it's one of those cases where something is very simple on the outside, but quite complex on the inside. As a side effect of engineering the perfect button, I researched how recording buttons behave in other apps. I compiled this post from my findings in case anyone is interested in such things at the intersection of engineering and UX. I never imagined writing a review of Mac apps' recording buttons, but I find it interesting and here we are 😄 Why is it not that simple? In my app, when you press the recording button, the following happens under the hood: App checks microphone and system audio permissions. Shows a disclaimer reminding you to get consent from participants. Checks if another recording is in progress and asks if you want to interrupt it. Checks if the AI model and speech assets for the chosen language are available. Tries to reserve the chosen language locale. Checks if any meeting recording is playing now, and stops it. Starts microphone recording. Waits a bit to prevent races in CoreAudio and starts System Audio recording. Starts the mixer, which mixes system audio with microphone input to produce a single audio stream for transcription. Resolves the transcriber audio format. Creates and starts the transcriber. The thing that adds complexity is that all these steps are asynchronous, and the world changes when we begin the next one. Also, each one can fail, resulting in some unexpected state. Problem Imagine the case when the user starts a recording and wants to stop it immediately. In this case, the user presses start recording and presses stop after a couple hundred milliseconds: In this scenario, we end up in an inconsistent state: recording is ongoing, but the app shows that it's stopped. Oops. Obvious solution The app would just prevent the user from interacting with it during start/stop. This guarantees that nothing will interrupt the flow during startup. However, it gives us the following problems: The user is blocked for up to 1 second waiting for feedback. They are not sure what happened, whether recording has started or not. They wait for feedback to continue their work. This breaks the flow and produces a tiny bit of friction. If the user changes their mind, they have to wait until the button is enabled again. Slight frustration again. While these details might not even be noticed by people, they create repetitive friction every day. And that's not what I expect from a helping tool. Perfect button I'd call the button perfect if it's: Functional Responsive It means that the button should immediately and optimistically indicate a state change, and never be disabled. Let's see how some of the apps handle this. Disclaimer: I totally understand why some apps disable the button and I don't blame anyone for that, especially indie developers. Each solution has its pros and cons, and these apps have many strengths. I am sharing my opinion here. QuickTime Recording starts instantly. When I press stop, it takes some time, disabling the button for a while. MacWhisper There's a slight delay between the press of the start button and the appearance of the stop button. I can't stop recording immediately. Also, the button changes its position after I start recording, which requires additional cognitive effort from me to find it. Superwhisper This is the interesting one. It has a third state: transcribing. When I press start, it indicates that instantly. If I press stop immediately and nothing is recorded, it just stops instantly. And only if I press stop after it's running for some time, I have to wait for the transcription. I can clearly see that the developer thought about the implementation and put effort into it. Otter As you can see, the button becomes disabled while it starts recording. And I need to wait before I can stop. It's difficult for me to understand what it does for so long. Talat The button is disabled while recording is starting. The good thing is that the recording start is quite fast here, so it's mostly unnoticeable. iPhone Voice Memos When I press start, it starts. When I press stop, it stops. Nothing more. Fireflies The button is locked during start, and it takes quite a time. The same thing for stopping. And I can't stop immediately if I changed my mind. Alter When I press start, it takes some time to react. Alter doesn't show any progress during that time. If I try to stop recording immediately, the button still shows "Start Meeting Recording", and when I press it, it stops. Verdict As you can see, most of the apps choose to disable the button during startup. As a developer, I understand why they do so. Also, I can easily imagine bringing that up at work and getting plenty of "Who cares?" replies. I understand that. However, would the original Mac exist without deep attention to details? P.S. I skipped the engineering part since the post is already too long, and I'm not even sure if you're interested in it. Please let me know if you are, and I'd be happy to write about it.
已收集讨论
This is a refreshing post for a change :) So Superwhisper got it right?
Thanks, happy you find it refreshing:) In my opinion yes. At least the button feels like it was made with care. I also like Talat’s one, it’s fast.
I really like Superwhisper’s implementation here as well. It’s quite satisfying, and just works. The little details matter, and apps that do not get this right with little delays and other issues will tend to cause enough friction to fall into disuse.
True. Refreshing to hear people who agree on importance of details.
Adding the designer angle to your "fall into disuse" point: what users actually notice isn't the 200ms delay, it's the moment the button's label and its real state diverge. "Start" labeled but already busy, or "Stop" grayed out while still recording. Latency is cumulative, but trust is binary. One wrong UI moment and the user switches apps. The honest-state pattern Superwhisper uses isn't just better recording UX. Same shape shows up in export flows, OAuth handshakes, iCloud sync indicators. Recording is the canary because the cost of being wrong is highest (lost meeting). Indie Mac apps live or die on whether the dev sees the pattern in all those places, not just where the OP looked.
This is a really good post. I’ve run into this same state-machine mess with recording UX, and honestly the start/stop button ends up being way more of the product than I expected. The hard part is not “toggle recording.” It’s: what state are we in, did audio actually start, did the user cancel, do we have a recoverable artifact, and are we telling the truth in the UI? I like your Superwhisper read too. That third state matters. A recording app needs to feel instant, but it also needs to be honest when the machine is still doing work. Would definitely read the engineering follow-up.
Thank you! Yes, totally feel what you mean, I didn't expect that too at the start. Will try to compose good engineering read too :)
I truly enjoyed your post and the effort you took in researching and spotlighting these intricate details. Superwhisper is already synonymous with dictation, and the fact that they even scrutinize such minute details only confirms the excellence of the developer. It's precisely such attention to the smallest details that makes an app go from good to exceptional.
Thank you, appreciate your words. I thought it might be interesting for the users to read about the things behind the apps they use. Or for devs to read about the challenges other devs face with such non-obvious things. I agree that it makes an app exceptional.
Thanks for taking the time to write this up. As a non-coder, just a user, it's instructive to learn how much goes on behind my simple click of a button. While I want a click to produce a response reasonably quickly, I'm not sure I necessarily expect something to happen instantaneously when I click that button. But that may be because I'm of an age that I well remember waiting a few seconds for CDs and hard drives to spin up and down. And, before that, playing tape cassettes for 10 minutes just to load a rudimentary game!
Thanks, happy to hear it was useful for you. Yeah, we all have our own habits and expectations. My opinion here is based on two things: Instant is better than not instant If something works in most of the cases and takes ~200 ms, we have a right to be optimistic in UI
Ugh, CoreAudio race conditions are the absolute worst. Honestly, it’s crazy how much hidden complexity goes into something as seemingly basic as a record button. Respect for the deep dive!
Thanks! Yes, that’s absolutely crazy. Race conditions, crashes without any clear reason, etc. My favorite is a hang out of the blue on removeTap call.
Speaking for Alter, it’s interesting to note that what you’re trying to record makes it even more complicated. Eg meetings is another story vs dictation. Recording the mic is straightforward but if you need system audio as well and play with routing or mixing, good luck. Especially if you want to support all apps (hi FaceTime) Then add local transcription and you get another complexity layer, and just like you said, all behind a single button!
And if we add speaker diarization to that… :)
Haha and Bluetooth devices…!
The button is really a tiny state machine. Users need to know if the app is armed, recording, paused, processing, failed, or blocked by permissions, and the hotkey should reflect the same state as the UI. The detail that builds trust is a safe cancel path and a draft recovery path when recording starts but transcription fails.
The edge case I would test hardest is pressing the hotkey again while startup is still pending. A small delay is tolerable, but losing the spoken chunk or leaving mic/system audio in a half-on state breaks trust fast. I would separate the states pretty aggressively: arming, recording, stopping, processing, failed. The stop/cancel path should preserve any recoverable audio or partial transcript, and the menu bar icon/hotkey state should match the button state because a lot of people trigger dictation without looking at the app window.