J Lopes

J Lopes

@jlopes.eu

Frontend developer, designer & writer. Pagan, maker of small things. Occasionally pie.

125 followers535 following82 posts

J Lopes reposted

Since 2004, this community has helped make Firefox and the open internet possible. Our “Come Back to Firefox” campaign reunites a few early internet icons to remind people what made the internet special, and why it’s worth protecting.

Two hundred views on my site analytics. "Lovely. I'm a big hit. I might get a little cake," I think. Until I realise 198 weren't real people. I'm left with two humans, and one of them is me.

The doomscroll is an undying beast we feed within ourselves. It floats a little behind you, drifting closer whenever you look away. It never asks for much, just one more swipe to keep the algorithm alive.

Every scroll made it bigger, feeding from my curiosity and fed me boredom. I try to keep my distance, and sometimes I do well. I rather go outside and touch grass. But my phone is always in my pocket.

Should websites have a light/dark toggle at all? Why lock a page into one experience and force a choice on the user? You decide! (https://jlopes.eu/blog/design-tokens-beyond-dark-mode/)

jlopes.euBeyond Light and Dark (mode)A while ago, Lea Verou argued that in a website, a dark mode toggle only needs two states1: light and dark, and a “system” preference. Many other experts wanted to give their take on the argument. Vale2 found it “a tad too magical” to have system as an additional option, and proposed that the system would set the colour scheme from the get-go. Bramus was in the 3 button side of the debate3, and didn’t ”… believe that being able to force a site into a specific mode should be dependent on the time of the day.” On the other side, Marcin Wichary4 was against the 3-state side, and Josh Collinsworth5 reiterated that Lea got it right the first time. This ran for weeks. And it was refreshing to see how professionals and peers had their own educated opinion. Lea Verou continued6 on a subsequent article, positing that the best toggle is probably none. If it’s deemed important to provide a toggle to the user, then it should be located within the settings. She sent her article to a colleague with an HCI (human-computer interaction) PhD for review. After a back and forth regarding if a dark mode toggle was even commonplace in websites, on her searching she noticed that every persistent toggle found was on sites built for developers. > I could not think of a single well-known consumer-facing site with a persistent dark mode toggle. > **Lea Verou** Other7 professionals8 had9 fun10 takes11. As developers we tend to fall into a false consensus12, thinking we’re the target audience of the products we make. I myself built that toggle more than once, on smaller projects. Where it’s likely I have a captive target audience of 1 😅. ## So don’t ask **Colour is a design decision** , there’s a reason why colour schemes are the way they are. There is room to respect the user’s decision of light and dark mode, if the theme allows for it, but it shouldn’t take the centre stage. In nearly every project I’ve worked on, the design goes through an iterative process of experimentation and user testing. Since CSS variables13 became baseline in all major browsers, it’s been easier to define brand rules, colour variations and beyond. PreviewCSS My padding, margin, shadows, background and text colour, are defined by CSS variables. :root { --bg: #222; --color: hsl(158 100% 60%); --padding: 42px; --width: calc(100% - 4rem); --margin: 2rem; --border-radius: 10px; --shadow: 0 0 20px -3px var(--color); } .test-element { background-color: var(--bg); border-radius: var(--border-radius); box-shadow: var(--shadow); color: var(--color); padding: var(--padding); width: var(--width); margin: var(--margin); } But what if colour also becomes part of the iterative process? What if we don’t limit to a light and a dark and a “Green, actually” theme? ## And now for something completely different! If you’ve been reading up to this point, **we salute you**! This was all a ruse so I could talk about themes. Thinking about how colour compliment each other in a similar layout is fun! On my site you may find a vast array of themes throughout all pages and articles. The blog archive showcases what colour scheme each article might use, and I have a **hidden page** to help me pick from those I had already made. But how does it work, making themes granular to the point that it can be set on a page item level? My process works more or less like the :root {}14 pseudo-class, but I scope it into an element attribute. A data attribute15 in fact! I named it `data-theme`. PreviewHTMLCSS #### Subtitle ### Title Lorem ipsum dolor sit amet. #### Subtitle ### Title Lorem ipsum dolor sit amet. #### Subtitle ### Title Lorem ipsum dolor sit amet. <div class="demo"> <article data-theme="paper"> <h4>Subtitle</h4> <h3>Title</h3> <p>Lorem ipsum dolor sit amet.</p> </article> <article data-theme="midnight"> <h4>Subtitle</h4> <h3>Title</h3> <p>Lorem ipsum dolor sit amet.</p> </article> <article data-theme="citrus"> <h4>Subtitle</h4> <h3>Title</h3> <p>Lorem ipsum dolor sit amet.</p> </article> </div> .demo { display: flex; flex-wrap: wrap; gap: 14px; padding: 24px 18px; width: 100%; [data-theme='paper'] { --surface: hsl(38 44% 97%); --text: hsl(25 22% 20%); --accent: hsl(8 72% 46%); --font: Georgia, 'Times New Roman', serif; --radius: 2px; --border: 1px solid hsl(30 20% 82%); --shadow: 0 1px 2px hsl(25 30% 20% / 0.1), 0 10px 28px hsl(25 30% 20% / 0.08); } [data-theme='midnight'] { --surface: hsl(230 28% 11%); --text: hsl(210 24% 92%); --accent: hsl(158 72% 56%); --font: ui-monospace, 'SF Mono', Menlo, monospace; --radius: 12px; --border: 1px solid hsl(158 72% 56% / 0.35); --shadow: 0 0 30px hsl(158 72% 56% / 0.22); } [data-theme='citrus'] { --surface: hsl(56 78% 91%); --text: hsl(78 42% 18%); --accent: hsl(292 60% 44%); --font: system-ui, 'Segoe UI', sans-serif; --radius: 18px; --border: 0; --shadow: 3px 3px 0 hsl(292 60% 44%); } [data-theme] { --accent-alpha: hsl(from var(--accent) h s l / 35%); } article { flex: 1 1 150px; padding: 16px 18px; background: var(--surface); color: var(--text); font-family: var(--font); border: var(--border); border-radius: var(--radius); box-shadow: var(--shadow); } h4 { font-size: 12px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); margin: 0.5rem 0; } h3 { font-size: 30px; line-height: 1.15; color: var(--accent); margin: 4px 0 12px; } p { font-size: 13px; line-height: 1.7; padding: 7px 9px; background: var(--accent-alpha); border-radius: calc(var(--radius) / 2); } p { margin: 0; } } **The markup can be exactly the same** , and the `data-theme` derives the differences. I’m not claiming this a novel idea, and I’m not the only one talking about it. For example, Una Kravets16 called this a **microtheme**. Even though her article veers more on using css to adapt colours based on backgrounds, it follows the same spirit. ## Rolling for colours Nearly every colour scheme I created was generated from images or randomly using my colour palette tool. The page listens for an image paste anywhere on it, which is helpful when i find an image that translates the exact colours I’m looking for. I set the output for HSL, because it makes it easier (at least in my methods), in case I want to nudge a lightness manually without complicating with calculations. ## Then I check the maths HSL (hue, saturation and lightness) is a handy colour format to easily adapt colours, but not so great to analyse between them. the lightness value is the average of the brightest and dimmest channel, so it treats yellow and blue as equally bright. `hsl(60 100% 50%)` (yellow) and `hsl(240 100% 50%)` (blue) both have 50% lightness, and on white the yellow has a WCAG (Web Content Accessibility Guidelines) 2 score of **1.07:1** while the blue scores **8.59:1**. WCAG 2 measures relative luminance17, and it calculates it on sRGB (standard red, green and blue) channels. Each channel is divided by 255, and is weighted by how bright it looks, relatively. It runs from 1:1 to 21:118, and AA requires 4.5:1 for text versus background. const relativeLuminance = ([red, green, blue]) => { const linear = [red, green, blue].map((channel) => { const value = channel / 255; return value <= 0.03928 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4); }); return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]; }; const contrast = (txt, bg) => { const txtLuminance = relativeLuminance(txt); const bgLuminance = relativeLuminance(bg); return (Math.max(txtLuminance, bgLuminance) + 0.05) / (Math.min(txtLuminance, bgLuminance) + 0.05); }; WCAG 2 is considered the legal minimum19, required in many professional fields. In the near future, APCA (Accessible Perceptual Contrast Algorithm) will probably model better, but as of April 202620 the WCAG 3 draft contrast algorithm is **yet to be determined** , so for now WCAG 2.221 is the official recommendation. ## One last thing With `light-dark()`, each colour have a light value and a dark value, depending on the colour scheme. Set `color-scheme: light dark` first, then use `light-dark()` inside the existing custom properties. The function has been Baseline for a while22, expected to be widely available in november 2026. Each microtheme gets two modes, while the themes keep their identity. And nother thing, some high contrast accessibility settings in operating systems may push for Forced colors23 mode, which may affect any theme negatively. In this situation it’s best to test for good ol’ graceful degradation24 ❤️ Making things readable and accessible, equitable designs, does not remove the fun of the craft. Some of us shine like diamonds, working within constraints. Let’s add colour and style the world our own way, and keep toggles to a minimum!
View full thread

This system was built to be this way. The platform owners control the pipelines, keeping a distracted population easier to manage with dopamine-driven feeds that are laggy and choked with ads. It's convenience, sleight of hand, manufactured consent, showing us puppies so we don't notice the wiring.

The revolution will not be t̶e̶l̶e̶v̶i̶s̶e̶d̶ ̶ notified. Once in a while we just need to put down our phones and remember our rights, before the feed distracts us again.

Algorithms are fine-tuned to cater to our quiet and automated doomscrolls. You check your phone, then hours later, you're watching puppies, forgetting why you picked it up in the first place. It ignores your interests by showing you content that engages you, makes you watch down an endless feed.

"Your submission will be reviewed by a real human" has "this wine is vegan" or "kosher salt" vibes... As opposed to what? Why is not commonplace anymore? 😓

Every SaaS company also relies on other SaaS providers. "Homebrew" solutions are frequently sufficient and help avoid staff and expenses. However, this brought a vicious cycle of not relying on the ecosystem and a reversal of decisions, such as rehiring employees they had previously laid off.

jlopes.euAI is the Poisoned Apple of the SaaS Ecosystem - J LopesCompanies replaced people with AI and are now quietly rehiring them. On why the last 20% of the work still needs humans, and what "good enough" is doing to SaaS.

It all began with a small Unicode lookup tool. I loaded 30,000 blocks on a single web page, and my browser crashed. The fix: render only the ~30 tiles actually on screen, swapping them as you scroll. Full write-up on how virtualization works and if/when you need it, plus a live 10,000-cell demo!

jlopes.euUnderstanding List Virtualization - J LopesHow windowing speeds up huge grids: render only visible rows, tune overscan, and compare how React vs Vue skip re-rendering unchanged rows.

J Lopes reposted

You are up to date.