Hack the Trello UI for a Cleaner, Pleasing, Distraction-Free Web Experience
Having returned to Trello for Individuals (free) after several years, I see it was acquired by Atlassian and now has “Try Premium Free” banners in far too many places, along with persistent nags to the same effect. Also, the columns are all gray and depressing.
Trello Pain Points:
- Trello has too many distracting “free trial” buttons and banners.
- The column colors are gray and uninspiring.
- There is no black or charcoal background option.
Here are some of the distracting “free trial” call-to-action (CTA) buttons:
My company already pays enterprise fees per seat, but I just want something simple for home organization—without the clutter and nagging—and with a black background.
I’d like to use Trello the way I did before Atlassian bought it—without all the nag banners. Can it just be a board of simple sticky notes again? Is that still possible?
1. Make the Background Black

We’re given the choice of pastel colors or a few default backgrounds—but not black. If I’m keeping Trello on a side monitor all day, I’d prefer a low-light theme like solid black. Sure, a black PNG could be used as a background, but really—one line of CSS could fix this, right?

The first Tampermonkey script simply changes the root background color on body:hover
. Why on hover? Because this is a React-based site—HTML elements appear, disappear, and morph without reloading the page. You could use CSS !important
, but applying the rule on hover
is surprisingly effective.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // ==UserScript== // @name Trello | Black Backgrounds on Boards // @namespace https://ericdraken.com/ // @version 1.0 // @description Give Trello boards a black background // @author Eric Draken (ericdraken.com) // @match https://trello.com/b/* // @require https://code.jquery.com/jquery-3.4.1.min.js // @icon https://www.google.com/s2/favicons?sz=64&domain=trello.com // @grant none // ==/UserScript== /*global jQuery*/ /*eslint no-multi-spaces: "off"*/ (function($) { 'use strict'; const action_fn = () => $('#trello-root').css('background-color', '#000'); $(action_fn); // Once on document ready $('html').hover(action_fn); // Again on hover in case the page transitions })(jQuery.noConflict()); |
2. Color the Columns
It would be nice if the columns had colors. Instead of managing multiple boards I’d eventually forget about, I’d prefer a single board with several columns representing different projects I can bounce between—like one big physical wall of sticky notes.
The plan is to detect the column title text, then work backward to style both the column and the container holding the cards. In this example, a column name like [Setup]
will trigger a pastel purple theme. Edit the script to add or modify keywords that apply styles to columns.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | // ==UserScript== // @name Trello | Color Board Columns // @namespace https://ericdraken.com/ // @version 1.0 // @description Give Trello board columns some color // @author Eric Draken (ericdraken.com) // @match https://trello.com/b/* // @require https://code.jquery.com/jquery-3.4.1.min.js // @icon https://www.google.com/s2/favicons?sz=64&domain=trello.com // @grant none // ==/UserScript== /*global jQuery*/ /*eslint no-multi-spaces: "off"*/ (function($) { 'use strict'; const style_map = { 'green':{ // Just a unique key text: 'Complete', // Search for this text in the column name target: 'h2', parent: '.js-list.list-wrapper', children: { '.list.js-list-content': {backgroundColor: '#D7FDDF'}, // Column card holder '.list-card-details': {backgroundColor: 'white'}, // Card in the column } }, 'yellow':{ text: 'In Progress', target: 'h2', parent: '.js-list.list-wrapper', children: { '.list.js-list-content': {backgroundColor: '#FBFFDE'}, '.list-card-details': {backgroundColor: 'white'}, } }, 'blue':{ text: '[Misc]', target: 'h2', parent: '.js-list.list-wrapper', children: { '.list.js-list-content': {backgroundColor: '#E0FFFD'}, '.list-card-details': {backgroundColor: 'white'}, } }, 'red':{ text: 'Blocked', target: 'h2', parent: '.js-list.list-wrapper', children: { '.list.js-list-content': {backgroundColor: '#FFD1D1'}, '.list-cards': {backgroundColor: 'white'}, } }, 'purple':{ text: '[Setup]', target: 'h2', parent: '.js-list.list-wrapper', children: { '.list.js-list-content': {backgroundColor: '#D0D0FE'}, '.list-card-details': {backgroundColor: 'white'}, } }, }; const action_fn = () => { for (const [_, style] of Object.entries(style_map)) { let $targets = $(`${style.target}:contains('${style.text}')`); $targets.each((_, elem) => { let $parent = $(elem).parents(style.parent); if ($parent.length) { for (const [selector, css] of Object.entries(style.children)) { if (selector != '.') { $parent.find(selector).css(css); } else { $parent.css(css); } } } }); } }; $(action_fn); // Once on document ready $('html').hover(action_fn); // Again on hover in case the page transitions })(jQuery.noConflict()); |
This script is quite crude, but it sets RGB hex colours in the desired places.
3. Remove Free-Trial and Upgrade Distractions
Here’s what Trello looks like without the nag banners:
Using the same technique—searching for phrases like “Premium free” and “upgrade”—we can target <span>
, <h2>
, etc. elements, then walk back up the DOM to find their container elements and eliminate them (well, display: none
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | // ==UserScript== // @name Trello | Remove Free-Trial Distractions // @namespace https://ericdraken.com/ // @version 1.0 // @description Remove free trial nag buttons and banners // @author Eric Draken (ericdraken.com) // @match https://trello.com/b/* // @require https://code.jquery.com/jquery-3.4.1.min.js // @icon https://www.google.com/s2/favicons?sz=64&domain=trello.com // @grant none // ==/UserScript== /*global jQuery*/ /*eslint no-multi-spaces: "off"*/ (function($) { 'use strict'; const debug = true; const fuzzy_matches = { 'left nav > Try Premium free': { // Just a unique key text: 'Premium free', // Search for this text target: 'button div', closest: '.js-react-root', children: [{ target: '.', css: {display: 'none'}, }] }, 'left nav > Free': { text: 'Free', target: 'js-react-root nav p', closest: '.', children: [{ target: '.', css: {display: 'none'}, }] }, }; const exact_matches = { 'board menu > custom fields': { target: '.disabled', css: {display: 'none'} } }; const action_fn = () => { // Fuzzy matches first for (const [_, entry] of Object.entries(fuzzy_matches)) { let $targets = $(`${entry.target}:contains('${entry.text}')`); $targets.each((_, elem) => { let $parent = entry.closest != '.' ? $(elem).closest(entry.closest) : $(elem); if ($parent.length) { for (const child of entry.children) { if (child.target != '.') { $parent.find(child.target).css(child.css); } else { $parent.css(child.css); } } } else if(debug) { console.log(`Closest ancestor (${entry.closest}) not found from ${elem.tagName}`); } }); } // Exact matches for (const [_, entry] of Object.entries(exact_matches)) { let $target = $(entry.target); if($target.length) { $target.css(entry.css); } else { console.log(`Exact match (${entry.target}) not found`); } } }; $(action_fn); // Once on document ready $('html').hover(action_fn); // Again on hover in case the page transitions })(jQuery.noConflict()); |
4. Trello Awesomeness in One Script
After working on individual improvements for Trello—and since there’s overlap in how the CSS search algorithm works—I figured, why not roll everything into one script, add some extra zhuzh, and use HSL color format (e.g., color: hsl(74deg 13% 49%)
) to create some very cool themes.
Why use HSL color notation? It stands for Hue, Saturation, and Lightness—and it’s great for one reason: if I find a sweet color and want a complementary shade that’s lighter or darker, I’m basically SOL when using the RGB color wheel. What usually happens is I end up with ugly mismatched colors.
With HSL, if I find a nice color, I can simply adjust the lightness to make it darker or lighter while preserving the original hue and tone. Could I just use alpha
on RGB? No—because HTML containers stack and blend visually.
HSL Example
- HSL(35deg 100% 69%) = #FFBD61
- HSL(35deg 100% 49%) = #945600
As you can see, reducing the lightness by 20% significantly alters the RGB hex output. HSL gives us a powerful way to create theme variations without the trial-and-error of RGB tweaking. Chrome DevTools includes an HSL toggle—click the arrows in the color picker to cycle between formats.
Customize to your liking. One additional feature: exact CSS rules are injected using the !important
directive and applied globally to the DOM’s stylesheet. To improve robustness, a setInterval(..., 1000)
loop is used to detect and style new elements every second—because global rules alone aren’t sufficient.
MutationObserver
on the DOM instead? You bet. However, because this is a React-based site, it uses a shadow DOM, and nodes in the DOM are constantly changing. That means a high-level observer—like one on <body>
—would be required. We’d then have to iterate through the entire DOM to detect changes, and for each change, traverse up the ancestor chain to apply CSS. In short, whenever the page updates, we’d have to walk down the DOM, then walk it back up for every CSS override we want to apply—very inefficient.How to Use
A keyword in the column controls the color. See the screenshot below.
To adjust the behavior, tweak the fields in the JavaScript below. For example, if a column contains the word Complete
, the green theme is applied. To avoid false matches, I prefer using square brackets—for instance, [Misc]
triggers the blue theme.
1 2 3 4 5 6 7 8 9 10 11 12 13 | ... 'green column': { // Just a unique key text: 'Complete', // Search for this text in the column name ... children: [{ target: '.list.js-list-content', // Column card holder css: {'background-color': 'hsl(74deg 13% 49%)'} },{ target: '.list-card-details', // Card in the column css: {'background-color': 'hsl(74deg 13% 79%)', 'border': 'solid 1px hsl(74deg 13% 39%)'} }] }, ... |
The Trello Awesomeness Script
Feel free to fork this script on GitHub.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | // ==UserScript== // @name Trello | Make Trello Awesome Again // @namespace https://ericdraken.com/ // @version 1.0.1.20220427 // @description Remove free trial nags; use earthtones for columns; set a dark background // @author Eric Draken (ericdraken.com) // @match https://trello.com/b/* // @require https://code.jquery.com/jquery-3.4.1.min.js // @icon https://www.google.com/s2/favicons?sz=64&domain=trello.com // @grant none // ==/UserScript== /*global jQuery*/ /*eslint no-multi-spaces: "off"*/ (function($) { 'use strict'; const debug = false; // Turn on for detailed console logs const fuzzy_matches = { /***** Add column colors *****/ 'complete column': { // Just a unique key text: 'Complete', // Search for this text in the column name target: 'h2', closest: '.js-list.list-wrapper', children: [{ target: '.list.js-list-content', // Column card holder css: {'background-color': 'hsl(74deg 13% 49%)'} },{ target: '.list-card-details', // Card in the column css: {'background-color': 'hsl(74deg 13% 79%)', 'border': 'solid 1px hsl(74deg 13% 39%)'} }] }, 'progress column':{ text: 'In Progress', target: 'h2', closest: '.js-list.list-wrapper', children: [{ target: '.list.js-list-content', css: {'background-color': 'hsl(35deg 100% 39%)'} },{ target: '.list-card-details', css: {'background-color': 'hsl(35deg 100% 69%)', 'border': 'solid 1px hsl(35deg 100% 29%)'} }] }, 'misc column':{ text: '[Misc]', target: 'h2', closest: '.js-list.list-wrapper', children: [{ target: '.list.js-list-content', css: {'background-color': 'hsl(207deg 15% 32%)'} },{ target: '.list-card-details', css: {'background-color': 'hsl(207deg 15% 62%)', 'border': 'solid 1px hsl(207deg 15% 22%)'} }] }, 'blocked column':{ text: 'Blocked', target: 'h2', closest: '.js-list.list-wrapper', children: [{ target: '.list.js-list-content', css: {'background-color': 'hsl(13deg 48% 43%)'} },{ target: '.list-card-details', css: {'background-color': 'hsl(13deg 48% 73%)', 'border': 'solid 1px hsl(13deg 48% 33%)'} }] }, 'setup column':{ text: '[Setup]', target: 'h2', closest: '.js-list.list-wrapper', children: [{ target: '.list.js-list-content', css: {'background-color': 'hsl(214deg 9% 52%)'} },{ target: '.list-card-details', css: {'background-color': 'hsl(214deg 9% 82%)', 'border': 'solid 1px hsl(214deg 9% 42%)'} }] }, 'project column':{ text: '[Project]', target: 'h2', closest: '.js-list.list-wrapper', children: [{ target: '.list.js-list-content', css: {'background-color': 'hsl(191deg 100% 28%)'} },{ target: '.list-card-details', css: {'background-color': 'hsl(191deg 100% 58%)', 'border': 'solid 1px hsl(191deg 100% 8%)'} }] }, 'patent column':{ text: '[Patent]', target: 'h2', closest: '.js-list.list-wrapper', children: [{ target: '.list.js-list-content', css: {'background-color': 'hsl(290deg 100% 68%)'} },{ target: '.list-card-details', css: {'background-color': 'hsl(290deg 100% 88%)', 'border': 'solid 1px hsl(290deg 100% 48%)'} }] }, /***** Remove nag elements *****/ 'left nav > Try Premium free': { // Just a unique key text: 'Premium free', // Search for this text target: 'button div', closest: '.js-react-root', children: [{ target: '.', css: {'display': 'none'} }] }, 'left nav > Free': { text: 'Free', target: '.js-react-root > nav p', closest: '.', children: [{ target: '.', css: {'display': 'none'} }] }, }; const exact_matches = { /***** Add column colors *****/ 'Add a card': { target: 'a.open-card-composer.js-open-card-composer > .js-add-a-card', css: {'color': '#172b4d'} }, 'board icons': { target: '#board .icon-sm, #board .icon-md, #board .icon-lg', css: {'color': '#172b4d'} }, /***** Black board background *****/ 'black board background': { target: '#trello-root', css: {'background-color': 'hsl(204deg 19% 16%)'} }, /***** Remove nag elements *****/ 'board menu > custom fields': { target: '.board-menu-navigation-item.disabled', css: {'display': 'none'} }, 'card edit screen > Start free trial': { target: '.button-link.disabled', css: {'display': 'none'} }, 'anything disabled': { target: '.disabled', css: {'display': 'none'} }, 'card edit screen > Disabled custom fields': { target: '.js-card-back-custom-fields-prompt', css: {'display': 'none'} }, /***** Enhancements *****/ 'show label labels by default': { target: '.card-label.mod-card-front', css: { 'height': '16px', 'line-height': '16px', 'max-width': '198px', 'padding': '0 8px', } } }; /***** Business logic *****/ const addCSSRule = (selector, css_obj) => { if ( typeof css_obj !== 'object' ) throw "Use objects"; const style_id = 'newTrelloStyle'; const style = document.getElementById(style_id) || (function() { const style = document.createElement('style'); style.type = 'text/css'; style.id = style_id; document.head.appendChild(style); return style; })(); const sheet = style.sheet; let css = selector + ' {'; for (const [key, val] of Object.entries(css_obj)) { css += `${key}: ${val} !important`; } css += '}'; sheet.insertRule(css, (sheet.rules || sheet.cssRules || []).length); debug && console.log(`[Debug:addCSSRule] ${css} at ${(sheet.rules || sheet.cssRules || []).length}`) } const getCSSSelector = ($this, path) => { if ( typeof $this.get(0) === 'undefined' ) return ''; if ( typeof path === 'undefined' ) path = ''; if ( $this.is('html') ) return 'html' + path; let cur = $this.get(0).tagName.toLowerCase(); let id = $this.attr('id') let clazz = $this.attr('class'); debug && console.log(`[Debug:getCSSSelector]> ${cur}, ${id}, ${clazz}, (${path})`); // Build a selector with the highest specifity if ( typeof id !== 'undefined' ) { return cur + '#' + id + path; } else if ( typeof clazz !== 'undefined' ) { cur += '.' + clazz.split(/[\s\n]+/).join('.'); } // Recurse up the DOM return getCSSSelector($this.parent(), ' > ' + cur + path ); }; const actionFn = () => { // Exact matches for (const [rule, entry] of Object.entries(exact_matches)) { let $target = $(entry.target); if($target.length) { debug && console.log(`[Debug:actionFn] ${getCSSSelector($target)} css: ${JSON.stringify(entry.css)}`); // Only run these CSS rules once, ever addCSSRule(entry.target, entry.css); delete exact_matches[rule]; } } // Fuzzy matches first for (const [_, entry] of Object.entries(fuzzy_matches)) { let $targets = $(`${entry.target}:contains('${entry.text}')`); $targets.each((_, elem) => { let $parent = entry.closest != '.' ? $(elem).closest(entry.closest) : $(elem); if ($parent.length) { debug && console.log(`[Debug:actionFn] Closest ancestor (${entry.closest}) found from ${elem.tagName}`); for (const child of entry.children) { let $div = child.target !== '.' ? $parent.find(child.target) : $parent; if ($div.length) { debug && console.log(`[Debug:actionFn] ${getCSSSelector($div)} css: ${JSON.stringify(child.css)}`); $div.css(child.css); } } } }); } }; let timer = setInterval(actionFn, 1000); // No choice but to apply periodically as this is a React app $(actionFn); // Run once when the DOM is ready })(jQuery.noConflict()); |
Installation
You can copy and paste the script above into a new Tampermonkey editor, which you can find in the Chrome Web Store. This works in both Chrome and Brave for now.
Conclusion
With a bit of patience and DevTools magic, we’re able to figure out which elements need to be adjusted or hidden on a React page with mangled CSS class names and a dynamic DOM structure—to apply column colors and remove “free trial” distractions.