Fixing the Invisible Spinner: How I Made My First Open Source Contribution to Ghost
When a tiny invisible spinner taught me the real power of open source and attention to detail.

Introduction
When you think about open source, you might imagine developers fixing huge bugs, adding features, or diving into complex backend systems.
But my first contribution to Ghost, an open-source publishing platform, was not about any of that.
It was about a spinner, a tiny loading indicator that almost no one noticed... because it was invisible.
That small bug became my gateway into open source, teaching me about debugging, design systems, accessibility, and collaboration.
How It Started
While exploring Ghost’s GitHub issues, I came across Issue #24706:
“Spinner color contrast issue in Share Profile.”
It did not sound glamorous, just a UI detail, but something about it caught my attention.
If users cannot see feedback while an action is happening, the experience feels broken.
So I decided, this is the issue I’ll fix.
Setting Up My Environment
Before touching any code, I had to set up Ghost locally, a challenge in itself.
It involved:
Forking and cloning the repo
Installing dependencies using
yarn installSetting up MySQL, Redis, and Caddy (for ActivityPub)
Enabling ActivityPub in Ghost’s configuration
It took patience, multiple retries, and a lot of reading through Ghost’s contributor docs. But once I saw Ghost running locally, I knew I was ready.
Reproducing the Bug
Inside Ghost’s admin panel, under:
Network → Preferences → Share your profile,
Users can generate shareable profile images with three backgrounds:
Light mode
Dark mode
Accent colour mode
When I clicked “Copy Image”, I noticed the issue immediately:
The spinner appeared... but it was barely visible, sometimes completely invisible.
| Mode | Button Background | Spinner Color | Visibility |
| Light | Black | Black | ❌ Barely visible |
| Dark | White | White | ❌ Invisible |
| Accent | Black | Black | ❌ Barely visible |
A tiny problem, but one that breaks the feedback loop for users.
Tracing the Root Cause
The issue came from a simple logic flaw in Profile.tsx:
<LoadingIndicator
color={`${backgroundColor === 'dark' ? 'dark' : 'light'}`}
size='sm'
/>
It based the spinner colour on the page background, not the button it was sitting on.
Result: perfect logic, wrong context.
The Fix
I wrote a small helper function that picked the spinner colour based on the actual button colour:
const getSpinnerColorForButton = (backgroundColor: 'light' | 'dark' | 'accent') => {
switch (backgroundColor) {
case 'light': return 'light'; // White spinner on dark button
case 'dark': return 'dark'; // Black spinner on white button
case 'accent': return 'light'; // White spinner on dark button
default: return 'dark';
}
};
Then replaced the inline ternary with:
<LoadingIndicator color={getSpinnerColorForButton(backgroundColor)} size='sm' />
A two-line change, but now, the spinner adapted perfectly in all modes.
Testing the Solution
After rebuilding Ghost locally, I tested all three modes again.
| Mode | Expected | Result |
| Light | White spinner | ✅ Visible |
| Dark | Black spinner | ✅ Visible |
| Accent | White spinner | ✅ Visible |
Success 🎉
What once was an invisible spinner now gave clear feedback across every theme.
Submitting My Pull Request
Following Ghost’s commit guidelines, I wrote this commit message:
Fixed spinner visibility in Share Profile copy button
closes https://github.com/TryGhost/Ghost/issues/24706
Added getSpinnerColorForButton() helper function to ensure optimal contrast
between spinner and button backgrounds, improving UX in ActivityPub Share Profile.
Created a branch, pushed changes, and submitted my first ever PR to an open-source project.
Why This Small Fix Mattered
This was not just a cosmetic change.
It improved accessibility, user trust, and consistency subtly, but meaningfully.
It taught me that:
Even the smallest UI bug affects user confidence.
Simplicity is powerful when done with intention.
Contributing isn’t about complexity, it’s about care.
Lessons I Learned
Observe before coding — clarity saves hours.
Context matters — a “dark” mode doesn’t mean everything is dark.
Readable code is lasting code — functions tell stories too.
Follow project conventions — every repo has its rhythm.
Small steps create big confidence.
The Real Impact
This one-line fix made Ghost’s ActivityPub experience smoother for every user.
But more importantly, it made me realise something:
Open source isn’t just about contributing code; it’s about contributing care.
That small act of improving a spinner made me part of a community that values every detail.
Final Thoughts
If you’re new to open source, start small.
Find a bug you understand.
Fix it. Document it. Share it.
You’ll learn, grow, and feel the same joy I did seeing your name appear in the contributors list of a project you love.
Resources
Have you made your first open source contribution yet? Share your story in the comments — I’d love to hear it!




