Hacking Slack's UI: CSS Injection in Electron Apps
Hook
Before Slack added dark mode in 2019, thousands of developers were manually editing their Slack installation files after every update just to get a darker UI. This wasn't just persistence—it was a masterclass in Electron app customization.
Context
For years, Slack's bright white interface was a jarring presence in developers' otherwise carefully crafted dark-themed workflows. Opening Slack felt like staring into a flashlight at 2 AM during an on-call incident. While competitors like Discord shipped with dark themes, Slack resisted, leaving users to squint through blinding white channels or resort to third-party solutions.
The slack-black-theme project emerged as a community response to this pain point. Created by Widget, it leveraged the fact that Slack's desktop application is built on Electron—essentially a Chromium browser wrapped in a native shell. This architecture meant that Slack's UI was just HTML, CSS, and JavaScript under the hood, making it theoretically hackable. The challenge was finding a reliable injection point that would survive Slack's sandboxed webview architecture and actually apply custom styles across all of Slack's interface components.
Technical Insight
The brilliance of slack-black-theme lies in its exploitation of Electron's architecture. Slack's desktop app uses multiple webviews to isolate team workspaces, and the injection technique needed to target all of them. The solution modifies Slack's bootstrapping code to inject custom CSS before any UI renders.
The core injection happens in Slack's ssb-interop.js file (or index.js in older versions). Users append this JavaScript snippet to the end of the file:
document.addEventListener('DOMContentLoaded', function() {
let tt__customCss = ".menu ul li a {color: #fff !important;} // ... more CSS"
// Wait for webviews to initialize
$.ajax({
url: 'https://cdn.rawgit.com/widget-/slack-black-theme/master/custom.css',
success: function(css) {
// Inject into all team webviews
$('div.TeamView webview').each(function() {
this.addEventListener('didFinishLoading', function() {
this.executeJavaScript(`
let s = document.createElement('style');
s.type = 'text/css';
s.innerHTML = \`${css}\`;
document.head.appendChild(s);
`);
});
});
}
});
});
This approach is deceptively sophisticated. First, it waits for the DOM to fully load before attempting any modifications. Then it queries for div.TeamView webview elements—Slack's internal structure for displaying separate team workspaces. Each webview is a separate sandboxed context, so the code must inject styles into each one individually.
The didFinishLoading event listener is crucial. Webviews load asynchronously, so attempting to inject CSS immediately would fail. By hooking into this event, the code ensures the webview's document is ready to receive the style injection. The executeJavaScript method then runs code within that webview's context, creating a style element and appending it to the document head.
The theming system uses CSS variables for easy customization:
:root {
--primary: #09F;
--text: #CCC;
--background: #080808;
--background-elevated: #222;
}
.p-channel_sidebar { background: var(--background); }
.c-message { color: var(--text); }
.c-button--primary { background: var(--primary); }
This variable-based approach means users can customize colors without understanding Slack's complex DOM structure. They simply override the four CSS variables in their local copy, and the entire theme adapts. It's a clean abstraction that separates color choices from styling logic.
For development, the project includes a hot-reload mechanism:
const cssPath = '/path/to/custom.css';
const css = fs.readFileSync(cssPath, 'utf-8');
fs.watchFile(cssPath, () => {
const updatedCss = fs.readFileSync(cssPath, 'utf-8');
// Re-inject into all webviews
});
This watches the local CSS file and re-injects it whenever changes are detected, enabling rapid iteration without restarting Slack. It's the kind of developer experience detail that shows this was built by someone who actually used their own tool.
The CSS itself targets hundreds of Slack's class names—.c-message, .p-channel_sidebar, .c-message_kit__background—demonstrating deep reverse-engineering of Slack's component library. Every button, input field, modal, and dropdown needed individual attention to ensure consistent theming across the entire application.
Gotcha
The fundamental limitation is brittleness. Every Slack update overwrites the modified files, forcing users to re-apply the hack manually. Even minor updates can break the injection if Slack changes file names, moves the bootstrapping code, or alters their webview structure. Users report spending hours after updates trying to figure out why their theme stopped working, only to discover Slack renamed ssb-interop.js or restructured their initialization sequence.
The security implications are also concerning. The default configuration fetches CSS from a CDN on every Slack launch, creating a network dependency and potential attack vector. If someone compromised the CDN or performed a man-in-the-middle attack, they could inject malicious JavaScript into your Slack client. The project uses rawgit by default, which has been deprecated since 2018, meaning those URLs could stop working at any time. More importantly, modifying Slack's application files likely violates their terms of service and could theoretically get your account flagged, though enforcement appears nonexistent. The approach also provides no forward compatibility—there's no graceful degradation if Slack's DOM structure changes. Your theme simply breaks, often in visually jarring ways with some elements dark and others blindingly white.
Verdict
Skip if you're using modern Slack. The built-in dark mode (added in 2019) is officially supported, won't break on updates, and covers all UI elements consistently. The maintenance burden of re-applying this hack after every update simply isn't worth it anymore. Skip if you're concerned about security or terms of service violations—modifying application files and injecting remote code is inherently risky. Use if you're on an older Slack version without native dark mode support and are willing to accept the maintenance burden. Use if you're interested in learning Electron customization techniques—this project is an excellent case study in webview manipulation, CSS injection, and working around sandboxing constraints. Use if you need very specific custom styling beyond what Slack's built-in themes offer, though be prepared to maintain your CSS as Slack's UI evolves. The educational value remains high even if the practical utility has diminished.