<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>MCMedia RSS Feed - Blogs</title>
<link>https://news.mcmedia.cam/feed/Blogs</link>
<description>MCMedia News RSS Feed - Blogs specific feed</description>
<docs>https://news.mcmedia.cam/rss-info.html</docs>
<generator>MCMedia RSS Generator v1.0</generator>
<lastBuildDate>Sun, 17 May 2026 02:50:30 +0000</lastBuildDate>
<atom:link href="https://news.mcmedia.cam/rss/rss_blogs.xml" rel="self" type="application/rss+xml"/>
<item>
  <title>External import maps, today!</title>
  <link>https://lea.verou.me/blog/2026/external-import-maps-today/</link>
  <pubDate>Sun, 17 May 2026 03:45:29 +0200</pubDate>
  <description>A few weeks ago, I posted Web dependencies are broken. Can we fix them? . Today’s post is a little less gloomy: Turns out that the major limitation that would allow centralized set-it-and-forget-it import map management can be lifted today, with excellent browser support! The core idea is that you can use DOM methods to inject an import map dynamically , by literally creating an element in a classic (blocking) script and appending it after the injector script. 💡 This is a gamechanger. It makes external import maps nice-to-have sugar instead of the only way to have centralized import map management decoupled from HTML generation. All we need to do is build a little injector script, no need for tightly coupled workflows that take over everything. Once you have that, it takes a single line of HTML to include it anywhere. If you’re already using a templating system, great! You could add to your template for every page. But you don’t need a templating system: even if you’re rawdogging HTML (e.g. for a simple SPA), it’s no big deal to just include a in there manually. This is not even new: when the injector is a classic (non-module) script placed before any modules are fetched, it works in every import map implementation , all the way back to Chrome 89, Safari 16.4+, and Firefox 108+ ! Turns out, JSPM made the same discovery: JSPM v4 uses the same technique . It is unclear why it took all of us so long to discover it but I’m glad we got there. How does it work? Basic import map injector script First, while there is some progress around making import maps more resilient, your best bet for maximum compatibility is for the injector script to be a good ol’ blocking that comes before everything else. This means no type=&quot;module&quot; , no async , no defer — you want to get it in before any modules start loading or many browsers will ignore it. Then, you literally use DOM methods to create a and append it after the script that is injecting the import map (which you can get via document.currentScript ). This is a minimal example: (()=&gt;{ const map = { /* elided */ }; const script = Object.assign(document.createElement(&quot;script&quot;), { type: &quot;importmap&quot;, textContent: JSON.stringify(map) }); document.currentScript.after(script); })(); Fixing relative URLs Remember, this literally injects inline import maps in your page. This means that any relative URLs will be interpreted relative to the current page ! If you’re building an SPA or your URLs are all absolute or root-relative, that’s no biggie. But if these are relative URLs, they will not work as expected across pages. You need to compute the absolute URL for each mapped URL and use that instead. This sounds complicated, but it only adds about 5 more lines of code: (()=&gt;{ const map = { /* elided */ }; const mapUrl = document.currentScript.src; const rebase = m =&gt; { for (let k in m) m[k] = new URL(m[k], mapUrl).href; return m; }; rebase(map.imports); for (let scope in (map.scopes ?? {})) { rebase(map.scopes[scope]); } const script = Object.assign(document.createElement(&quot;script&quot;), { type: &quot;importmap&quot;, textContent: JSON.stringify(map) }); document.currentScript?.after(script); })(); Error handling Note that document.currentScript is null in module scripts, since the same module can be loaded from different places and different scripts. Once it becomes possible to inject import maps from a module script, you could use import.meta.url to get the URL of the current module. Until then, you can use a bit of error handling to catch mistakes: (()=&gt;{ const map = { /* elided */ }; const mapUrl = document.currentScript?.src; if (!mapUrl) { throw new Error(&quot;Import map injector script must be a classic (non-module) script&quot;); } const rebase = m =&gt; { for (let k in m) m[k] = new URL(m[k], mapUrl).href; return m; }; rebase(map.imports); for (let scope in (map.scopes ?? {})) { rebase(map.scopes[scope]); } const script = Object.assign(document.createElement(&quot;script&quot;), { type: &quot;importmap&quot;, textContent: JSON.stringify(map) }); document.currentScript?.after(script); })(); This is the minimum, since the script literally breaks if document.currentScript is null . You could get more elaborate and warn about async / defer attributes, or if type=&quot;module&quot; scripts are present before the current script. These are left as an exercise for the reader. Do we still need external import maps? While this alleviates the immediate need for external import maps , the DX and footguns make it a bit gnarly, so having first-class external import map support would still be a welcome improvement. But even if we could do today, the unfortunate coupling with HTML is still at the receiving end of all this, and creates certain limitations, such as specifiers not working in worker scripts . My position remains that HTML being the only way to include import maps is a hack . I’m not saying this pejoratively. Hacks are often okay — even necessary! — in the short term. This particular hack allowed us to get import maps out the door and shipped quickly, without getting bogged down into architecture astronaut style discussions that can be non-terminating. But it also created architectural debt . These types of issues can always be patched ad hoc, but that increases complexity, both for implementers and web developers. Ultimately, we need deeper integration of specifiers and import maps across the platform . (with or without an src attribute) should become a shortcut , not the only way mappings can be specified. In my earlier post, I outlined a few ideas that could help get us closer to that goal and make import maps ubiquitous and mindless . Since they were well received, I opened issues for them: Linking to import maps via an HTTP header ( Link ?) specifier: URLs to bridge the gap between specifiers and URLs Since import maps as an import attribute proved out to be tricky, I also filed another proposal for a synchronous import map API . I’m linking to issues in standards repos in the interest of transparency. Please don’t spam them, even with supportive comments (that’s what reactions are for). Also keep in mind that the vast majority of import map improvements are meant for tooling authors and infrastructure providers — nobody expects regular web developers to author import maps by hand. The hope is also that better platform-wide integration can pave the way for satisfying the (many!) requests to expand specifiers beyond JS imports . Currently, the platform has no good story for importing non-JS resources from a package, such as styles, images, icons, etc. But even without any further improvement, simply the fact that injector scripts are possible opens up so many possibilities! The moment I found out about this I started working on making the tool I wished had existed to facilitate end-to-end dependency management without a build process (piggybacking on the excellent JSPM Generator for the heavy lifting), which I will announce in a separate post very soon [1] . Stay tuned! But if you’re particularly curious and driven, you can find it even before then, both the repo and npm package are already public 😉🤫 ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>External import maps, today!</title>
  <link>https://lea.verou.me/blog/2026/external-import-maps-today/</link>
  <pubDate>Sun, 17 May 2026 03:45:29 +0200</pubDate>
  <description>A few weeks ago, I posted Web dependencies are broken. Can we fix them? . Todays post is a little less gloomy: Turns out that the major limitation that would allow centralized set-it-and-forget-it import map management can be lifted today, with excellent browser support! The core idea is that you can use DOM methods to inject an import map dynamically , by literally creating an element in a classic (blocking) script and appending it after the injector script. This is a gamechanger. It makes external import maps nice-to-have sugar instead of the only way to have centralized import map management decoupled from HTML generation. All we need to do is build a little injector script, no need for tightly coupled workflows that take over everything. Once you have that, it takes a single line of HTML to include it anywhere. If youre already using a templating system, great! You could add to your template for every page. But you dont need a templating system: even if youre rawdogging HTML (e.g. for a simple SPA), its no big deal to just include a in there manually. This is not even new: when the injector is a classic (non-module) script placed before any modules are fetched, it works in every import map implementation , all the way back to Chrome 89, Safari 16.4+, and Firefox 108+ ! Turns out, JSPM made the same discovery: JSPM v4 uses the same technique . It is unclear why it took all of us so long to discover it but Im glad we got there. How does it work? Basic import map injector script First, while there is some progress around making import maps more resilient, your best bet for maximum compatibility is for the injector script to be a good ol blocking that comes before everything else. This means no type=&quot;module&quot; , no async , no defer you want to get it in before any modules start loading or many browsers will ignore it. Then, you literally use DOM methods to create a and append it after the script that is injecting the import map (which you can get via document.currentScript ). This is a minimal example: (()=&gt;{ const map = { /* elided */ }; const script = Object.assign(document.createElement(&quot;script&quot;), { type: &quot;importmap&quot;, textContent: JSON.stringify(map) }); document.currentScript.after(script); })(); Fixing relative URLs Remember, this literally injects inline import maps in your page. This means that any relative URLs will be interpreted relative to the current page ! If youre building an SPA or your URLs are all absolute or root-relative, thats no biggie. But if these are relative URLs, they will not work as expected across pages. You need to compute the absolute URL for each mapped URL and use that instead. This sounds complicated, but it only adds about 5 more lines of code: (()=&gt;{ const map = { /* elided */ }; const mapUrl = document.currentScript.src; const rebase = m =&gt; { for (let k in m) m[k] = new URL(m[k], mapUrl).href; return m; }; rebase(map.imports); for (let scope in (map.scopes ?? {})) { rebase(map.scopes[scope]); } const script = Object.assign(document.createElement(&quot;script&quot;), { type: &quot;importmap&quot;, textContent: JSON.stringify(map) }); document.currentScript?.after(script); })(); Error handling Note that document.currentScript is null in module scripts, since the same module can be loaded from different places and different scripts. Once it becomes possible to inject import maps from a module script, you could use import.meta.url to get the URL of the current module. Until then, you can use a bit of error handling to catch mistakes: (()=&gt;{ const map = { /* elided */ }; const mapUrl = document.currentScript?.src; if (!mapUrl) { throw new Error(&quot;Import map injector script must be a classic (non-module) script&quot;); } const rebase = m =&gt; { for (let k in m) m[k] = new URL(m[k], mapUrl).href; return m; }; rebase(map.imports); for (let scope in (map.scopes ?? {})) { rebase(map.scopes[scope]); } const script = Object.assign(document.createElement(&quot;script&quot;), { type: &quot;importmap&quot;, textContent: JSON.stringify(map) }); document.currentScript?.after(script); })(); This is the minimum, since the script literally breaks if document.currentScript is null . You could get more elaborate and warn about async / defer attributes, or if type=&quot;module&quot; scripts are present before the current script. These are left as an exercise for the reader. Do we still need external import maps? While this alleviates the immediate need for external import maps , the DX and footguns make it a bit gnarly, so having first-class external import map support would still be a welcome improvement. But even if we could do today, the unfortunate coupling with HTML is still at the receiving end of all this, and creates certain limitations, such as specifiers not working in worker scripts . My position remains that HTML being the only way to include import maps is a hack . Im not saying this pejoratively. Hacks are often okay even necessary! in the short term. This particular hack allowed us to get import maps out the door and shipped quickly, without getting bogged down into architecture astronaut style discussions that can be non-terminating. But it also created architectural debt . These types of issues can always be patched ad hoc, but that increases complexity, both for implementers and web developers. Ultimately, we need deeper integration of specifiers and import maps across the platform . (with or without an src attribute) should become a shortcut , not the only way mappings can be specified. In my earlier post, I outlined a few ideas that could help get us closer to that goal and make import maps ubiquitous and mindless . Since they were well received, I opened issues for them: Linking to import maps via an HTTP header ( Link ?) specifier: URLs to bridge the gap between specifiers and URLs Since import maps as an import attribute proved out to be tricky, I also filed another proposal for a synchronous import map API . Im linking to issues in standards repos in the interest of transparency. Please dont spam them, even with supportive comments (thats what reactions are for). Also keep in mind that the vast majority of import map improvements are meant for tooling authors and infrastructure providers nobody expects regular web developers to author import maps by hand. The hope is also that better platform-wide integration can pave the way for satisfying the (many!) requests to expand specifiers beyond JS imports . Currently, the platform has no good story for importing non-JS resources from a package, such as styles, images, icons, etc. But even without any further improvement, simply the fact that injector scripts are possible opens up so many possibilities! The moment I found out about this I started working on making the tool I wished had existed to facilitate end-to-end dependency management without a build process (piggybacking on the excellent JSPM Generator for the heavy lifting), which I will announce in a separate post very soon [1] . Stay tuned! But if youre particularly curious and driven, you can find it even before then, both the repo and npm package are already public</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Web dependencies are broken. Can we fix them?</title>
  <link>https://lea.verou.me/blog/2026/web-deps/</link>
  <pubDate>Sun, 17 May 2026 03:45:27 +0200</pubDate>
  <description>No, this is not another rant about npm’s security issues. Abstraction is the cornerstone of modern software engineering. Reusing logic and building higher-level solutions from lower-level building blocks is what makes all the technological wonders around us possible. Imagine if every time anyone wrote a calculator they also had to reinvent floating-point arithmetic and string encoding! In healthy ecosystems dependencies are normal, cheap, and first-class. “Dependency-free” is not a badge of honor. And yet, the web platform has outsourced this fundamental functionality to third-party tooling . As a result, code reuse has become a balancing of tradeoffs that should not have existed in the first place. In NodeJS, you just npm install and reference specifiers straight away in your code. Same in Python, with pip install . Same in Rust with cargo add . In healthy ecosystems you don’t ponder how or whether to use dependencies. The ecosystem assumes dependencies are normal, cheap, and first-class . You just install them, use them, and move on. “Dependency-free” is not a badge of honor. Instead, dependency management in the web platform consists of bits and bobs of scattered primitives, with no coherent end-to-end solution . Naturally, bundlers such as Webpack , rollup , and esbuild have picked up the slack, with browserify being the one that started it all, in 2012. There is nothing wrong with bundlers when used as a performance optimization to minimize waterfall effects and overhead from too many HTTP requests. You know, what a bundler is supposed to do. It is okay to require advanced tools for advanced needs , and performance optimization is generally an advanced use case. Same for most other things bundlers and build tools are used for, such as strong typing, linting, or transpiling. All of these are needs that come much later than dependency management, both in a programmer’s learning journey, as well as in a project’s development lifecycle. Dependency management is such a basic and ubiquitous need, it should be a part of the platform, decoupled from bundling. Requiring advanced tools for basic needs is a textbook usability cliff . In other ecosystems, optimizations happen (and are learned) after dependency resolution. On the web, optimization is the price of admission! This is not normal. Bundlers have become so ubiquitous that most JS developers cannot even imagine deploying code without them. READMEs are written assuming a bundler, without even mentioning the assumption. It’s just how JS is consumed. My heart breaks for the newbie trying to use a drag and drop library, only to get mysterious errors about specifiers that failed to resolve. However, bundling is not technically a necessary step of dependency management. Importing files through URLs is natively supported in every browser, via ESM imports. HTTP/2 makes importing multiple small files far more reasonable than it used to be — at least from a connection overhead perspective. You can totally get by without bundlers in a project that doesn’t use any libraries. But the moment you add that first dependency, everything changes. You are suddenly faced with a huge usability cliff : which bundler to use, how to configure it, how to deploy with it, a mountain of decisions standing between you and your goal of using that one dependency. That one drag and drop library. For newcomers, this often comes very early in their introduction to the web platform, and it can be downright overwhelming. Dependencies without bundlers, today? It is technically possible to use dependencies without bundlers, today. There are a few different approaches, and — I will not sugarcoat it — they all suck . There are three questions here: Use specifiers or URLs? How to resolve specifiers to URLs? Which URL do my dependencies live at? There is currently no good answer to any of them, only fragile workarounds held together by duct tape. Using a dependency should not need any additional song and dance besides “install this package” + “now import it here”. That’s it. That’s the minimum necessary to declare intent . And that’s precisely how it works in NodeJS and other JS runtimes. Anything beyond that is reducing signal-to-noise ratio , especially if it needs to be done separately for every project or worse, for every dependency. You may need to have something to bite hard on while reading the next few sections. It’s going to be bad . Rawdogging node_modules/ imports Typically, package managers like npm take care of deduplicating compatible package versions and may use a directory like node_modules to install packages. In theory, one could deploy node_modules/ as part of their website and directly reference files in client-side JS. For example, to use Vue : import { createApp } from &quot;../node_modules/vue/dist/vue.esm-browser.js&quot;; It works out of the box, and is a very natural thing to try the first time you install a package and you notice node_modules . Great, right? No. Not great. First, deploying your entire node_modules directory is both wasteful , and a security risk . In fact, most serverless hosts (e.g. Netlify or Vercel ) automatically remove it from the publicly deployed files after the build is finished. Additionally, it violates encapsulation : paths within a package are generally seen as an implementation detail of the package itself, and packages expose specifier exports like vue or colorjs.io/fn that they map to internal paths. If you decide to circumvent this and link to files directly, you now need to update your import paths whenever you update the package. It is also fragile, as not every module is installed directly in node_modules/ — though those explicitly marked as app dependencies are. Importing from public CDNs Another common path is importing from CDNs like Unpkg and JSDelivr . For Vue, it would look like this: import { createApp } from &quot;https://unpkg.com/vue@3/dist/vue.esm-browser.js&quot;; It’s quick and easy. Nothing to install or configure! Great, right? No. Not great. It is always a bad idea to introduce a dependency on a whole other domain you do not control , and an even worse one when linking to executable code. First, there is the obvious security risk. Unless you link to a specific version, down to the patch number and/or use SRI , the resource could turn malicious overnight under your nose if the package is compromised. And even if you link to a specific version, there is always the risk that the CDN itself could get compromised. Who remembers polyfill.io ? But even supply-chain attacks aside, any third-party domain is an unnecessary additional point of failure . I still remember scrambling to change JSDelivr URLs to Unpkg during an outage right before one of my talks, or having to hunt down all my repos that used RawGit URLs when it sunset, including many libraries. The DX is also suboptimal. You lose the immediacy and resilience of local, relative paths. Without additional tooling ( Requestly , hosts file edits, etc.), you now need to wait for CDN roundtrips even during local development. Wanted to code on a flight? Good luck. Needed to show a live demo during a talk, over clogged conference wifi? Maybe sacrifice a goat to the gods first. And while they maintain encapsulation slightly better than raw file imports, as they let you reference a package by its name for its default export, additional specifiers (e.g. packagename/fn ) typically still require importing by file path. “But with public CDNs, I benefit from the resource having already been cached by another website the user visited!” Oh my sweet summer child. I hate to be the one to break it to you, but no, you don’t, and that has been the case since about 2020 . Double keyed caching obliterated this advantage . What is double keyed caching? In case you were not aware, yes, your browser will redownload every single resource anew for every single website (origin) that requests it. Yes, even if it’s exactly the same. This changed to prevent cross-site leaks : malicious websites could exfiltrate information about your past network activity by measuring how long a resource took to download, and thus infer whether it was cached. Those who have looked into this problem claim that there is no other way to prevent these timing attacks other than to actually redownload the resource. No way for the browser to even fake a download by simply delaying the response. Even requiring resources to opt-in (e.g. via CORS) was ruled out, the concern being that websites could then use it as a third-party tracking mechanism. I personally have trouble accepting that such wasteful bandwidth usage was the best balance of tradeoffs for all Web users , including those in emerging economies and different locales [1] . It’s not that I don’t see the risks — it’s that I am acutely aware of the cost, a cost that is disproportionately borne by those not in the Wealthy Western Web . How likely is it that a Web user in Zimbabwe, where 1 GB of bandwidth costs 17% of the median monthly income , would choose to download React or nine weights of Roboto thousands of times to avoid seeing personalized ads? And how patronizing is it for people in California to be making this decision for them? node_modules imports locally + rewrite to CDN remotely A quick and dirty way to get local URLs for local development and CDN URLs for the remote site is to link to relative ./node_modules URLs, and add a URL rewrite to a CDN if that is not found. E.g. with Netlify rewrites this looks like this: node_modules/:modulename/* https://cdn.jsdelivr.net/npm/:modulename@latest/:splat 301 Since node_modules is not deployed, this will always redirect on the remote URL, while still allowing for local URLs during development. Great, right? No. Not great. Like the mythical hydra, it solves one problem and creates two new ones. First, it still carries many of the same issues of the approaches it combines: Linking to CDNs is inherently insecure It breaks encapsulation of the dependencies Additionally, it introduces a new problem: the two files need to match, but the naïve approach above would always just link to the latest version. Sure, one could alleviate this by building the _redirects file with tooling, to link to specific versions, read from package-lock.json . But the point is not that it’s insurmountable, but that it should not be this hard . Copy packages or exports to local directory Another solution is a lightweight build script that copies either entire packages or specific exports into a directory that will actually get deployed. When dependencies are few, this can be as simple as an npm script: { &quot;scripts&quot;: { &quot;lib&quot;: &quot;cp node_modules/vue/dist/vue.esm-browser.js common/lib/vue.js&quot;, &quot;build&quot;: &quot;npm run lib&quot; } } So now we have our own nice subset of node_modules/ and we don’t depend on any third-party domains. Great, right? No. Not great. Just like most other solution, this still breaks encapsulation, forcing us to maintain a separate, ad-hoc index of specifiers to file paths. Additionally, it has no awareness of the dependency graph. Dependencies of dependencies need to be copied separately. But wait a second. Did I say dependencies of dependencies? How would that even work? Dependencies that use dependencies In addition to their individual flaws, all of the solutions above share a major flaw: they can only handle importing dependency-free packages . But what happens if the package you’re importing also uses dependencies? It gets unimaginably worse my friend, that’s what happens. There is no reasonable way for a library author to link to dependencies without excluding certain consumer workflows. There is no local URL a library author can use to reliably link to dependencies, and CDN URLs are highly problematic. Specifiers are the only way here. So the moment you include a dependency that uses dependencies, you’re forced into specifier-based dependency management workflows , whether these are bundlers, or import map flavored JSON vomit in every single HTML page (discussed later). “Browser” bundles As a fig leaf, libraries will often provide a “browser” bundle that consumers can import instead of their normal dist , which does not use specifiers. This combines all their dependencies into a single dependency-free file that you can import from a browser. This means they can use whatever dependencies they want, and you can still import that bundle using regular ESM imports in a browser, sans bundler. Great, right? No. Not great. It’s called a bundle for a reason. It bundles all their dependencies too, and now they cannot be shared with any other dependency in your tree, even if it’s exactly the same version of exactly the same package. You’re not avoiding bundling, you’re outsourcing it , and multiplying the size of your JS code in the process. And if the library author has not done that, you’re stuck with little to do, besides a CDN that rewrites specifiers on the fly like esm.sh , with all CDN downsides described above. As someone who regularly releases open source packages ( some with billions of npm installs ), I find this incredibly frustrating. I want to write packages that can be consumed by people using or not using bundlers, without penalizing either group , but the only way to do that today is to basically not use any dependencies. I cannot even modularize my own packages without running into this! This doesn’t scale. But won’t import maps solve all our problems? Browsers can import specifiers, as long as the mapping to a URL is explicitly provided through an import map . Import maps look like this: { &quot;imports&quot;: { &quot;vue&quot;: &quot;./node_modules/vue/dist/vue.runtime.esm-bundler.js&quot;, &quot;lodash&quot;: &quot;./node_modules/lodash-es/lodash.js&quot;, } } Did you notice something? Yes, this is an HTML block. No, I cannot link to an import map that lives in a separate file. Instead, I have to include the darn thing in. Every. Single. Page. The moment you decide to use JS dependencies, you now need an HTML templating tool as well. 🙃 “💡 Oh I know, I’ll generate this from my library via DOM methods! ” I hear you say. No, my sweet summer child. It needs to be present at parse time. So unless you’re willing to document.write() it (please don’t), the answer is a big flat NOPE. Edit: I’ve never been happier to be wrong! Turns out injecting import maps via DOM methods actually works in all browsers (as long as it is done by a non-module script that runs before any modules are fetched)! In fact, JSPM 4.0 uses it! 🎉 This is a gamechanger . “💡 Ok, at least I’ll keep it short by routing everything through a CDN or the same local folder ” No, my sweet summer child. Go to sleep and dream of globs and URLPatterns . Then wake up and get to work, because you actually need to specify. Every. Single. Mapping. Yes, transitive dependencies too. You wanted to use dependencies? You will pay with your blood, sweat, and tears. Or, well, another build tool. So now I need a build tool to manage the import map , like JSPM . It also needs to talk to my HTML templating tool, which I now had to add so it can spit out these import maps on. Every. Single. HTML. Page. There are three invariants that import maps violate: Locality : Dependency declarations live in HTML, not JS. Libraries cannot declare their own dependencies. Composability : Import maps do not compose across dependencies and require global coordination Scalability : Mapping every transitive dependency is not viable without tooling Plus, you still have all of the issues discussed above, because you still need URLs to link to. By trying to solve your problem with import maps, you now got multiple problems. To sum up, in their current form, import maps don’t eliminate bundlers — they recreate them in JSON form, while adding an HTML dependency and worse latency. Are bundlers the lesser evil? Given the current state of the ecosystem, not using bundlers in any nontrivial application does seem like an exercise in masochism. Indeed, per State of JS 2024 , bundlers were extremely popular, with Webpack having been used by 9 in 10 developers and having close to 100% awareness! But sorting by sentiment paints a different picture, with satisfaction, interest, and positivity dropping year after year. Even those who never question the status quo can feel it in their gut that this is not okay. This is not a reasonable way to manage dependencies. This is not a healthy ecosystem. Out of curiosity, I also ran two polls on my own social media. Obviously, this suffers from selection bias , due to the snowball sampling nature of social media, but I was still surprised to see such a high percentage of bundle-less JS workflows: Twitter/X poll : 17.6% of respondents Mastodon poll : 40% (!) of respondents I’m very curious how these folks manage the problems discussed here. Oftentimes when discussing these issues, I get the question “but other languages are completely compiled, why is it a problem here?”. Yes, but their compiler is official and always there. You literally can’t use the language without it. The problem is not compilation, it’s fragmentation. It’s the experience of linking to a package via a browser import only to see errors about specifiers. It’s adding mountains of config and complexity to use a utility function. It’s having no clear path to write a package that uses another package, even if both are yours. Abstraction itself is not something to outsource to third-party tools. This is the programming equivalent of privatizing fundamental infrastructure — roads, law enforcement, healthcare — systems that work precisely because everyone can rely on them being there. Like boiling frogs , JS developers have resigned themselves to immense levels of complexity and gruntwork as simply how things are . The rise of AI introduced swaths of less technical folks to web development and their overwhelm and confusion is forcing us to take a long hard look at the current shape of the ecosystem — and it’s not pretty. Few things must always be part of a language’s standard library, but dependency management is absolutely one of them. Any cognitive overhead should be going into deciding which library to use, not whether to include it and how . This is also actively harming web platform architecture . Because bundlers are so ubiquitous, we have ended up designing the platform around them, when it should be the opposite. For example, because import.meta.url is unreliable when bundlers are used, components have no robust way to link to other resources (styles, images, icons, etc.) relative to themselves, unless these resources can be part of the module tree. So now we are adding features to the web platform that break any reasonable assumption about what HTML, CSS, and JS are, like JS imports for CSS and HTML, which could have been a simple fetch() if web platform features could be relied on. And because using dependencies is nontrivial, we are adding features to the standard library that could have been userland or even browser-provided dependencies. To reiterate, the problem isn’t that bundlers exist — it’s that they are the only viable way to get first-class dependency management on the web. JS developers deserve better. The web platform deserves better. Where do we go from here? As a web standards person, my first thought when spotting such a lacking is “how can the web platform improve?”. And after four years in the TAG , I cannot shake the holistic architectural perspective of “which part of the Web stack is best suited for this?” Specifiers vs URLs Before we can fix this, we need to understand why it is the way it is. What is the fundamental reason the JS ecosystem overwhelmingly prefers specifiers over URLs? On the surface, people often quote syntax, but that seems to be a red herring. There is little DX advantage of foo (a specifier) over ./foo.js (a URL), or even ./foo (which can be configured to have a JS MIME type). Another oft-cited reason is immutability: Remote URLs can change, whereas specifiers cannot. This also appears to be a red herring: local URLs can be just as immutable as specifiers. Digging deeper, it seems that the more fundamental reason has to do with purview . A URL is largely the same everywhere, whereas foo can resolve to different things depending on context. A specifier is app-controlled whereas a URL is not. There needs to be a standard location for a dependency to be located and referenced from, and that needs to be app-controlled. Additionally, specifiers are universal . Once a package is installed, it can be imported from anywhere, without having to work out paths. The closest HTTP URLs can get to this is root-relative URLs, and that’s still not quite the same. Specifiers are clearly the path of least resistance here, so the low hanging fruit would be to make it easier to map specifiers to URLs, starting by improving import maps. Improving import maps An area with huge room for improvement here is import maps . Both making it easier to generate and include import maps, and making the import maps themselves smaller, leaner, and easier to maintain. External import maps The biggest need here is external import maps , even if it’s only via . This would eliminate the dependency on HTML templating and opens the way for generating them with a simple build tool. This was actually part of the original import map work , and was removed from the spec due to lack of implementer interest, despite overwhelming demand. In 2022, external import maps were prototyped in WebKit (Safari), which prompted a new WHATWG issue . Unfortunately, it appears that progress has since stalled once more. Import maps without HTML? External import maps do alleviate some of the core pain points, but are still globally managed in HTML, which hinders composability and requires heavier tooling. What if import maps could be imported into JS code? If JS could import import maps, (e.g. via import &quot;map.json&quot; with { type: &quot;importmap&quot; } ), this would eliminate the dependency on HTML altogether, allowing for scripts to localize their own import info, and for the graph to be progressively composed instead of globally managed. Edit: Turns out that injecting import maps via DOM methods actually works in all browsers (as long as certain conditions are met)! 🎉 This alleviates the need for external import maps as a regular JS file can just inject them. Import maps via HTTP header? Going further, import maps via an HTTP header (e.g. Link ) would even allow webhosts to generate them for you and send them down the wire completely transparently. This could be the final missing piece for making dependencies truly first-class. Imagine a future where you just install packages and use specifiers without setting anything up, without compiling any files into other files, with the server transparently handling the mapping ! Deploying dependencies to URLs However, import maps need URLs to map specifiers to, so we also need some way to deploy the relevant subset of node_modules to public-facing URLs, as deploying the entire node_modules directory is not a viable option. clientDependencies in package.json ? One solution might be a way to explicitly mark dependencies as client side , possibly even specific exports. This would decouple detection from processing app files: in complex apps it can be managed via tooling, and in simple apps it could even be authored manually, since it would only include top-level dependencies. Figuring out the dependency graph Even if we had better ways to mark which dependencies are client-side and map specifiers to URLs, these are still pieces of the puzzle, not the entire puzzle. Without a way to figure out what depends on what, transitive dependencies will still need to be managed globally at the top level, defeating any hope of a tooling-light workflow. The current system relies on reading and parsing thousands of package.json files to build the dependency graph. This is reasonable for a JS runtime where the cost of file reads is negligible, but not for a browser where HTTP roundtrips are costly. And even if it were, this does not account for any tree-shaking. Defining specifiers as a type of URL? Think of how this works when using URLs: modules simply link to other URLs and the graph is progressively composed through these requests. What if specifiers could work the same way? What if we could look up and route specifiers when they are actually imported? Here’s a radical idea: What if specifiers were just another type of URL , and specifier resolution could be handled by the server in the same way a URL is resolved when it is requested? They could use a specifier: protocol, that can be omitted in certain contexts, such as ESM imports. How would these URLs be different than regular local URLs? Their protocol would be implied in certain contexts — that would be how we can import bare specifiers in ESM Their resolution would be customizable (e.g. through import maps, or even regular URL rewrites) Despite looking like absolute URLs, their resolution would depend on the request’s Origin header (thus allowing different modules to use different versions of the same dependency). A request to a specifier: URL without an Origin header would fail. HTTP caching would work differently; basically in a way that emulates the current behavior of the JS module cache. Architecturally, this has several advantages: It bridges the gap between specifiers and URLs . Rather than having two entirely separate primitives for linking to a resource, it makes specifiers a high-level primitive and URLs the low-level primitive that explains it. It allows retrofitting specifiers into parts of the platform that were not designed for them, such as CSS @import . This is not theoretical: I was at a session at TPAC where bringing specifiers to CSS was discussed. With this, every part of the platform that takes URLs can now utilize specifiers, it would just need to specify the protocol explicitly. Obviously, this is just a loose strawman at this point, and would need a lot of work to turn into an actual proposal (which I’d be happy to help out with, with funding ), but I suspect we need some way to bridge the gap between these two fundamentally different ways to import modules. Too radical? Quite likely. But abstraction is foundational, and you often need radical solutions to fix foundational problems. Even if this is not the right path, I doubt incremental improvements can get us out of this mess for good. But in the end, this is about the problem . I’m much more confident that the problem needs solving, than I am of any particular solution. Hopefully, after reading this, so are you. So this is a call to action for the community. To browser vendors, to standards groups, to individual developers. Let’s fix this! 💪🏼 Thanks to Jordan Harband , Wes Todd , and Anne van Kesteren for reviewing earlier versions of this draft. In fact, when I was in the TAG, Sangwhan Moon and I drafted a Finding on the topic, but the TAG never reached consensus on it. ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Web dependencies are broken. Can we fix them?</title>
  <link>https://lea.verou.me/blog/2026/web-deps/</link>
  <pubDate>Sun, 17 May 2026 03:45:27 +0200</pubDate>
  <description>No, this is not another rant about npms security issues. Abstraction is the cornerstone of modern software engineering. Reusing logic and building higher-level solutions from lower-level building blocks is what makes all the technological wonders around us possible. Imagine if every time anyone wrote a calculator they also had to reinvent floating-point arithmetic and string encoding! In healthy ecosystems dependencies are normal, cheap, and first-class. Dependency-free is not a badge of honor. And yet, the web platform has outsourced this fundamental functionality to third-party tooling . As a result, code reuse has become a balancing of tradeoffs that should not have existed in the first place. In NodeJS, you just npm install and reference specifiers straight away in your code. Same in Python, with pip install . Same in Rust with cargo add . In healthy ecosystems you dont ponder how or whether to use dependencies. The ecosystem assumes dependencies are normal, cheap, and first-class . You just install them, use them, and move on. Dependency-free is not a badge of honor. Instead, dependency management in the web platform consists of bits and bobs of scattered primitives, with no coherent end-to-end solution . Naturally, bundlers such as Webpack , rollup , and esbuild have picked up the slack, with browserify being the one that started it all, in 2012. There is nothing wrong with bundlers when used as a performance optimization to minimize waterfall effects and overhead from too many HTTP requests. You know, what a bundler is supposed to do. It is okay to require advanced tools for advanced needs , and performance optimization is generally an advanced use case. Same for most other things bundlers and build tools are used for, such as strong typing, linting, or transpiling. All of these are needs that come much later than dependency management, both in a programmers learning journey, as well as in a projects development lifecycle. Dependency management is such a basic and ubiquitous need, it should be a part of the platform, decoupled from bundling. Requiring advanced tools for basic needs is a textbook usability cliff . In other ecosystems, optimizations happen (and are learned) after dependency resolution. On the web, optimization is the price of admission! This is not normal. Bundlers have become so ubiquitous that most JS developers cannot even imagine deploying code without them. READMEs are written assuming a bundler, without even mentioning the assumption. Its just how JS is consumed. My heart breaks for the newbie trying to use a drag and drop library, only to get mysterious errors about specifiers that failed to resolve. However, bundling is not technically a necessary step of dependency management. Importing files through URLs is natively supported in every browser, via ESM imports. HTTP/2 makes importing multiple small files far more reasonable than it used to be at least from a connection overhead perspective. You can totally get by without bundlers in a project that doesnt use any libraries. But the moment you add that first dependency, everything changes. You are suddenly faced with a huge usability cliff : which bundler to use, how to configure it, how to deploy with it, a mountain of decisions standing between you and your goal of using that one dependency. That one drag and drop library. For newcomers, this often comes very early in their introduction to the web platform, and it can be downright overwhelming. Dependencies without bundlers, today? It is technically possible to use dependencies without bundlers, today. There are a few different approaches, and I will not sugarcoat it they all suck . There are three questions here: Use specifiers or URLs? How to resolve specifiers to URLs? Which URL do my dependencies live at? There is currently no good answer to any of them, only fragile workarounds held together by duct tape. Using a dependency should not need any additional song and dance besides install this package + now import it here. Thats it. Thats the minimum necessary to declare intent . And thats precisely how it works in NodeJS and other JS runtimes. Anything beyond that is reducing signal-to-noise ratio , especially if it needs to be done separately for every project or worse, for every dependency. You may need to have something to bite hard on while reading the next few sections. Its going to be bad . Rawdogging node_modules/ imports Typically, package managers like npm take care of deduplicating compatible package versions and may use a directory like node_modules to install packages. In theory, one could deploy node_modules/ as part of their website and directly reference files in client-side JS. For example, to use Vue : import { createApp } from &quot;../node_modules/vue/dist/vue.esm-browser.js&quot;; It works out of the box, and is a very natural thing to try the first time you install a package and you notice node_modules . Great, right? No. Not great. First, deploying your entire node_modules directory is both wasteful , and a security risk . In fact, most serverless hosts (e.g. Netlify or Vercel ) automatically remove it from the publicly deployed files after the build is finished. Additionally, it violates encapsulation : paths within a package are generally seen as an implementation detail of the package itself, and packages expose specifier exports like vue or colorjs.io/fn that they map to internal paths. If you decide to circumvent this and link to files directly, you now need to update your import paths whenever you update the package. It is also fragile, as not every module is installed directly in node_modules/ though those explicitly marked as app dependencies are. Importing from public CDNs Another common path is importing from CDNs like Unpkg and JSDelivr . For Vue, it would look like this: import { createApp } from &quot;https://unpkg.com/vue@3/dist/vue.esm-browser.js&quot;; Its quick and easy. Nothing to install or configure! Great, right? No. Not great. It is always a bad idea to introduce a dependency on a whole other domain you do not control , and an even worse one when linking to executable code. First, there is the obvious security risk. Unless you link to a specific version, down to the patch number and/or use SRI , the resource could turn malicious overnight under your nose if the package is compromised. And even if you link to a specific version, there is always the risk that the CDN itself could get compromised. Who remembers polyfill.io ? But even supply-chain attacks aside, any third-party domain is an unnecessary additional point of failure . I still remember scrambling to change JSDelivr URLs to Unpkg during an outage right before one of my talks, or having to hunt down all my repos that used RawGit URLs when it sunset, including many libraries. The DX is also suboptimal. You lose the immediacy and resilience of local, relative paths. Without additional tooling ( Requestly , hosts file edits, etc.), you now need to wait for CDN roundtrips even during local development. Wanted to code on a flight? Good luck. Needed to show a live demo during a talk, over clogged conference wifi? Maybe sacrifice a goat to the gods first. And while they maintain encapsulation slightly better than raw file imports, as they let you reference a package by its name for its default export, additional specifiers (e.g. packagename/fn ) typically still require importing by file path. But with public CDNs, I benefit from the resource having already been cached by another website the user visited! Oh my sweet summer child. I hate to be the one to break it to you, but no, you dont, and that has been the case since about 2020 . Double keyed caching obliterated this advantage . What is double keyed caching? In case you were not aware, yes, your browser will redownload every single resource anew for every single website (origin) that requests it. Yes, even if its exactly the same. This changed to prevent cross-site leaks : malicious websites could exfiltrate information about your past network activity by measuring how long a resource took to download, and thus infer whether it was cached. Those who have looked into this problem claim that there is no other way to prevent these timing attacks other than to actually redownload the resource. No way for the browser to even fake a download by simply delaying the response. Even requiring resources to opt-in (e.g. via CORS) was ruled out, the concern being that websites could then use it as a third-party tracking mechanism. I personally have trouble accepting that such wasteful bandwidth usage was the best balance of tradeoffs for all Web users , including those in emerging economies and different locales [1] . Its not that I dont see the risks its that I am acutely aware of the cost, a cost that is disproportionately borne by those not in the Wealthy Western Web . How likely is it that a Web user in Zimbabwe, where 1 GB of bandwidth costs 17% of the median monthly income , would choose to download React or nine weights of Roboto thousands of times to avoid seeing personalized ads? And how patronizing is it for people in California to be making this decision for them? node_modules imports locally + rewrite to CDN remotely A quick and dirty way to get local URLs for local development and CDN URLs for the remote site is to link to relative ./node_modules URLs, and add a URL rewrite to a CDN if that is not found. E.g. with Netlify rewrites this looks like this: node_modules/:modulename/* https://cdn.jsdelivr.net/npm/:modulename@latest/:splat 301 Since node_modules is not deployed, this will always redirect on the remote URL, while still allowing for local URLs during development. Great, right? No. Not great. Like the mythical hydra, it solves one problem and creates two new ones. First, it still carries many of the same issues of the approaches it combines: Linking to CDNs is inherently insecure It breaks encapsulation of the dependencies Additionally, it introduces a new problem: the two files need to match, but the naïve approach above would always just link to the latest version. Sure, one could alleviate this by building the _redirects file with tooling, to link to specific versions, read from package-lock.json . But the point is not that its insurmountable, but that it should not be this hard . Copy packages or exports to local directory Another solution is a lightweight build script that copies either entire packages or specific exports into a directory that will actually get deployed. When dependencies are few, this can be as simple as an npm script: { &quot;scripts&quot;: { &quot;lib&quot;: &quot;cp node_modules/vue/dist/vue.esm-browser.js common/lib/vue.js&quot;, &quot;build&quot;: &quot;npm run lib&quot; } } So now we have our own nice subset of node_modules/ and we dont depend on any third-party domains. Great, right? No. Not great. Just like most other solution, this still breaks encapsulation, forcing us to maintain a separate, ad-hoc index of specifiers to file paths. Additionally, it has no awareness of the dependency graph. Dependencies of dependencies need to be copied separately. But wait a second. Did I say dependencies of dependencies? How would that even work? Dependencies that use dependencies In addition to their individual flaws, all of the solutions above share a major flaw: they can only handle importing dependency-free packages . But what happens if the package youre importing also uses dependencies? It gets unimaginably worse my friend, thats what happens. There is no reasonable way for a library author to link to dependencies without excluding certain consumer workflows. There is no local URL a library author can use to reliably link to dependencies, and CDN URLs are highly problematic. Specifiers are the only way here. So the moment you include a dependency that uses dependencies, youre forced into specifier-based dependency management workflows , whether these are bundlers, or import map flavored JSON vomit in every single HTML page (discussed later). Browser bundles As a fig leaf, libraries will often provide a browser bundle that consumers can import instead of their normal dist , which does not use specifiers. This combines all their dependencies into a single dependency-free file that you can import from a browser. This means they can use whatever dependencies they want, and you can still import that bundle using regular ESM imports in a browser, sans bundler. Great, right? No. Not great. Its called a bundle for a reason. It bundles all their dependencies too, and now they cannot be shared with any other dependency in your tree, even if its exactly the same version of exactly the same package. Youre not avoiding bundling, youre outsourcing it , and multiplying the size of your JS code in the process. And if the library author has not done that, youre stuck with little to do, besides a CDN that rewrites specifiers on the fly like esm.sh , with all CDN downsides described above. As someone who regularly releases open source packages ( some with billions of npm installs ), I find this incredibly frustrating. I want to write packages that can be consumed by people using or not using bundlers, without penalizing either group , but the only way to do that today is to basically not use any dependencies. I cannot even modularize my own packages without running into this! This doesnt scale. But wont import maps solve all our problems? Browsers can import specifiers, as long as the mapping to a URL is explicitly provided through an import map . Import maps look like this: { &quot;imports&quot;: { &quot;vue&quot;: &quot;./node_modules/vue/dist/vue.runtime.esm-bundler.js&quot;, &quot;lodash&quot;: &quot;./node_modules/lodash-es/lodash.js&quot;, } } Did you notice something? Yes, this is an HTML block. No, I cannot link to an import map that lives in a separate file. Instead, I have to include the darn thing in. Every. Single. Page. The moment you decide to use JS dependencies, you now need an HTML templating tool as well. Oh I know, Ill generate this from my library via DOM methods! I hear you say. No, my sweet summer child. It needs to be present at parse time. So unless youre willing to document.write() it (please dont), the answer is a big flat NOPE. Edit: Ive never been happier to be wrong! Turns out injecting import maps via DOM methods actually works in all browsers (as long as it is done by a non-module script that runs before any modules are fetched)! In fact, JSPM 4.0 uses it! This is a gamechanger . Ok, at least Ill keep it short by routing everything through a CDN or the same local folder No, my sweet summer child. Go to sleep and dream of globs and URLPatterns . Then wake up and get to work, because you actually need to specify. Every. Single. Mapping. Yes, transitive dependencies too. You wanted to use dependencies? You will pay with your blood, sweat, and tears. Or, well, another build tool. So now I need a build tool to manage the import map , like JSPM . It also needs to talk to my HTML templating tool, which I now had to add so it can spit out these import maps on. Every. Single. HTML. Page. There are three invariants that import maps violate: Locality : Dependency declarations live in HTML, not JS. Libraries cannot declare their own dependencies. Composability : Import maps do not compose across dependencies and require global coordination Scalability : Mapping every transitive dependency is not viable without tooling Plus, you still have all of the issues discussed above, because you still need URLs to link to. By trying to solve your problem with import maps, you now got multiple problems. To sum up, in their current form, import maps dont eliminate bundlers they recreate them in JSON form, while adding an HTML dependency and worse latency. Are bundlers the lesser evil? Given the current state of the ecosystem, not using bundlers in any nontrivial application does seem like an exercise in masochism. Indeed, per State of JS 2024 , bundlers were extremely popular, with Webpack having been used by 9 in 10 developers and having close to 100% awareness! But sorting by sentiment paints a different picture, with satisfaction, interest, and positivity dropping year after year. Even those who never question the status quo can feel it in their gut that this is not okay. This is not a reasonable way to manage dependencies. This is not a healthy ecosystem. Out of curiosity, I also ran two polls on my own social media. Obviously, this suffers from selection bias , due to the snowball sampling nature of social media, but I was still surprised to see such a high percentage of bundle-less JS workflows: Twitter/X poll : 17.6% of respondents Mastodon poll : 40% (!) of respondents Im very curious how these folks manage the problems discussed here. Oftentimes when discussing these issues, I get the question but other languages are completely compiled, why is it a problem here?. Yes, but their compiler is official and always there. You literally cant use the language without it. The problem is not compilation, its fragmentation. Its the experience of linking to a package via a browser import only to see errors about specifiers. Its adding mountains of config and complexity to use a utility function. Its having no clear path to write a package that uses another package, even if both are yours. Abstraction itself is not something to outsource to third-party tools. This is the programming equivalent of privatizing fundamental infrastructure roads, law enforcement, healthcare systems that work precisely because everyone can rely on them being there. Like boiling frogs , JS developers have resigned themselves to immense levels of complexity and gruntwork as simply how things are . The rise of AI introduced swaths of less technical folks to web development and their overwhelm and confusion is forcing us to take a long hard look at the current shape of the ecosystem and its not pretty. Few things must always be part of a languages standard library, but dependency management is absolutely one of them. Any cognitive overhead should be going into deciding which library to use, not whether to include it and how . This is also actively harming web platform architecture . Because bundlers are so ubiquitous, we have ended up designing the platform around them, when it should be the opposite. For example, because import.meta.url is unreliable when bundlers are used, components have no robust way to link to other resources (styles, images, icons, etc.) relative to themselves, unless these resources can be part of the module tree. So now we are adding features to the web platform that break any reasonable assumption about what HTML, CSS, and JS are, like JS imports for CSS and HTML, which could have been a simple fetch() if web platform features could be relied on. And because using dependencies is nontrivial, we are adding features to the standard library that could have been userland or even browser-provided dependencies. To reiterate, the problem isnt that bundlers exist its that they are the only viable way to get first-class dependency management on the web. JS developers deserve better. The web platform deserves better. Where do we go from here? As a web standards person, my first thought when spotting such a lacking is how can the web platform improve?. And after four years in the TAG , I cannot shake the holistic architectural perspective of which part of the Web stack is best suited for this? Specifiers vs URLs Before we can fix this, we need to understand why it is the way it is. What is the fundamental reason the JS ecosystem overwhelmingly prefers specifiers over URLs? On the surface, people often quote syntax, but that seems to be a red herring. There is little DX advantage of foo (a specifier) over ./foo.js (a URL), or even ./foo (which can be configured to have a JS MIME type). Another oft-cited reason is immutability: Remote URLs can change, whereas specifiers cannot. This also appears to be a red herring: local URLs can be just as immutable as specifiers. Digging deeper, it seems that the more fundamental reason has to do with purview . A URL is largely the same everywhere, whereas foo can resolve to different things depending on context. A specifier is app-controlled whereas a URL is not. There needs to be a standard location for a dependency to be located and referenced from, and that needs to be app-controlled. Additionally, specifiers are universal . Once a package is installed, it can be imported from anywhere, without having to work out paths. The closest HTTP URLs can get to this is root-relative URLs, and thats still not quite the same. Specifiers are clearly the path of least resistance here, so the low hanging fruit would be to make it easier to map specifiers to URLs, starting by improving import maps. Improving import maps An area with huge room for improvement here is import maps . Both making it easier to generate and include import maps, and making the import maps themselves smaller, leaner, and easier to maintain. External import maps The biggest need here is external import maps , even if its only via . This would eliminate the dependency on HTML templating and opens the way for generating them with a simple build tool. This was actually part of the original import map work , and was removed from the spec due to lack of implementer interest, despite overwhelming demand. In 2022, external import maps were prototyped in WebKit (Safari), which prompted a new WHATWG issue . Unfortunately, it appears that progress has since stalled once more. Import maps without HTML? External import maps do alleviate some of the core pain points, but are still globally managed in HTML, which hinders composability and requires heavier tooling. What if import maps could be imported into JS code? If JS could import import maps, (e.g. via import &quot;map.json&quot; with { type: &quot;importmap&quot; } ), this would eliminate the dependency on HTML altogether, allowing for scripts to localize their own import info, and for the graph to be progressively composed instead of globally managed. Edit: Turns out that injecting import maps via DOM methods actually works in all browsers (as long as certain conditions are met)! This alleviates the need for external import maps as a regular JS file can just inject them. Import maps via HTTP header? Going further, import maps via an HTTP header (e.g. Link ) would even allow webhosts to generate them for you and send them down the wire completely transparently. This could be the final missing piece for making dependencies truly first-class. Imagine a future where you just install packages and use specifiers without setting anything up, without compiling any files into other files, with the server transparently handling the mapping ! Deploying dependencies to URLs However, import maps need URLs to map specifiers to, so we also need some way to deploy the relevant subset of node_modules to public-facing URLs, as deploying the entire node_modules directory is not a viable option. clientDependencies in package.json ? One solution might be a way to explicitly mark dependencies as client side , possibly even specific exports. This would decouple detection from processing app files: in complex apps it can be managed via tooling, and in simple apps it could even be authored manually, since it would only include top-level dependencies. Figuring out the dependency graph Even if we had better ways to mark which dependencies are client-side and map specifiers to URLs, these are still pieces of the puzzle, not the entire puzzle. Without a way to figure out what depends on what, transitive dependencies will still need to be managed globally at the top level, defeating any hope of a tooling-light workflow. The current system relies on reading and parsing thousands of package.json files to build the dependency graph. This is reasonable for a JS runtime where the cost of file reads is negligible, but not for a browser where HTTP roundtrips are costly. And even if it were, this does not account for any tree-shaking. Defining specifiers as a type of URL? Think of how this works when using URLs: modules simply link to other URLs and the graph is progressively composed through these requests. What if specifiers could work the same way? What if we could look up and route specifiers when they are actually imported? Heres a radical idea: What if specifiers were just another type of URL , and specifier resolution could be handled by the server in the same way a URL is resolved when it is requested? They could use a specifier: protocol, that can be omitted in certain contexts, such as ESM imports. How would these URLs be different than regular local URLs? Their protocol would be implied in certain contexts that would be how we can import bare specifiers in ESM Their resolution would be customizable (e.g. through import maps, or even regular URL rewrites) Despite looking like absolute URLs, their resolution would depend on the requests Origin header (thus allowing different modules to use different versions of the same dependency). A request to a specifier: URL without an Origin header would fail. HTTP caching would work differently; basically in a way that emulates the current behavior of the JS module cache. Architecturally, this has several advantages: It bridges the gap between specifiers and URLs . Rather than having two entirely separate primitives for linking to a resource, it makes specifiers a high-level primitive and URLs the low-level primitive that explains it. It allows retrofitting specifiers into parts of the platform that were not designed for them, such as CSS @import . This is not theoretical: I was at a session at TPAC where bringing specifiers to CSS was discussed. With this, every part of the platform that takes URLs can now utilize specifiers, it would just need to specify the protocol explicitly. Obviously, this is just a loose strawman at this point, and would need a lot of work to turn into an actual proposal (which Id be happy to help out with, with funding ), but I suspect we need some way to bridge the gap between these two fundamentally different ways to import modules. Too radical? Quite likely. But abstraction is foundational, and you often need radical solutions to fix foundational problems. Even if this is not the right path, I doubt incremental improvements can get us out of this mess for good. But in the end, this is about the problem . Im much more confident that the problem needs solving, than I am of any particular solution. Hopefully, after reading this, so are you. So this is a call to action for the community. To browser vendors, to standards groups, to individual developers. Lets fix this! Thanks to Jordan Harband , Wes Todd , and Anne van Kesteren for reviewing earlier versions of this draft. In fact, when I was in the TAG, Sangwhan Moon and I drafted a Finding on the topic, but the TAG never reached consensus on it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>In the economy of user effort, be a bargain, not a scam</title>
  <link>https://lea.verou.me/blog/2025/user-effort/</link>
  <pubDate>Sun, 17 May 2026 03:45:25 +0200</pubDate>
  <description>Alan Kay [source] One of my favorite product design principles is Alan Kay’s “Simple things should be simple, complex things should be possible” . [1] I had been saying it almost verbatim long before I encountered Kay’s quote. Kay’s maxim is deceptively simple, but its implications run deep. It isn’t just a design ideal — it’s a call to continually balance friction, scope, and tradeoffs in service of the people using our products. This philosophy played a big part in Prism’s success back in 2012, helping it become the web’s de facto syntax highlighter for years, with over 2 billion npm downloads. Simple things were easy : All it took to highlight code on a webpage was including two files, a JS file and a CSS file. No markup changes. No JS glue code. Styling used readable CSS class names. Even adding new languages — the most common “complex” use case — required far less knowledge and effort than alternatives. At the same time, highly complex things were possible : Prism exposed a deep extensibility model so plugin authors could patch internals and dramatically alter behavior. These choices were not free. The friendly styling API increased clash risk, and deep extensibility reduced encapsulation. These were conscious, hard, tradeoffs. What things are simple and what things are complex? Simple refers to use cases that are simple from the user’s perspective , i.e. the most common use cases. They may be hard to implement, and interface simplicity is often inversely correlated with implementation simplicity. And which things are complex , depends on product scope . Instagram’s complex cases are vastly different than Photoshop’s complex cases, but as long as there is a range, Kay’s principle still applies. Since Alan Kay was a computer scientist, his quote is typically framed as a PL or API design principle, but that sells it short. It applies to a much, much broader class of interfaces. This distinction hinges on the distribution of use cases . Products often cut scope by identifying the ~20% of use cases that drive ~80% of usage — aka the Pareto Principle . Some products, however, have such diverse use cases that Pareto doesn’t meaningfully apply to the product as a whole. There are common use cases and niche use cases, but no clean 20-80 split. The tail of niche use cases is so long, it becomes significant in aggregate . For lack of a better term, I’ll call these long‑tail UIs . Nearly all creative tools are long-tail UIs. That’s why it works so well for programming languages and APIs — both are types of creative interfaces. But so are graphics editors, word processors, spreadsheets, and countless other interfaces that help humans create artifacts — even some you would never describe as creative. Yes, programming languages and APIs are user interfaces . If this surprises you, watch my DotJS 2024 talk titled “API Design is UI Design” . It’s only 20 minutes, but covers a lot of ground, including some of the ideas in this post. To underscore this point, I try to include both code and GUI examples in these posts. If code isn’t your thing, skip them and the post will still make sense. Example: Google Calendar You wouldn’t describe Google Calendar as a creative tool, but it is a tool that helps humans create artifacts (calendar events). It is also a long-tail product: there is a set of common, conceptually simple cases (one-off events at a specific time and date), and a long tail of complex use cases (recurring events, guests, multiple calendars, timezones, etc.). Indeed, Kay’s maxim has clearly been used in its design. The simple case has been so optimized that you can literally add a one hour calendar event with a single click (using a placeholder title). A different duration can be set after that first click through dragging [2] . But almost every edge case is also catered to — with additional user effort. Google Calendar is squarely a long-tail UI. The Pareto Principle is still useful for individual features , as they tend to be more narrowly defined. E.g. there is a set of spreadsheet formulas (actually much smaller than 20%) that drives &gt;80% of formula usage. While creative tools are the poster child of long-tail UIs, there are long-tail components in many transactional interfaces such as e-commerce or meal delivery (e.g. result filtering &amp; sorting, product personalization interfaces, etc.). Filtering UIs are another big category of long-tail UIs, and they involve so many tradeoffs and tough design decisions you could literally write a book about just them. Airbnb’s filtering UI here is definitely making an effort to make simple things easy with (personalized! 😍) shortcuts and complex things possible via more granular controls. It’s all about the curve Picture a plane with two axes: the horizontal axis being the complexity of the desired task (from the user’s perspective), and the vertical axis the cognitive and/or physical effort users need to expend to accomplish their task using a given interface. Following Kay’s maxim guarantees these two points: Simple things being easy guarantees a point on the lower left (low use case complexity → low user effort). Complex things being possible guarantees a point somewhere on the far right. The lower down, the better — but higher up is acceptable . Alan Kay&#39;s maxim visualized. But even if we get these two points — what about all the points in between? There are infinite different ways to connect them, and they produce vastly different overall user experiences. How does your interface fare when their use case gets slightly more complex? Are users yeeted into the deep end of interface complexity (bad), or do they only need to invest a proportional, incremental amount of additional effort to achieve their goal (good)? Meet the complexity-to-effort curve , the most important usability metric you’ve never heard of. .fallback-image { display: none; } @supports not (d: initial) { .fallback-image { display: block; &amp; ~ object { display: none; } } For delightful user experiences, making simple things easy and complex things possible is not enough — the transition between the two should also be smooth. You see, simple use cases are the spherical cows in space of product design . They work great for prototypes to convince stakeholders, or in marketing demos, but the real world is messy . Most artifacts that users need to create to achieve their real-life goals rarely fit into your “simple” flows completely, no matter how well you’ve done your homework. They are mostly simple — with a liiiiitle wart here and there. For a long-tail interface to serve user needs well in practice , we need to consciously design the curve, not just its endpoints . User effort as a currency A model with surprising predictive power is to treat user effort as a currency that users are spending to buy solutions to their problems. Nobody likes paying it; in an ideal world software would read our mind and execute perfectly with zero user effort. Since we don’t live in such a world, users understand to pay a bit of effort to achieve their goals, and are generally willing to pay more when they feel their use case warrants it. Just like regular pricing, actual user experience often depends more on the relationship between cost and budget than on the absolute cost itself. If you pay more than you expected, you feel ripped off. You may still pay it because you need the product in the moment, but you’ll be looking for a better deal in the future. And if you pay less than you had budgeted, you feel like you got a bargain, with all the delight and loyalty that entails. Avoid usability cliffs Incremental user effort cost should be proportional to incremental value gained. Suppose you’re ordering pizza. You want a simple cheese pizza with ham and mushrooms. You use the online ordering system, and you notice that adding ham to your pizza triples its price. We’re not talking some kind of fancy ham where the pigs were fed on caviar and bathed in champagne, just a regular run-of-the-mill pizza topping. You may still order it if you’re really craving ham on your pizza and no other options are available, but how does it make you feel? It’s not that different when the currency is user effort. The all too familiar “ But I just wanted to _________, why is it so hard? ”. When a small increase in use case complexity results in a disproportionately large increase in user effort cost, we have a usability cliff . Usability cliffs make users feel resentful, just like the customers of our fictitious pizza shop. A usability cliff is when a small increase in use case complexity requires a large increase in user effort. Usability cliffs are very common in products that make simple things easy and complex things possible through entirely separate flows with no integration between them: a super high level one that caters to the most common use case with little or no flexibility, and a very low-level one that is an escape hatch: it lets users do whatever, but they have to recreate the solution to the simple use case from scratch before they can tweak it. Example: The HTML video element Simple things are certainly easy: all we need to get a video with a nice sleek set of controls that work well on every device is a single attribute: controls . We just slap it on our element and we’re done with a single line of HTML: figure.multiple &gt; pre { box-sizing: border-box !important; min-width: max(min(10rem, 100%), 65%) !important; overflow: scroll; } ➡️ Now suppose use case complexity increases just a little . Maybe I want to add buttons to jump 10 seconds back or forwards. Or a language picker for subtitles. Or just to hide the volume control on a video that has no audio track. None of these are particularly niche, but the default controls are all-or-nothing: the only way to change them is to reimplement the whole toolbar from scratch, which takes hundreds of lines of code to do well. Simple things are easy and complex things are possible. But once use case complexity crosses a certain (low) threshold, user effort abruptly shoots up. That’s a usability cliff. Example: Instagram editor For Instagram’s photo editor, the simple use case is canned filters, whereas the complex ones are those requiring tweaking through individual low-level controls. However, they are implemented as separate flows: you can tweak the filter’s intensity , but you can’t see or adjust the primitives it’s built from. You can layer both types of edits on the same image, but they are additive, which doesn’t work well. Ideally, the two panels would be integrated, so that selecting a filter would adjust the low-level controls accordingly, which would both facilitate incremental tweaking and serve as a teaching aid for how filters work. Example: Filtering in Coda My favorite end-user facing product that gets this right is Coda , a cross between a document editor, a spreadsheet, and a database. All over its UI, it supports entering formulas instead of raw values, which makes complex things possible . To make simple things easy , it also provides the GUI you’d expect even without a formula language. But here’s the twist: these presets generate formulas behind the scenes that users can tweak ! Whenever users need to go a little beyond what the UI provides, they can switch to the formula editor and adjust what was generated — far easier than writing it from scratch. Another nice touch: “And” is not just communicating how multiple filters are combined, but is also a control that lets users edit the logic. Defining high-level abstractions in terms of low-level primitives is a great way to achieve a smooth complexity-to-effort curve, as it allows you to expose tweaking at various intermediate levels and scopes. The downside is that it can sometimes constrain the types of high-level solutions that can be implemented. Whether the tradeoff is worth it depends on the product and use cases. Maximize signal-to-noise ratio If you like eating out, this may be a familiar scenario: — I would like the rib-eye please, medium-rare. — Thank you sir/ma’am. How would you like your steak cooked? Keep user effort close to the minimum necessary to declare intent Annoying, right? And yet, this is how many user interfaces work; expecting users to communicate the same intent multiple times in slightly different ways. If incremental value should require incremental user effort , an obvious corollary is that things that produce no value should not require user effort . Using the currency model makes this obvious: who likes paying without getting anything in return? Respect user effort. Treat it as a scarce resource — just like regular currency — and keep it close to the minimum necessary to declare intent . Do not require users to do work that confers them no benefit, and could have been handled by the UI. If it can be derived from other input, it should be derived from other input. Source: NNGroup (adapted). A once ubiquitous example that is thankfully going away, is the credit card form which asks for the type of credit card in a separate dropdown. Credit card numbers are designed so that the type of credit card can be determined from the first four digits. There is zero reason to ask for it separately. Beyond wasting user effort, duplicating input that can be derived introduces an unnecessary error condition that you now need to handle: what happens when the entered type is not consistent with the entered number? If it can be derived from other input, it should be. User actions that meaningfully communicate intent to the interface are signal . Any other step users need to take to accomplish their goal, is noise . This includes communicating the same input more than once, providing input separately that could be derived from other input with complete or high certainty, transforming input from their mental model to the interface’s mental model, and any other demand for user effort that does not serve to communicate new information about the user’s goal. Some noise is unavoidable. The only way to have 100% signal-to-noise ratio would be if the interface could mind read. But too much noise increases friction and obfuscates signal. Example: Programmatic Element removal The two web platform methods to programmatically remove an element from the page provide a short yet demonstrative example of this for APIs. To signal intent in this case, the user needs to communicate two things: (a) what they want to do (remove an element), and (b) which element to remove. Anything beyond that is noise. The modern element.remove() DOM method has an extremely high signal-to-noise ratio. It’s hard to imagine a more concise way to signal intent. It replaced the older parent.removeChild(child) method, which had much worse ergonomics. The older method was framed around removing a child , so it required two parameters: the element to remove, and its parent. But the parent is not a separate source of truth — it would always be the child node’s parent! As a result, its actual usage involved boilerplate , where developers had to write a much noisier if (element.parentNode) element.parentNode.removeChild(element) [3] . The difference in signal-to-noise ratio is staggering: 81% vs 20% in this case. Of course, it was usually encapsulated in utility functions, which provided a similar signal-to-noise ratio as the modern method. However, user-defined abstractions don’t come for free, there is an effort (and learnability) tax there, too. Boilerplate is repetitive code that users need to include without thought, because it does not actually communicate intent. It’s the software version of red tape : hoops you need to jump through to accomplish your goal, that serve no obvious purpose in furthering said goal except for the fact that they are required of you. In case of parent.removeChild() above, the amount of boilerplate may seem small, but when viewed as a percentage of the total amount of code, the difference is staggering. The exact ratio (81% vs 20% here) varies based on the specifics of the API (variable names, method wording, etc.), but when the difference is meaningful, it transcends these types of low-level details. Think of it like big-O notation for API design. Improving signal-to-noise ratio is also why the front-end web industry gravitated towards component architectures over copy-pasta snippets: components increase signal-to-noise ratio by encapsulating boilerplate and exposing a much higher signal UI. They are the utility functions of user interfaces. As an exercise for the reader, try to calculate the signal-to-noise ratio of a Bootstrap accordion (or any other complex component in any UI library that expects you to copy-paste snippets). Instead of syntax, visual interfaces have micro-interactions. There are various models to quantify the user effort cost of micro-interactions, such as KLM . You cannot uncover friction by asking users Users are much more vocal about things not being possible, than things being hard. When pointing out friction issues in design reviews , I have sometimes heard “ users have not complained about this ”. This reveals a fundamental misunderstanding about the psychology of user feedback . Users are much more vocal about things not being possible, than about things being hard. The reason becomes clear if we look at the neuroscience of each. Friction is transient in working memory; after completing the task, details fade from the user’s prefrontal cortex. However, the negative emotions persist in the limbic system and build up over time. Filing a complaint requires prefrontal engagement, which for friction is brief or absent. Users often can’t even articulate why the software feels unpleasant: the specifics vanish; the feeling remains. Hard limitations, on the other hand, persist as conscious appraisals. The trigger doesn’t go away, since there is no workaround, so it’s far more likely to surface in explicit user feedback. Both types of pain points cause negative emotions, but friction is primarily processed by the limbic system (emotion), whereas hard limitations remain in the prefrontal cortex (reasoning). This also means that when users finally do reach the breaking point and complain about friction, you better listen. Friction is primarily processed by the limbic system, whereas hard limitations remain in the prefrontal cortex Second, user complaints are filed when there is a mismatch in expectations . Things are not possible but the user feels they should be, or interactions cost more user effort than the user had budgeted, e.g. because they know that a competing product offers the same feature for less (work). Often, users have been conditioned to expect poor user experiences, either because all options in the category are high friction, or because the user is too novice to know better [4] . So they begrudgingly pay the price, and don’t think they have the right to complain, because it’s just how things are. You might ask, “If all competitors are equally high-friction, how does this hurt us?” An unmet need is a standing invitation to disruption that a competitor can exploit at any time. Because you’re not only competing within a category; you’re competing with all alternatives — including nonconsumption (see Jobs‑to‑be‑Done ). Even for retention, users can defect to a different category altogether (e.g., building native apps instead of web apps). Historical examples abound. When it comes to actual currency, a familiar example is Airbnb : Until it came along, nobody would complain that a hotel of average price is expensive — it was just the price of hotels. If you couldn’t afford it, you just couldn’t afford to travel, period. But once Airbnb showed there is a cheaper alternative for hotel prices as a whole , tons of people jumped ship. It’s no different when the currency is user effort. Stripe took the payment API market by storm when it demonstrated that payment APIs did not have to be so high friction. iPhone disrupted the smartphone market when it demonstrated that no, you did not have to be highly technical to use a smartphone. The list goes on. Unfortunately, friction is hard to instrument. With good telemetry you can detect specific issues (e.g., dead clicks), but there is no KPI to measure friction as a whole. And no, NPS isn’t it — and you’re probably using it wrong anyway . Instead, the emotional residue from friction quietly drags many metrics down (churn, conversion, task completion), sending teams in circles like blind men touching an elephant . That’s why dashboards must be paired with product vision and proactive, first‑principles product leadership . Steve Jobs exemplified this posture: proactively, aggressively eliminating friction presented as “inevitable.” He challenged unnecessary choices, delays, and jargon, without waiting for KPIs to grant permission. Do mice really need multiple buttons? Does installing software really need multiple steps? Do smartphones really need a stylus? Of course, this worked because he had the authority to protect the vision; most orgs need explicit trust to avoid diluting it. So, if there is no metric for friction, how do you identify it? Usability testing lets you actually observe firsthand what things are hard instead of having them filtered through users’ memories and expectations. Design reviews/audits by usability experts is complementary to usability testing, as it often uncovers different issues. Design reviews are also great for maximizing the effectiveness of usability testing by getting the low-hanging fruit issues out of the way before it. Dogfooding is unparalleled as a discovery tool — nothing else will identify as many issues as using the product yourself, for your own, real needs. However, it’s important to keep in mind that you’re a huge power user of your own product. You cannot surface learnability issues ( curse of knowledge ) and you will surface issues no-one else has. Dogfooding is a fantastic discovery tool, but you still need user research to actually evaluate and prioritize the issues it surfaces. User needs come first Reducing friction rarely comes for free, just because someone had a good idea. These cases do exist, and they are great, but it usually takes sacrifices. And without it being an organizational priority, it’s very hard to steer these tradeoffs in that direction. The most common tradeoff is implementation complexity. Simplifying user experience is usually a process of driving complexity inwards and encapsulating it in the implementation. Explicit, low-level interfaces are far easier to implement, which is why there are so many of them. Especially as deadlines loom, engineers will often push towards externalizing complexity into the user interface, so that they can ship faster. And if Product leans more data-driven than data-informed, it’s easy to look at customer feedback and conclude that what users need is more features ( it’s not ) . Simple to use is often at odds with simple to implement. The first faucet is a thin abstraction : it exposes the underlying implementation directly, passing the complexity on to users, who now need to do their own translation of temperature and pressure into amounts of hot and cold water. It prioritizes implementation simplicity at the expense of wasting user effort. The second design prioritizes user needs and abstracts the underlying implementation to support the user’s mental model. It provides controls to adjust the water temperature and pressure independently, and internally translates them to the amounts of hot and cold water. This interface sacrifices some implementation simplicity to minimize user effort. This is why I’m skeptical of blanket calls for “simplicity.”: they are platitudes. Everyone agrees that, all else equal, simpler is better. It’s the tradeoffs between different types of simplicity that are tough. In some cases, reducing friction even carries tangible financial risks, which makes leadership buy-in crucial. This kind of tradeoff cannot be made by individual designers — only when eliminating friction is an organizational priority. The Oslo airport train ticket machine is the epitome of a high signal-to-noise interface. You simply swipe your credit card to enter and you swipe your card again as you leave the station at your destination. That’s it. No choices to make. No buttons to press. No ticket. You just swipe your card and you get on the train. Today this may not seem radical, but back in 2003, it was groundbreaking . To be able to provide such a frictionless user experience, they had to make a financial tradeoff: it does not ask for a PIN code, which means the company would need to absorb the financial losses from fraudulent charges (stolen credit cards, etc.). When user needs are prioritized at the top, it helps to cement that priority as an organizational design principle to point to when these tradeoffs come along in the day-to-day. Having a design principle in place will not instantly resolve all conflict, but it helps turn conflict about priorities into conflict about whether an exception is warranted, or whether the principle is applied correctly, both of which are generally easier to resolve. Of course, for that to work everyone needs to be on board with the principle. But here’s the thing with design principles (and most principles in general): they often seem obvious in the abstract, so it’s easy to get alignment in the abstract. It’s when the abstract becomes concrete that it gets tough. The Web Platform has its own version of this principle, which is called Priority of Constituencies : “User needs come before the needs of web page authors, which come before the needs of user agent implementors, which come before the needs of specification writers, which come before theoretical purity.” This highlights another key distinction: the hierarchy of user needs is more nuanced than just users over developers. Consumers over Producers While users over developers is a good starting point, it is not sufficient to fully describe the hierarchy of user needs for many products. A more flexible framing is consumers over producers ; developers are just one type of producer. Consumers are typically more numerous than producers, so this minimizes collective pain . Producers are typically more advanced , and can handle more complexity than consumers. I’ve heard this principle worded as “ Put the pain on those who can bear it ”, which emphasizes this aspect. Producers are typically more invested , and less likely to leave The web platform has multiple tiers of producers: Specification writers are at the bottom of the hierarchy, and thus, can handle the most pain ( ow! 🥴) Browser developers (“ user agent implementors ” in the principle) are consumers when it comes to specifications, but producers when it comes to the web platform Web developers are consumers when it comes to the web platform, but producers when it comes to their own websites Even within the same tier there are often producer vs consumer dynamics. E.g. when it comes to web development libraries, the web developers who write them are producers and the web developers who use them are consumers. This distinction also comes up in extensible software, where plugin authors are still consumers when it comes to the software itself, but producers when it comes to their own plugins. It also comes up in dual sided marketplace products (e.g. Airbnb, Uber, etc.), where buyer needs are generally higher priority than seller needs. Life by a thousand ✨ sprinkles of delight ✨ In the economy of user effort, the antithesis of overpriced interfaces that make users feel ripped off are those where every bit of user effort required feels meaningful and produces tangible value to them. The interface is on the user’s side, gently helping them along with every step, instead of treating their time and energy as disposable. The user feels like they’re getting a bargain : they get to spend less than they had budgeted for! And we all know how motivating a good bargain is. User effort bargains don’t have to be radical innovations; don’t underestimate the power of small touches. A zip code input that auto-fills city and state, a web component that automatically adapts to its context without additional configuration, a pasted link that automatically defaults to the website title (or the selected text, if any), a freeform date that is correctly parsed into structured data, a login UI that remembers whether you have an account and which service you’ve used to log in before, an authentication flow that takes you back to the page you were on before. Sometimes many small things can collectively make a big difference. In some ways, it’s the polar opposite of death by a thousand paper cuts : Life by a thousand sprinkles of delight! 😀 Conclusion In the end, “ simple things simple, complex things possible ” is table stakes. The key differentiator is the shape of the curve between those points. Products win when user effort scales smoothly with use case complexity, cliffs are engineered out, and every interaction declares a meaningful piece of user intent . That doesn’t just happen by itself. It involves hard tradeoffs, saying no a lot, and prioritizing user needs at the organizational level . Treating user effort like real money, forces you to design with restraint. A rule of thumb is place the pain where it’s best absorbed by prioritizing consumers over producers . Do this consistently, and the interface feels delightful in a way that sticks. Delight turns into trust. Trust into loyalty. Loyalty into product-market fit. Kay himself replied on Quora and provided background on this quote . Don’t you just love the internet? ↩︎ Yes, typing can be faster than dragging, but minimizing homing between input devices improves efficiency more, see KLM ↩︎ Yes, today it would have been element.parentNode?.removeChild(element) , which is a little less noisy, but this was before the optional chaining operator. ↩︎ When I was running user studies at MIT, I’ve often had users exclaim “I can’t believe it! I tried to do the obvious simple thing and it actually worked!” ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>In the economy of user effort, be a bargain, not a scam</title>
  <link>https://lea.verou.me/blog/2025/user-effort/</link>
  <pubDate>Sun, 17 May 2026 03:45:25 +0200</pubDate>
  <description>Alan Kay [source] One of my favorite product design principles is Alan Kays Simple things should be simple, complex things should be possible . [1] I had been saying it almost verbatim long before I encountered Kays quote. Kays maxim is deceptively simple, but its implications run deep. It isnt just a design ideal its a call to continually balance friction, scope, and tradeoffs in service of the people using our products. This philosophy played a big part in Prisms success back in 2012, helping it become the webs de facto syntax highlighter for years, with over 2 billion npm downloads. Simple things were easy : All it took to highlight code on a webpage was including two files, a JS file and a CSS file. No markup changes. No JS glue code. Styling used readable CSS class names. Even adding new languages the most common complex use case required far less knowledge and effort than alternatives. At the same time, highly complex things were possible : Prism exposed a deep extensibility model so plugin authors could patch internals and dramatically alter behavior. These choices were not free. The friendly styling API increased clash risk, and deep extensibility reduced encapsulation. These were conscious, hard, tradeoffs. What things are simple and what things are complex? Simple refers to use cases that are simple from the users perspective , i.e. the most common use cases. They may be hard to implement, and interface simplicity is often inversely correlated with implementation simplicity. And which things are complex , depends on product scope . Instagrams complex cases are vastly different than Photoshops complex cases, but as long as there is a range, Kays principle still applies. Since Alan Kay was a computer scientist, his quote is typically framed as a PL or API design principle, but that sells it short. It applies to a much, much broader class of interfaces. This distinction hinges on the distribution of use cases . Products often cut scope by identifying the ~20% of use cases that drive ~80% of usage aka the Pareto Principle . Some products, however, have such diverse use cases that Pareto doesnt meaningfully apply to the product as a whole. There are common use cases and niche use cases, but no clean 20-80 split. The tail of niche use cases is so long, it becomes significant in aggregate . For lack of a better term, Ill call these longtail UIs . Nearly all creative tools are long-tail UIs. Thats why it works so well for programming languages and APIs both are types of creative interfaces. But so are graphics editors, word processors, spreadsheets, and countless other interfaces that help humans create artifacts even some you would never describe as creative. Yes, programming languages and APIs are user interfaces . If this surprises you, watch my DotJS 2024 talk titled API Design is UI Design . Its only 20 minutes, but covers a lot of ground, including some of the ideas in this post. To underscore this point, I try to include both code and GUI examples in these posts. If code isnt your thing, skip them and the post will still make sense. Example: Google Calendar You wouldnt describe Google Calendar as a creative tool, but it is a tool that helps humans create artifacts (calendar events). It is also a long-tail product: there is a set of common, conceptually simple cases (one-off events at a specific time and date), and a long tail of complex use cases (recurring events, guests, multiple calendars, timezones, etc.). Indeed, Kays maxim has clearly been used in its design. The simple case has been so optimized that you can literally add a one hour calendar event with a single click (using a placeholder title). A different duration can be set after that first click through dragging [2] . But almost every edge case is also catered to with additional user effort. Google Calendar is squarely a long-tail UI. The Pareto Principle is still useful for individual features , as they tend to be more narrowly defined. E.g. there is a set of spreadsheet formulas (actually much smaller than 20%) that drives &gt;80% of formula usage. While creative tools are the poster child of long-tail UIs, there are long-tail components in many transactional interfaces such as e-commerce or meal delivery (e.g. result filtering &amp; sorting, product personalization interfaces, etc.). Filtering UIs are another big category of long-tail UIs, and they involve so many tradeoffs and tough design decisions you could literally write a book about just them. Airbnbs filtering UI here is definitely making an effort to make simple things easy with (personalized! ) shortcuts and complex things possible via more granular controls. Its all about the curve Picture a plane with two axes: the horizontal axis being the complexity of the desired task (from the users perspective), and the vertical axis the cognitive and/or physical effort users need to expend to accomplish their task using a given interface. Following Kays maxim guarantees these two points: Simple things being easy guarantees a point on the lower left (low use case complexity low user effort). Complex things being possible guarantees a point somewhere on the far right. The lower down, the better but higher up is acceptable . Alan Kay&#39;s maxim visualized. But even if we get these two points what about all the points in between? There are infinite different ways to connect them, and they produce vastly different overall user experiences. How does your interface fare when their use case gets slightly more complex? Are users yeeted into the deep end of interface complexity (bad), or do they only need to invest a proportional, incremental amount of additional effort to achieve their goal (good)? Meet the complexity-to-effort curve , the most important usability metric youve never heard of. .fallback-image { display: none; } @supports not (d: initial) { .fallback-image { display: block; &amp; ~ object { display: none; } } For delightful user experiences, making simple things easy and complex things possible is not enough the transition between the two should also be smooth. You see, simple use cases are the spherical cows in space of product design . They work great for prototypes to convince stakeholders, or in marketing demos, but the real world is messy . Most artifacts that users need to create to achieve their real-life goals rarely fit into your simple flows completely, no matter how well youve done your homework. They are mostly simple with a liiiiitle wart here and there. For a long-tail interface to serve user needs well in practice , we need to consciously design the curve, not just its endpoints . User effort as a currency A model with surprising predictive power is to treat user effort as a currency that users are spending to buy solutions to their problems. Nobody likes paying it; in an ideal world software would read our mind and execute perfectly with zero user effort. Since we dont live in such a world, users understand to pay a bit of effort to achieve their goals, and are generally willing to pay more when they feel their use case warrants it. Just like regular pricing, actual user experience often depends more on the relationship between cost and budget than on the absolute cost itself. If you pay more than you expected, you feel ripped off. You may still pay it because you need the product in the moment, but youll be looking for a better deal in the future. And if you pay less than you had budgeted, you feel like you got a bargain, with all the delight and loyalty that entails. Avoid usability cliffs Incremental user effort cost should be proportional to incremental value gained. Suppose youre ordering pizza. You want a simple cheese pizza with ham and mushrooms. You use the online ordering system, and you notice that adding ham to your pizza triples its price. Were not talking some kind of fancy ham where the pigs were fed on caviar and bathed in champagne, just a regular run-of-the-mill pizza topping. You may still order it if youre really craving ham on your pizza and no other options are available, but how does it make you feel? Its not that different when the currency is user effort. The all too familiar But I just wanted to _________, why is it so hard? . When a small increase in use case complexity results in a disproportionately large increase in user effort cost, we have a usability cliff . Usability cliffs make users feel resentful, just like the customers of our fictitious pizza shop. A usability cliff is when a small increase in use case complexity requires a large increase in user effort. Usability cliffs are very common in products that make simple things easy and complex things possible through entirely separate flows with no integration between them: a super high level one that caters to the most common use case with little or no flexibility, and a very low-level one that is an escape hatch: it lets users do whatever, but they have to recreate the solution to the simple use case from scratch before they can tweak it. Example: The HTML video element Simple things are certainly easy: all we need to get a video with a nice sleek set of controls that work well on every device is a single attribute: controls . We just slap it on our element and were done with a single line of HTML: figure.multiple &gt; pre { box-sizing: border-box !important; min-width: max(min(10rem, 100%), 65%) !important; overflow: scroll; } Now suppose use case complexity increases just a little . Maybe I want to add buttons to jump 10 seconds back or forwards. Or a language picker for subtitles. Or just to hide the volume control on a video that has no audio track. None of these are particularly niche, but the default controls are all-or-nothing: the only way to change them is to reimplement the whole toolbar from scratch, which takes hundreds of lines of code to do well. Simple things are easy and complex things are possible. But once use case complexity crosses a certain (low) threshold, user effort abruptly shoots up. Thats a usability cliff. Example: Instagram editor For Instagrams photo editor, the simple use case is canned filters, whereas the complex ones are those requiring tweaking through individual low-level controls. However, they are implemented as separate flows: you can tweak the filters intensity , but you cant see or adjust the primitives its built from. You can layer both types of edits on the same image, but they are additive, which doesnt work well. Ideally, the two panels would be integrated, so that selecting a filter would adjust the low-level controls accordingly, which would both facilitate incremental tweaking and serve as a teaching aid for how filters work. Example: Filtering in Coda My favorite end-user facing product that gets this right is Coda , a cross between a document editor, a spreadsheet, and a database. All over its UI, it supports entering formulas instead of raw values, which makes complex things possible . To make simple things easy , it also provides the GUI youd expect even without a formula language. But heres the twist: these presets generate formulas behind the scenes that users can tweak ! Whenever users need to go a little beyond what the UI provides, they can switch to the formula editor and adjust what was generated far easier than writing it from scratch. Another nice touch: And is not just communicating how multiple filters are combined, but is also a control that lets users edit the logic. Defining high-level abstractions in terms of low-level primitives is a great way to achieve a smooth complexity-to-effort curve, as it allows you to expose tweaking at various intermediate levels and scopes. The downside is that it can sometimes constrain the types of high-level solutions that can be implemented. Whether the tradeoff is worth it depends on the product and use cases. Maximize signal-to-noise ratio If you like eating out, this may be a familiar scenario: I would like the rib-eye please, medium-rare. Thank you sir/maam. How would you like your steak cooked? Keep user effort close to the minimum necessary to declare intent Annoying, right? And yet, this is how many user interfaces work; expecting users to communicate the same intent multiple times in slightly different ways. If incremental value should require incremental user effort , an obvious corollary is that things that produce no value should not require user effort . Using the currency model makes this obvious: who likes paying without getting anything in return? Respect user effort. Treat it as a scarce resource just like regular currency and keep it close to the minimum necessary to declare intent . Do not require users to do work that confers them no benefit, and could have been handled by the UI. If it can be derived from other input, it should be derived from other input. Source: NNGroup (adapted). A once ubiquitous example that is thankfully going away, is the credit card form which asks for the type of credit card in a separate dropdown. Credit card numbers are designed so that the type of credit card can be determined from the first four digits. There is zero reason to ask for it separately. Beyond wasting user effort, duplicating input that can be derived introduces an unnecessary error condition that you now need to handle: what happens when the entered type is not consistent with the entered number? If it can be derived from other input, it should be. User actions that meaningfully communicate intent to the interface are signal . Any other step users need to take to accomplish their goal, is noise . This includes communicating the same input more than once, providing input separately that could be derived from other input with complete or high certainty, transforming input from their mental model to the interfaces mental model, and any other demand for user effort that does not serve to communicate new information about the users goal. Some noise is unavoidable. The only way to have 100% signal-to-noise ratio would be if the interface could mind read. But too much noise increases friction and obfuscates signal. Example: Programmatic Element removal The two web platform methods to programmatically remove an element from the page provide a short yet demonstrative example of this for APIs. To signal intent in this case, the user needs to communicate two things: (a) what they want to do (remove an element), and (b) which element to remove. Anything beyond that is noise. The modern element.remove() DOM method has an extremely high signal-to-noise ratio. Its hard to imagine a more concise way to signal intent. It replaced the older parent.removeChild(child) method, which had much worse ergonomics. The older method was framed around removing a child , so it required two parameters: the element to remove, and its parent. But the parent is not a separate source of truth it would always be the child nodes parent! As a result, its actual usage involved boilerplate , where developers had to write a much noisier if (element.parentNode) element.parentNode.removeChild(element) [3] . The difference in signal-to-noise ratio is staggering: 81% vs 20% in this case. Of course, it was usually encapsulated in utility functions, which provided a similar signal-to-noise ratio as the modern method. However, user-defined abstractions dont come for free, there is an effort (and learnability) tax there, too. Boilerplate is repetitive code that users need to include without thought, because it does not actually communicate intent. Its the software version of red tape : hoops you need to jump through to accomplish your goal, that serve no obvious purpose in furthering said goal except for the fact that they are required of you. In case of parent.removeChild() above, the amount of boilerplate may seem small, but when viewed as a percentage of the total amount of code, the difference is staggering. The exact ratio (81% vs 20% here) varies based on the specifics of the API (variable names, method wording, etc.), but when the difference is meaningful, it transcends these types of low-level details. Think of it like big-O notation for API design. Improving signal-to-noise ratio is also why the front-end web industry gravitated towards component architectures over copy-pasta snippets: components increase signal-to-noise ratio by encapsulating boilerplate and exposing a much higher signal UI. They are the utility functions of user interfaces. As an exercise for the reader, try to calculate the signal-to-noise ratio of a Bootstrap accordion (or any other complex component in any UI library that expects you to copy-paste snippets). Instead of syntax, visual interfaces have micro-interactions. There are various models to quantify the user effort cost of micro-interactions, such as KLM . You cannot uncover friction by asking users Users are much more vocal about things not being possible, than things being hard. When pointing out friction issues in design reviews , I have sometimes heard users have not complained about this . This reveals a fundamental misunderstanding about the psychology of user feedback . Users are much more vocal about things not being possible, than about things being hard. The reason becomes clear if we look at the neuroscience of each. Friction is transient in working memory; after completing the task, details fade from the users prefrontal cortex. However, the negative emotions persist in the limbic system and build up over time. Filing a complaint requires prefrontal engagement, which for friction is brief or absent. Users often cant even articulate why the software feels unpleasant: the specifics vanish; the feeling remains. Hard limitations, on the other hand, persist as conscious appraisals. The trigger doesnt go away, since there is no workaround, so its far more likely to surface in explicit user feedback. Both types of pain points cause negative emotions, but friction is primarily processed by the limbic system (emotion), whereas hard limitations remain in the prefrontal cortex (reasoning). This also means that when users finally do reach the breaking point and complain about friction, you better listen. Friction is primarily processed by the limbic system, whereas hard limitations remain in the prefrontal cortex Second, user complaints are filed when there is a mismatch in expectations . Things are not possible but the user feels they should be, or interactions cost more user effort than the user had budgeted, e.g. because they know that a competing product offers the same feature for less (work). Often, users have been conditioned to expect poor user experiences, either because all options in the category are high friction, or because the user is too novice to know better [4] . So they begrudgingly pay the price, and dont think they have the right to complain, because its just how things are. You might ask, If all competitors are equally high-friction, how does this hurt us? An unmet need is a standing invitation to disruption that a competitor can exploit at any time. Because youre not only competing within a category; youre competing with all alternatives including nonconsumption (see JobstobeDone ). Even for retention, users can defect to a different category altogether (e.g., building native apps instead of web apps). Historical examples abound. When it comes to actual currency, a familiar example is Airbnb : Until it came along, nobody would complain that a hotel of average price is expensive it was just the price of hotels. If you couldnt afford it, you just couldnt afford to travel, period. But once Airbnb showed there is a cheaper alternative for hotel prices as a whole , tons of people jumped ship. Its no different when the currency is user effort. Stripe took the payment API market by storm when it demonstrated that payment APIs did not have to be so high friction. iPhone disrupted the smartphone market when it demonstrated that no, you did not have to be highly technical to use a smartphone. The list goes on. Unfortunately, friction is hard to instrument. With good telemetry you can detect specific issues (e.g., dead clicks), but there is no KPI to measure friction as a whole. And no, NPS isnt it and youre probably using it wrong anyway . Instead, the emotional residue from friction quietly drags many metrics down (churn, conversion, task completion), sending teams in circles like blind men touching an elephant . Thats why dashboards must be paired with product vision and proactive, firstprinciples product leadership . Steve Jobs exemplified this posture: proactively, aggressively eliminating friction presented as inevitable. He challenged unnecessary choices, delays, and jargon, without waiting for KPIs to grant permission. Do mice really need multiple buttons? Does installing software really need multiple steps? Do smartphones really need a stylus? Of course, this worked because he had the authority to protect the vision; most orgs need explicit trust to avoid diluting it. So, if there is no metric for friction, how do you identify it? Usability testing lets you actually observe firsthand what things are hard instead of having them filtered through users memories and expectations. Design reviews/audits by usability experts is complementary to usability testing, as it often uncovers different issues. Design reviews are also great for maximizing the effectiveness of usability testing by getting the low-hanging fruit issues out of the way before it. Dogfooding is unparalleled as a discovery tool nothing else will identify as many issues as using the product yourself, for your own, real needs. However, its important to keep in mind that youre a huge power user of your own product. You cannot surface learnability issues ( curse of knowledge ) and you will surface issues no-one else has. Dogfooding is a fantastic discovery tool, but you still need user research to actually evaluate and prioritize the issues it surfaces. User needs come first Reducing friction rarely comes for free, just because someone had a good idea. These cases do exist, and they are great, but it usually takes sacrifices. And without it being an organizational priority, its very hard to steer these tradeoffs in that direction. The most common tradeoff is implementation complexity. Simplifying user experience is usually a process of driving complexity inwards and encapsulating it in the implementation. Explicit, low-level interfaces are far easier to implement, which is why there are so many of them. Especially as deadlines loom, engineers will often push towards externalizing complexity into the user interface, so that they can ship faster. And if Product leans more data-driven than data-informed, its easy to look at customer feedback and conclude that what users need is more features ( its not ) . Simple to use is often at odds with simple to implement. The first faucet is a thin abstraction : it exposes the underlying implementation directly, passing the complexity on to users, who now need to do their own translation of temperature and pressure into amounts of hot and cold water. It prioritizes implementation simplicity at the expense of wasting user effort. The second design prioritizes user needs and abstracts the underlying implementation to support the users mental model. It provides controls to adjust the water temperature and pressure independently, and internally translates them to the amounts of hot and cold water. This interface sacrifices some implementation simplicity to minimize user effort. This is why Im skeptical of blanket calls for simplicity.: they are platitudes. Everyone agrees that, all else equal, simpler is better. Its the tradeoffs between different types of simplicity that are tough. In some cases, reducing friction even carries tangible financial risks, which makes leadership buy-in crucial. This kind of tradeoff cannot be made by individual designers only when eliminating friction is an organizational priority. The Oslo airport train ticket machine is the epitome of a high signal-to-noise interface. You simply swipe your credit card to enter and you swipe your card again as you leave the station at your destination. Thats it. No choices to make. No buttons to press. No ticket. You just swipe your card and you get on the train. Today this may not seem radical, but back in 2003, it was groundbreaking . To be able to provide such a frictionless user experience, they had to make a financial tradeoff: it does not ask for a PIN code, which means the company would need to absorb the financial losses from fraudulent charges (stolen credit cards, etc.). When user needs are prioritized at the top, it helps to cement that priority as an organizational design principle to point to when these tradeoffs come along in the day-to-day. Having a design principle in place will not instantly resolve all conflict, but it helps turn conflict about priorities into conflict about whether an exception is warranted, or whether the principle is applied correctly, both of which are generally easier to resolve. Of course, for that to work everyone needs to be on board with the principle. But heres the thing with design principles (and most principles in general): they often seem obvious in the abstract, so its easy to get alignment in the abstract. Its when the abstract becomes concrete that it gets tough. The Web Platform has its own version of this principle, which is called Priority of Constituencies : User needs come before the needs of web page authors, which come before the needs of user agent implementors, which come before the needs of specification writers, which come before theoretical purity. This highlights another key distinction: the hierarchy of user needs is more nuanced than just users over developers. Consumers over Producers While users over developers is a good starting point, it is not sufficient to fully describe the hierarchy of user needs for many products. A more flexible framing is consumers over producers ; developers are just one type of producer. Consumers are typically more numerous than producers, so this minimizes collective pain . Producers are typically more advanced , and can handle more complexity than consumers. Ive heard this principle worded as Put the pain on those who can bear it , which emphasizes this aspect. Producers are typically more invested , and less likely to leave The web platform has multiple tiers of producers: Specification writers are at the bottom of the hierarchy, and thus, can handle the most pain ( ow! ) Browser developers ( user agent implementors in the principle) are consumers when it comes to specifications, but producers when it comes to the web platform Web developers are consumers when it comes to the web platform, but producers when it comes to their own websites Even within the same tier there are often producer vs consumer dynamics. E.g. when it comes to web development libraries, the web developers who write them are producers and the web developers who use them are consumers. This distinction also comes up in extensible software, where plugin authors are still consumers when it comes to the software itself, but producers when it comes to their own plugins. It also comes up in dual sided marketplace products (e.g. Airbnb, Uber, etc.), where buyer needs are generally higher priority than seller needs. Life by a thousand sprinkles of delight In the economy of user effort, the antithesis of overpriced interfaces that make users feel ripped off are those where every bit of user effort required feels meaningful and produces tangible value to them. The interface is on the users side, gently helping them along with every step, instead of treating their time and energy as disposable. The user feels like theyre getting a bargain : they get to spend less than they had budgeted for! And we all know how motivating a good bargain is. User effort bargains dont have to be radical innovations; dont underestimate the power of small touches. A zip code input that auto-fills city and state, a web component that automatically adapts to its context without additional configuration, a pasted link that automatically defaults to the website title (or the selected text, if any), a freeform date that is correctly parsed into structured data, a login UI that remembers whether you have an account and which service youve used to log in before, an authentication flow that takes you back to the page you were on before. Sometimes many small things can collectively make a big difference. In some ways, its the polar opposite of death by a thousand paper cuts : Life by a thousand sprinkles of delight! Conclusion In the end, simple things simple, complex things possible is table stakes. The key differentiator is the shape of the curve between those points. Products win when user effort scales smoothly with use case complexity, cliffs are engineered out, and every interaction declares a meaningful piece of user intent . That doesnt just happen by itself. It involves hard tradeoffs, saying no a lot, and prioritizing user needs at the organizational level . Treating user effort like real money, forces you to design with restraint. A rule of thumb is place the pain where its best absorbed by prioritizing consumers over producers . Do this consistently, and the interface feels delightful in a way that sticks. Delight turns into trust. Trust into loyalty. Loyalty into product-market fit. Kay himself replied on Quora and provided background on this quote . Dont you just love the internet? Yes, typing can be faster than dragging, but minimizing homing between input devices improves efficiency more, see KLM Yes, today it would have been element.parentNode?.removeChild(element) , which is a little less noisy, but this was before the optional chaining operator. When I was running user studies at MIT, Ive often had users exclaim I cant believe it! I tried to do the obvious simple thing and it actually worked!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>State of HTML 2025 now open!</title>
  <link>https://lea.verou.me/blog/2025/state-of-html/</link>
  <pubDate>Sun, 17 May 2026 03:45:23 +0200</pubDate>
  <description>tl;dr : State of HTML 2025 survey is now open! Take it now Mamma mia, here we go again! About two weeks ago, I announced that I was back leading this year’s State of HTML 2025 survey, after a one year hiatus. We are grateful for all the suggestions that poured in, they were immensely helpful in shaping the survey. After two weeks of hard work from a small team spanning three continents, we are finally ready to launch! I would urge each and every one of you that works with the web platform to fill out this survey . It’s a unique opportunity to have your voice heard in the browser vendors’ decision-making process. Survey results are used by browsers to prioritize roadmaps — the reason Google is funding this. The results from State of … surveys directly feed into prioritization for next year’s Interop project. Time spent thoughtfully filling them out is an investment that can come back to you tenfold in the form of seeing features you care about implemented, browser incompatibilities being prioritized, and gaps in the platform being addressed. In addition to browsers, several standards groups are also using the results for prioritization and decision-making. Additionally, you get to learn about new and upcoming features you may have missed, and get a personalized, sharable score at the end to see how you compare to other respondents! Take State of HTML 2025 Survey While the survey will be open for about a month, responses entered within the first two weeks (until end of July) will have a much higher impact on the Web, as preliminary data will be directly used to inform Interop 2026. Acknowledgements I would like to thank the following people for their help in fleshing out the survey: Sacha Greif for actually implementing the survey and tirelessly discussing my ideas My apprentice Dmitry Sharabin for various fixes and improvements My Google contacts, Philip Jägenstedt and Kadir Topal for making my involvement possible And everyone who responded to my earlier call for suggestions — these were invaluable in shaping the survey, and I wish I could include them all! FAQ What’s new this year? We spent a lot of time thinking about which features we are asking about and why. As a result, we added 35 new features, and removed 18 existing ones to make room. This is probably one of the hardest parts of the process, as we had to make some tough decisions. We are also using the Web Components section to pilot a new format for pain points questions, consisting of a multiple choice question with common pain points, followed by the usual free form text list: While this increases the number of questions, we are hoping it will reduce survey fatigue by allowing participants to skip the freeform question more frequently (or spend less time on it) if most of their pain points have already been covered by the multiple choice question. Last but not least, we introduced browser support icons for each feature, per popular request: Can I edit my responses? Absolutely! Do not worry about filling it out perfectly in one go. If you create an account, you can edit your responses for the whole period the survey is open, and even fill it out across multiple devices, e.g. start on your phone, then fill out some on your desktop, etc. Even if you’re filling it out anonymously, you can still edit responses on your device for some time, so you can have it open in a browser tab and revisit it periodically. Why are there JS questions in an HTML survey? This question comes up a lot every year. For the same reason there are JS APIs in the HTML standard : many JS APIs are intrinsically related to HTML. We mainly included JS APIs that are in some way related to HTML, such as: APIs used to manipulate HTML dynamically (DOM, interactivity, etc.) Web Components APIs, used to create custom HTML elements PWA features, including APIs used to access underlying system capabilities (OS capabilities, device capabilities, etc.) The only two exceptions to this are two Intl APIs, which were mainly included because we wanted to get participants thinking about any localization/internationalization pain points they may have. However, if you don’t write any JS, we absolutely still want to hear from you! In fact, I would encourage you even more strongly to fill out the survey, as people who don’t write JS are very underrepresented in these surveys. All questions are optional , so you can just skip any JS-related questions. There is also a question at the end, where you can select that you only write HTML/CSS: Why are some features included that are early stage proposals with no browser support? While proposals with no browser support are not good candidates for immediate prioritization by browsers, their context chips give browser vendors and standards groups invaluable insight into what matters to developers, which also drives prioritization decisions. However, we heard you loud and clear: when mature and early stage features are mixed together, you felt bait-and-switched. So this year, we are including icons to summarize browser support of each feature we ask about: We are hoping this will also help prevent cases where participants confuse a new feature they have never heard of, with a more established feature they are familiar with. Is the survey only available in English? Absolutely not! Localization has been an integral part of these surveys since the beginning. Fun fact: None of the people working on these surveys is a native English speaker. State of HTML 2024 had translations for 31 languages. However, since translations are a community effort, they are not necessarily complete, especially in the beginning. If you are a native speaker of a language that is not yet complete, please consider helping out ! I found a bug, what should I do? Please file an issue so we can fix it! File content issue File technical issue</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>State of HTML 2025 now open!</title>
  <link>https://lea.verou.me/blog/2025/state-of-html/</link>
  <pubDate>Sun, 17 May 2026 03:45:23 +0200</pubDate>
  <description>tl;dr : State of HTML 2025 survey is now open! Take it now Mamma mia, here we go again! About two weeks ago, I announced that I was back leading this years State of HTML 2025 survey, after a one year hiatus. We are grateful for all the suggestions that poured in, they were immensely helpful in shaping the survey. After two weeks of hard work from a small team spanning three continents, we are finally ready to launch! I would urge each and every one of you that works with the web platform to fill out this survey . Its a unique opportunity to have your voice heard in the browser vendors decision-making process. Survey results are used by browsers to prioritize roadmaps the reason Google is funding this. The results from State of surveys directly feed into prioritization for next years Interop project. Time spent thoughtfully filling them out is an investment that can come back to you tenfold in the form of seeing features you care about implemented, browser incompatibilities being prioritized, and gaps in the platform being addressed. In addition to browsers, several standards groups are also using the results for prioritization and decision-making. Additionally, you get to learn about new and upcoming features you may have missed, and get a personalized, sharable score at the end to see how you compare to other respondents! Take State of HTML 2025 Survey While the survey will be open for about a month, responses entered within the first two weeks (until end of July) will have a much higher impact on the Web, as preliminary data will be directly used to inform Interop 2026. Acknowledgements I would like to thank the following people for their help in fleshing out the survey: Sacha Greif for actually implementing the survey and tirelessly discussing my ideas My apprentice Dmitry Sharabin for various fixes and improvements My Google contacts, Philip Jägenstedt and Kadir Topal for making my involvement possible And everyone who responded to my earlier call for suggestions these were invaluable in shaping the survey, and I wish I could include them all! FAQ Whats new this year? We spent a lot of time thinking about which features we are asking about and why. As a result, we added 35 new features, and removed 18 existing ones to make room. This is probably one of the hardest parts of the process, as we had to make some tough decisions. We are also using the Web Components section to pilot a new format for pain points questions, consisting of a multiple choice question with common pain points, followed by the usual free form text list: While this increases the number of questions, we are hoping it will reduce survey fatigue by allowing participants to skip the freeform question more frequently (or spend less time on it) if most of their pain points have already been covered by the multiple choice question. Last but not least, we introduced browser support icons for each feature, per popular request: Can I edit my responses? Absolutely! Do not worry about filling it out perfectly in one go. If you create an account, you can edit your responses for the whole period the survey is open, and even fill it out across multiple devices, e.g. start on your phone, then fill out some on your desktop, etc. Even if youre filling it out anonymously, you can still edit responses on your device for some time, so you can have it open in a browser tab and revisit it periodically. Why are there JS questions in an HTML survey? This question comes up a lot every year. For the same reason there are JS APIs in the HTML standard : many JS APIs are intrinsically related to HTML. We mainly included JS APIs that are in some way related to HTML, such as: APIs used to manipulate HTML dynamically (DOM, interactivity, etc.) Web Components APIs, used to create custom HTML elements PWA features, including APIs used to access underlying system capabilities (OS capabilities, device capabilities, etc.) The only two exceptions to this are two Intl APIs, which were mainly included because we wanted to get participants thinking about any localization/internationalization pain points they may have. However, if you dont write any JS, we absolutely still want to hear from you! In fact, I would encourage you even more strongly to fill out the survey, as people who dont write JS are very underrepresented in these surveys. All questions are optional , so you can just skip any JS-related questions. There is also a question at the end, where you can select that you only write HTML/CSS: Why are some features included that are early stage proposals with no browser support? While proposals with no browser support are not good candidates for immediate prioritization by browsers, their context chips give browser vendors and standards groups invaluable insight into what matters to developers, which also drives prioritization decisions. However, we heard you loud and clear: when mature and early stage features are mixed together, you felt bait-and-switched. So this year, we are including icons to summarize browser support of each feature we ask about: We are hoping this will also help prevent cases where participants confuse a new feature they have never heard of, with a more established feature they are familiar with. Is the survey only available in English? Absolutely not! Localization has been an integral part of these surveys since the beginning. Fun fact: None of the people working on these surveys is a native English speaker. State of HTML 2024 had translations for 31 languages. However, since translations are a community effort, they are not necessarily complete, especially in the beginning. If you are a native speaker of a language that is not yet complete, please consider helping out ! I found a bug, what should I do? Please file an issue so we can fix it! File content issue File technical issue</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Influence the State of HTML 2025 Survey!</title>
  <link>https://lea.verou.me/blog/2025/design-state-of-html/</link>
  <pubDate>Sun, 17 May 2026 03:45:21 +0200</pubDate>
  <description>Mamma mia, here we go again! Two years ago, I was funded by Google to design the inaugural State of HTML survey . While I had led State of … surveys before (also graciously sponsored by Google), that was by far the most intense, as 0→1 projects often are. In addition to the research, content, and analysis work that goes into every State of … survey, the unique challenges it presented were a forcing function for finally tackling some longstanding UX issues with these surveys. As a result, we pioneered new survey interaction UIs , and validated them via usability testing. This work did not just affect State of HTML, but had ripple effects on all subsequent State of … surveys. The results made it all worth it. Turnout was the highest ever for a new Devographics [1] survey: 21 thousand participants , which remains a record high for State of HTML. The survey findings heavily influenced Interop 2024 (hello Popover API and Declarative Shadow DOM!) and helped prioritize several other initiatives, such as stylable selects . Despite lower 2024 participation, the survey still significantly influenced Interop 2025 ; notably, View transitions was added after being prominent in the survey for two years in a row. This is the goal of these surveys: to drive meaningful change in the web platform . Sure, getting a shareable score about what you know and seeing how you compare to the rest of the industry is fun, but the reason browser vendors pour thousands of dollars into funding these surveys is because they provide unique vendor-neutral insights into developer pain points and priorities , which helps them make better decisions about what to work on. And this ultimately helps you: by getting your voice heard, you can directly influence the tools you work with. It’s a win-win: developers get better tools, and browser vendors get better roadmaps. Fun fact One of my favorite examples of impact these surveys have driven is CSS Nesting . Browsers ignored the proposal to do Nesting natively for a decade , the rationale being that preprocessors served the need just fine. Then it came up in State of CSS 2022 as a top pain point and they scrambled to get it implemented ASAP. It was Baseline just a year later. Nothing moves things faster than demonstrating a clear need, and these surveys are a great way to do that. State of HTML 2025 Last year, I was too busy to take the lead again. Wrapping up my PhD and starting a new job immediately after, there was no time to breathe, let alone lead a survey. I’m happy to be returning to it this year, but my joy is bittersweet. When I was first asked to lead this year’s survey a few months ago, I was still too busy to take it on. Someone else from the community accepted the role — someone incredibly knowledgeable and talented who would have done a fantastic job. But they live in the Middle East, and as the war escalated, their safety and their family’s well-being were directly impacted. Understandably, leading a developer survey became the least of their concerns. In the meantime, I made a few decisions that opened up some availability, and I was able to step in at the last minute. It’s a sobering reminder that events which feel far away can hit close to home — shaping not just headlines, but the work and lives of people we know. Web Platform Features at the verge of Interop A big part of these surveys is “feature questions”: respondents are presented with a series of web platform features, and asked about their familiarity and sentiment towards them. At the end, they get a score based on how many they were familiar with that they can share with others, and browser vendors and standards groups get signal on which upcoming features to prioritize or improve. You can see which features were included in last year’s survey here or in [2] the table below. State of HTML Features Feature 2023 2024 ✅ ✅ autocomplete attribute ✅ ✅ HTML Media Capture ✅ ✅ input.showPicker() ✅ ✅ FormData API ✅ ✅ ✅ contenteditable=&quot;plaintext-only&quot; ✅ ✅ ✅ ✅ and ✅ ✅ Exclusive Accordion ✅ ✅ Popover API ✅ ✅ inert attribute ✅ ✅ Lazy loading ✅ ✅ srcset and sizes attributes ✅ ✅ Resource Hints ✅ ✅ Content-Security Policy (CSP) ✅ ✅ fetchpriority attribute ✅ ✅ blocking=&quot;render&quot; ✅ ✅ for AR/VR/3D content ✅ ✅ controlslist attribute ✅ ✅ ✅ ✅ Using Custom Elements ✅ ✅ Defining Custom Elements ✅ ✅ Scoped Custom Element Registries ✅ ✅ Shadow DOM ✅ ✅ Declarative Shadow DOM ✅ ✅ Named slot assignment ✅ ✅ Imperative slot assignment ✅ ✅ ElementInternals API ✅ ✅ DOM Parts ✅ ✅ HTML Modules ✅ ✅ Landmark elements ✅ ✅ tabindex attribute ✅ ✅ focusgroup attribute ✅ ✅ ✅ ✅ File System Access API ✅ ✅ Badging API ✅ ✅ Web Share API ✅ ✅ Launch Handler API ✅ ✅ File Handling API ✅ ✅ Window Controls Overlay API ✅ ✅ Isolated Web Apps ✅ ✅ Customizable Select ✅ EditContext ✅ caretPositionFromPoint ✅ Clipboard API ✅ CSS Custom Highlight API ✅ setHtmlUnsafe() ✅ parseHtmlUnsafe() ✅ Intl.Segmenter API ✅ I believe that co-designing these surveys with the community is the best way to avoid blind spots. While the timeline is tighter than usual this year ( the survey is launching later this month! ), there is still a little time to ask: 👉🏼 Which upcoming HTML features or Web APIs are currently on your radar? 👈🏼 What does “on your radar” mean? Features you’re excited about and would love to see progress on. Why focus on upcoming features? The best candidates for these surveys are features that are mature enough to be fleshed out (at least a mature proposal, ideally a spec and WPT tests ), but not so mature they have already been implemented in every browser. These are the features for which a survey such as this can drive meaningful impact . If it’s so early for a feature that it’s not yet fleshed out, it’s hard to make progress via initiatives such as Interop . Interest is still useful signal to help prioritize work on fleshing it out, but it’s a bit of a longer game. And for features that are already implemented everywhere, the only thing that can improve things further is passage of time — a problem for which I unfortunately have no solution (yet) . Obviously we’re looking at all the usual suspects already, and initiatives such as webstatus.dev and Web platform features explorer provide a treasure trove of data which makes this task infinitely easier than it used to be. But this kind of preliminary signal is also useful for filtering and prioritization — to give you a sense, my list of candidate new features to ask about already has 57 items (!). Given that State of HTML 2024 asked about 49 features, that will need some very heavy pruning. While the title is “State of HTML” , anything that wouldn’t fit better in State of CSS or State of JS is fair game. This includes topics such as accessibility, browser APIs, web components, templating, static site generation, media formats, and more. This may seem strange at first, but is no different than how the HTML specification itself covers a lot more than just HTML markup. Any way to reach me works fine. You can post in the comments here (preferred), or reply on BlueSky , Mastodon , Threads , LinkedIn , or Twitter . Make sure to check the other replies first, and 👍 those with features you care about. Looking forward to your ideas and comments! Devographics is the company behind “State of …” surveys. ↩︎ As an Easter egg, this widget is just a element with custom CSS. Inspect it to see how it works! It works best in Chrome and Safari, as they fully support ::details-content . Chrome also supports calc-size() , which enables a nice animation, while the interaction in Safari is more abrupt. In terms of a11y, the summary gets spoken out as a regular element, with “Show more” or “Show less” at the end of its content. It seems ok-ish to me, but I’d love to hear from those with more expertise in this area. ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Influence the State of HTML 2025 Survey!</title>
  <link>https://lea.verou.me/blog/2025/design-state-of-html/</link>
  <pubDate>Sun, 17 May 2026 03:45:21 +0200</pubDate>
  <description>Mamma mia, here we go again! Two years ago, I was funded by Google to design the inaugural State of HTML survey . While I had led State of surveys before (also graciously sponsored by Google), that was by far the most intense, as 01 projects often are. In addition to the research, content, and analysis work that goes into every State of survey, the unique challenges it presented were a forcing function for finally tackling some longstanding UX issues with these surveys. As a result, we pioneered new survey interaction UIs , and validated them via usability testing. This work did not just affect State of HTML, but had ripple effects on all subsequent State of surveys. The results made it all worth it. Turnout was the highest ever for a new Devographics [1] survey: 21 thousand participants , which remains a record high for State of HTML. The survey findings heavily influenced Interop 2024 (hello Popover API and Declarative Shadow DOM!) and helped prioritize several other initiatives, such as stylable selects . Despite lower 2024 participation, the survey still significantly influenced Interop 2025 ; notably, View transitions was added after being prominent in the survey for two years in a row. This is the goal of these surveys: to drive meaningful change in the web platform . Sure, getting a shareable score about what you know and seeing how you compare to the rest of the industry is fun, but the reason browser vendors pour thousands of dollars into funding these surveys is because they provide unique vendor-neutral insights into developer pain points and priorities , which helps them make better decisions about what to work on. And this ultimately helps you: by getting your voice heard, you can directly influence the tools you work with. Its a win-win: developers get better tools, and browser vendors get better roadmaps. Fun fact One of my favorite examples of impact these surveys have driven is CSS Nesting . Browsers ignored the proposal to do Nesting natively for a decade , the rationale being that preprocessors served the need just fine. Then it came up in State of CSS 2022 as a top pain point and they scrambled to get it implemented ASAP. It was Baseline just a year later. Nothing moves things faster than demonstrating a clear need, and these surveys are a great way to do that. State of HTML 2025 Last year, I was too busy to take the lead again. Wrapping up my PhD and starting a new job immediately after, there was no time to breathe, let alone lead a survey. Im happy to be returning to it this year, but my joy is bittersweet. When I was first asked to lead this years survey a few months ago, I was still too busy to take it on. Someone else from the community accepted the role someone incredibly knowledgeable and talented who would have done a fantastic job. But they live in the Middle East, and as the war escalated, their safety and their familys well-being were directly impacted. Understandably, leading a developer survey became the least of their concerns. In the meantime, I made a few decisions that opened up some availability, and I was able to step in at the last minute. Its a sobering reminder that events which feel far away can hit close to home shaping not just headlines, but the work and lives of people we know. Web Platform Features at the verge of Interop A big part of these surveys is feature questions: respondents are presented with a series of web platform features, and asked about their familiarity and sentiment towards them. At the end, they get a score based on how many they were familiar with that they can share with others, and browser vendors and standards groups get signal on which upcoming features to prioritize or improve. You can see which features were included in last years survey here or in [2] the table below. State of HTML Features Feature 2023 2024 autocomplete attribute HTML Media Capture input.showPicker() FormData API contenteditable=&quot;plaintext-only&quot; and Exclusive Accordion Popover API inert attribute Lazy loading srcset and sizes attributes Resource Hints Content-Security Policy (CSP) fetchpriority attribute blocking=&quot;render&quot; for AR/VR/3D content controlslist attribute Using Custom Elements Defining Custom Elements Scoped Custom Element Registries Shadow DOM Declarative Shadow DOM Named slot assignment Imperative slot assignment ElementInternals API DOM Parts HTML Modules Landmark elements tabindex attribute focusgroup attribute File System Access API Badging API Web Share API Launch Handler API File Handling API Window Controls Overlay API Isolated Web Apps Customizable Select EditContext caretPositionFromPoint Clipboard API CSS Custom Highlight API setHtmlUnsafe() parseHtmlUnsafe() Intl.Segmenter API I believe that co-designing these surveys with the community is the best way to avoid blind spots. While the timeline is tighter than usual this year ( the survey is launching later this month! ), there is still a little time to ask: Which upcoming HTML features or Web APIs are currently on your radar? What does on your radar mean? Features youre excited about and would love to see progress on. Why focus on upcoming features? The best candidates for these surveys are features that are mature enough to be fleshed out (at least a mature proposal, ideally a spec and WPT tests ), but not so mature they have already been implemented in every browser. These are the features for which a survey such as this can drive meaningful impact . If its so early for a feature that its not yet fleshed out, its hard to make progress via initiatives such as Interop . Interest is still useful signal to help prioritize work on fleshing it out, but its a bit of a longer game. And for features that are already implemented everywhere, the only thing that can improve things further is passage of time a problem for which I unfortunately have no solution (yet) . Obviously were looking at all the usual suspects already, and initiatives such as webstatus.dev and Web platform features explorer provide a treasure trove of data which makes this task infinitely easier than it used to be. But this kind of preliminary signal is also useful for filtering and prioritization to give you a sense, my list of candidate new features to ask about already has 57 items (!). Given that State of HTML 2024 asked about 49 features, that will need some very heavy pruning. While the title is State of HTML , anything that wouldnt fit better in State of CSS or State of JS is fair game. This includes topics such as accessibility, browser APIs, web components, templating, static site generation, media formats, and more. This may seem strange at first, but is no different than how the HTML specification itself covers a lot more than just HTML markup. Any way to reach me works fine. You can post in the comments here (preferred), or reply on BlueSky , Mastodon , Threads , LinkedIn , or Twitter . Make sure to check the other replies first, and those with features you care about. Looking forward to your ideas and comments! Devographics is the company behind State of surveys. As an Easter egg, this widget is just a element with custom CSS. Inspect it to see how it works! It works best in Chrome and Safari, as they fully support ::details-content . Chrome also supports calc-size() , which enables a nice animation, while the interaction in Safari is more abrupt. In terms of a11y, the summary gets spoken out as a regular element, with Show more or Show less at the end of its content. It seems ok-ish to me, but Id love to hear from those with more expertise in this area.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The Hovercar Framework for Deliberate Product Design</title>
  <link>https://lea.verou.me/blog/2025/hovercar/</link>
  <pubDate>Sun, 17 May 2026 03:45:19 +0200</pubDate>
  <description>You may be familiar with this wonderful illustration and accompanying blog post by Henrik Kniberg about good MVPs: It’s a very visual way to illustrate the age-old concept that that a good MVP is not the one developed in isolation over months or years, grounded on assumptions about user needs and goals, but one that delivers value to users as early as possible , so that future iterations can take advantage of the lessons learned from real users. From Hovercar to Skateboard I love Henrik’s metaphor so much, I have been using a similar system to flesh out product requirements and shipping goals, especially early on. It can be immediately understood by anyone who has seen Henrik’s illustration, and I find it can be a lot more pragmatic and flexible than the usual simple two tiered system (core requirements and stretch goals). Additionally, I find this fits nicely into a fixed time, variable scope development process, such as Shape Up . 🛹 The Skateboard aka the Pessimist’s MVP What is the absolute minimum we can ship, if need be? Utilitarian, bare-bones, and somewhat embarrassing, but shippable — barely. Anything that can be flintstoned gets flintstoned. 🛴 The Scooter aka the Realist’s MVP The minimum product that delivers value. Usable, but no frills. This is the target. 🚲 The Bicycle aka the Optimist’s MVP Stretch goals — UX polish, “sprinkles of delight” , nonessential but high I/E features. Great if we get here, fine if we don’t. 🏍️ The Motorcycle Post-launch highest priority items. 🚗 The Car Our ultimate vision, taking current constraints into account. 🏎️ The Hovercar aka the North Star UI The ideal experience — unconstrained by time, resources, or backwards compatibility. Unlikely to ship, but a guiding light for all of the above. Please note that the concept of a North Star UI has no relation to the North Star Metric . While both serve as a guiding light for product decisions, and both are important, the North Star UI guides you in designing the product, whereas the North Star Metric is about evaluating success. To avoid confusion, I’ll refer to it as “North Star UI”, although it’s not about the UI per se, but the product vision on a deeper level. The first three stages are much more concrete and pragmatic, as they directly affect what is being worked on. The more we go down the list, the less fleshed out specs are, as they need to allow room for customer input. This also allows us to outline future vision, without having to invest in it prematurely. The most controversial of these is the last one: the hovercar , i.e. the North Star UI . It is the very antithesis of the MVP. The MVP describes what we can ship ASAP, whereas the North Star describes the most idealized goal, one we may never be able to ship. It is easy to dismiss that as a waste of time, a purely academic exercise. “We’re all about shipping. Why would we spend time on something that may not even be feasible?” I hear you cry in Agile. Stay with me for a moment, and please try to keep an open mind. Paradoxical as it may sound, fleshing out your North Star can actually save you time . How? Start counting. Core Idea At its core, this framework is about breaking down tough product design problems into three more manageable components: North Star : What is the ideal solution? Constraints : What prevents us from getting there right now ? Compromises : How close can we reasonably get given these constraints? One way to frame it is is that 2 &amp; 3 are the product version of tech debt . [1] It’s important to understand what constraints are fair game to ignore for 1 and which are not. I often call these ephemeral or situational constraints . They are constraints that are not fundamental to the product problem at hand, but relate to the environment in which the product is being built and could be lifted or change over time. Things like: Engineering resources Time Technical limitations (within reason) Performance Backwards compatibility Regulatory requirements Unlike ephemeral constraints, certain requirements are part of the problem description and cannot be ignored. Some examples from the case studies below: Context Chips Survey UI : Efficiency and discoverability CSS Nesting Syntax : Conciseness and readability While these may be addressed differently in different solutions, it would be an oxymoron to have a North Star that did not take them into account. Benefits 1. It makes hard product problems tractable Nearly every domain of human endeavor has a version of divide and conquer : instead of solving a complex problem all at once, break it down into smaller, manageable components and solve them separately. Product design is no different. This process really shines when you’re dealing with the kinds of tough product problems where at least two of these questions are hard, so breaking it down can do wonders for reducing complexity. 2. It makes the product design process robust and adaptable By solving these components separately, our product design process becomes can more easily adapt to changes. I have often seen “unimplementable” solutions become implementable down the line, due to changes in internal or external factors, or simply because someone had a lightbulb moment. By addressing these components separately, when constraints get lifted all we need to reevaluate is our compromises. But without this modularization, our only solution is to go back to the drawing board. Unsurprisingly, companies often choose to simply miss out on the opportunity, because it’s cheaper (or seems cheaper) to do so. 3. It facilitates team alignment by making the implicit, explicit Every shipping goal is derived from the North Star, like peeling layers off an onion. This is whether you realize it or not. Whether you realize it or not, every shipping goal is always derived from the North Star , like peeling layers off an onion. In some contexts the process of breaking down a bigger shipping goal into milestones that can ship independently is even called layering . The process is so ingrained, so automatic, that most product designers don’t realize they are doing it. They go from hovercar to car so quickly they barely realize the hovercar was there to begin with. Thinking about the North Star is taboo — who has time for daydreaming? We must ship, yesterday ! But the hovercar is fundamental . Without it, there is no skateboard — you can’t reduce the unknown. When designing it is not an explicit part of the process, the result is that the main driver of all product design decisions is something that can never be explicitly discussed and debated like any other design decision. In what universe is that efficient? A skateboard might be a good MVP if your ultimate vision is a hovercar, but it would be a terrible minimum viable cruise ship — you might want to try a wooden raft for that. A skateboard may be a great MVP for a car, but a terrible MVP for a cruise ship. Making the North Star taboo doesn’t make it disappear (when did that ever work?). It just means that everyone is following a different version of it. And since MVPs are products of the North Star, this will manifest as difficulty reaching consensus at every step of the way. The product team will disagree on whether to ship a skateboard or a wooden raft, then on whether to build a scooter or a simple sailboat, then on whether to work on a speedboat or a yacht, and so on. It will seem like there is so much disconnect that every decision is hard, but there is actually only one root disconnect that manifests as multiple because it is never addressed head on. When the North Star is not clearly articulated, everyone has their own. Here is a story that will sound familiar to many readers: A product team is trying to design a feature to address a specific user pain point. Alice has designed an elegant solution that addresses not just the problem at hand, but several prevalent longstanding user pain points at once — an eigensolution . She is aware it would be a little trickier to implement than other potential solutions, but the increase in implementation effort is very modest, and easily offset by the tremendous improvement in user experience. She has even outlined a staged deployment strategy that allows it to ship incrementally, adding value and getting customer feedback earlier. Excited, she presents her idea to the product team, only to hear engineering manager Bob dismiss it with “this is scope creep and way too much work, it’s not worth doing” . However, what Bob is actually thinking is “this is a bad idea; any amount of work towards it is a waste” . The design session is now derailed; instead of debating Alice’s idea on its merits, the discussion has shifted towards costing and/or reducing effort. But this is a dead end because the amount of work was never the real problem. In the end, Alice wants to be seen as a team player, so she backs off and concedes to Bob’s “simpler” idea, despite her worries that it is overfit to the very specific use case being discussed, and the product is now worse. Arguing over effort feels safer and less confrontational than debating vision — but is often a proxy war. Additionally, it is not productive. If the idea is poor, effort is irrelevant. And once we know an idea is good and believe it to our core, we have more incentive to figure out implementation, which often proves to be easier than expected once properly investigated. Explicitly fleshing out the Hovercar strips away the noise and brings clarity. When we answer the questions above in order and reach consensus on the North Star before moving on to the compromises, we know what is an actual design decision and what is a compromise driven by practical constraints. Articulating these separately, allows us to discuss them separately. It is very hard to evaluate tradeoffs collaboratively if you are not on the same page about what we are trading off and how much it’s worth. You need both the cost and the benefit to do a cost-benefit analysis! Additionally, fleshing the North Star out separately ensures that everyone is on the same page about what is being discussed . All too often have I seen early design sessions where one person is discussing the skateboard, another the bicycle, and a third one the hovercar, no-one realizing that the reason they can’t reach consensus is that they are designing different things. 4. It can improve the MVP via user testing Conventional wisdom is that we strip down the North Star to an MVP, ship that, then iterate based on user input. With that process, our actual vision never really gets evaluated and by the time we get to it, it has already changed tremendously. But did you know you can actually get input from real users without writing a single line of code? Believe it or not, you don’t need to wait until a UI is prototyped to user test it. You can even user test a low-fi paper prototype or even a wireframe. This is widely known in usability circles, yet somehow entirely unheard of outside the field. The user tells you where they would click or tap on every step, and you mock the UI’s response by physically manipulating the prototype or showing them a wireframe of the next stage. Obviously, this works better for some types of products than others. It is notably hard to mock rich interactions or UIs with too many possible responses. But when it does work, its Impact/Effort ratio is very high; you get to see whether your core vision is on the right track, and adjust your MVP accordingly. It can be especially useful when there are different perspectives within a team about what the North Star might be, or when the problem is so novel that every potential solution is low-confidence. No-one’s product intuition is always right, and there is no point in evaluating compromises if it turns out that even the “perfect” solution was not actually all that great . 5. It paves the way for future evolution So far, we have discussed the merits of designing our North Star, assuming we will never be able to ship it. However, in many cases, simply articulating what the North Star is can bring it within reach. It’s not magic, just human psychology. Once we have a North Star, we can use it to evaluate proposed solutions : How do they relate to it? Are they a milestone along a path that ends at the North Star? Do they actively prevent us from ever getting there? Prioritizing solutions that get us closer to the North Star can be a powerful momentum building tool. Humans find it a lot easier to make one more step along a path they are already on, than to make the first step on an entirely new path. This is well-established in psychology and often used as a technique for managing depression or executive dysfunction. However, it applies on anything that involves humans — and that includes product design. Once we’re partway there, it naturally begs the question: can we get closer? How much closer? Even if we can’t get all the way there, maybe we can close enough that the remaining distance won’t matter. And often, the closer you get, the more achievable the finish line gets. In fact, sometimes simply reframing the North Star as a sequence of milestones rather than a binary goal can be all that is needed to make it feasible. For an example of this, check out the CSS Nesting case study below. Case studies In my 20 years of product design, I have seen ephemeral constraints melt away so many times I have learned to interpret “unimplementable” as “kinda hard; right now” . Two examples from my own experience that I find particularly relevant below, one around Survey UI, and one around a CSS language feature. Context Chips and the Power of Engineering Momentum The case study is described at length in Context Chips in Survey Design: “Okay, but how does it feel ?” . In a nutshell, the relevant bits are: Originally, I needed to aggressively prioritize due to minimal engineering resources, which led me to design an extremely low-effort solution which still satisfied requirements. The engineer hated the low-effort idea so much, he prototyped a much higher-effort solution in a day, backend and all. Previously, this would have been entirely out of the question. Once I took the ephemeral constraints out of the question, I was able to design a much better, novel solution, but it got pushback on the basis of effort. Prototyping it allowed us to user test it, which revealed it performed way better than alternatives. Once user testing built engineering momentum and the implementation was more deeply looked into, it turned out it did not actually require as much effort as initially thought. Here is a dirty little secret about software engineering (and possibly any creative pursuit): neither feasibility nor effort are fixed for a given task. Engineers are not automatons that will implement everything with the same energy and enthusiasm. They may implement product vision they disagree with, but you will be getting very poor ROI out of their time. Investing the time and energy to get engineers excited can really pay dividends. When good engineers are excited, they become miracle workers. In fact, engineering momentum is often, all that is needed to make the infeasible, feasible. It may seem hard to fit this into the crunch of OKRs and KPIs but it’s worth it; the difference is not small, it is orders of magnitude. Things that were impossible or insurmountable become feasible, and things that would normally take weeks or months get done in days. One way to build engineering momentum is to demonstrate the value and utility of what is being built . All too often, product decisions are made in a vacuum, based on gut feelings and assumptions about user needs. Backing them up with data , such as usability testing sessions is an excellent way to demonstrate (and test!) their basis. When possible, having engineers observe user testing sessions firsthand can be much more powerful than secondhand reports. Relaxed CSS Nesting and the Power of Evolution Sometimes high effort things just take a lot of hard work and there is no way around it. Other times, feasibility is just one good idea away . One of my favorite examples, and something I’m proud to have helped drive is the relaxed CSS Nesting syntax , now shipped in every browser . It is such an amazing case study on the importance of having an explicit and consensus-backed North Star UI [2] . In a nutshell, CSS nesting was a (then new) CSS syntax that let developers better organize their code through reducing repetition. #css-nesting-example { pre { font-size: 50%; font-weight: 600; width: auto; overflow: auto; } pre code { white-space: pre; } } table.browser-support { border-collapse: collapse; } table.browser-support th, table.browser-support td { border: 1px solid silver; } @media (width a { color: inherit; } table.browser-support { border-collapse: collapse; @media (width a { color: inherit; } } } Example of CSS code, with (right) and without (left) nesting. Which one is easier to read? This is one of the rare cases where the North Star was well known in advance, since the syntax was already well established in developer tooling (CSS preprocessors). Instead, the big challenge was navigating the practical constraints , since CSS implemented in browsers has different performance characteristics, so a syntax that is feasible for tooling may be out of reach for a browser. In this case, the North Star syntax had been ruled out by browser engineers due to prohibitive parsing performance [3] , so we had to design a different, more explicit syntax that could be parsed more efficiently. At this point, it is important to note that CSS Nesting is a feature that is very heavily used once available. Conciseness and readability are paramount, especially when conciseness is the sole purpose of the feature in the first place! Initial attempts for a syntax that satisfied these technical requirements introduced a lot of noise, making the syntax tedious to write and noisy to read. Even worse, these attempts were actively incompatible with the North Star syntax, as well as other parts of the language (namely, the @scope rule). This meant that even if the North Star syntax became feasible later, CSS would need to forever support syntax that would then have no purpose, and would only exist as a wart from the past, just like HTML doctypes. Once Google became very keen to ship Nesting (driven by State of CSS 2022 , which showed it as the top missing CSS feature), a small subset of the CSS Working Group, led by Elika Etemad and myself met to explore alternatives, and produced four competing proposals . The one that the group voted to adopt [4] was the one I designed explicitly to answer the question: If the North Star syntax is out of the question right now , what is the largest subset of it that is feasible? This highlights the importance of taking into account the long-term evolution of a product in addition to the short-term utility and usability (although they are more important). Some of the other proposals had slightly better ergonomics than that the intermediate milestone we went with, but were incompatible with the North Star so they would have ruled it out. We took a bet: we sacrificed a little bit of short-term usability , to have a chance at much better usability in the long term . Sacrificing a lot of short-term utility or usability for a better future evolution trajectory is generally a bad idea, because you are risking being stuck with the poor intermediate solution, but even that can be acceptable when you have high confidence that you’ll get there, especially if that future is not very far. Once we got consensus on this intermediate syntax, I started exploring whether we could get any closer to the 🌟 , even proposing an algorithm that would reduce the number of cases that required the slower parsing to essentially an edge case. A few other WG members joined me, with my co-TAG member Peter Linss being most vocal. This is a big advantage of North Star compatible designs: it is much easier to convince people to move a little further along on the path they are already on, than to move to a completely different path. With a bit of luck, you may even find yourself implementing an “infeasible” North Star without even realizing it, one little step at a time. We initially faced a lot of resistance from browser engineers, until eventually a brilliant Google engineer, Anders Ruud and his team experimented with variations of my proposed algorithm and actually closed in on a way to implement the North Star syntax in Chrome. The rest, as they say, is history. 🌟 Conclusion Hopefully by now you’re convinced about the value of investing time in reaching alignment on an explicit North Star that has buy-in from the entire product team. A common misconception is that the North Star is a static goal that prevents you from adapting to new data, such as customer feedback. But often, your North Star will change a lot over time, and that’s okay. Having an initial destination does not take away your ability to course correct. That’s not giving up, it’s adapting. And yes, it’s true that many product teams do use a vision-led approach — they just start from the car, not the hovercar. While that confers some of the benefits above, there is still an implicit reduction happening, because the hovercar is still there in the back of their mind. Note that for this framework to be beneficial, it is important that everyone is on the same page and understands the steps, benefits, and goals of this approach. Co-designing a North Star with a team that sees the process as a pointless thought experiment will only add friction and will not confer any of these benefits. Also, this is a mindset that can only work when applied top-down. If you are not a decision-maker at your place of work and leadership is not on board, you will have a very hard time if you try to push this ad hoc, without first getting leadership buy-in. You can try sending them a link to this blog post! If this post resonated, please share your own case studies in the comments. Or, if you decide to give this framework a try, I’d love to hear how it went! Indeed, looks like I’m not the first to draw a parallel between the two! ↩︎ I even did an entire talk about it at Web Unleashed , with a lot more technical detail than what I have included here. ↩︎ for any Compilers geeks out there that want all the deets: it required potentially unbounded lookahead since there is no fixed number of tokens a parser can read and be able to tell the difference between a selector and a declaration. ↩︎ Originally dubbed “Lea’s proposal” , and later “Non-letter start proposal” , but became known as Option 3 from its position among the five options considered (including the original syntax). ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The Hovercar Framework for Deliberate Product Design</title>
  <link>https://lea.verou.me/blog/2025/hovercar/</link>
  <pubDate>Sun, 17 May 2026 03:45:19 +0200</pubDate>
  <description>You may be familiar with this wonderful illustration and accompanying blog post by Henrik Kniberg about good MVPs: Its a very visual way to illustrate the age-old concept that that a good MVP is not the one developed in isolation over months or years, grounded on assumptions about user needs and goals, but one that delivers value to users as early as possible , so that future iterations can take advantage of the lessons learned from real users. From Hovercar to Skateboard I love Henriks metaphor so much, I have been using a similar system to flesh out product requirements and shipping goals, especially early on. It can be immediately understood by anyone who has seen Henriks illustration, and I find it can be a lot more pragmatic and flexible than the usual simple two tiered system (core requirements and stretch goals). Additionally, I find this fits nicely into a fixed time, variable scope development process, such as Shape Up . The Skateboard aka the Pessimists MVP What is the absolute minimum we can ship, if need be? Utilitarian, bare-bones, and somewhat embarrassing, but shippable barely. Anything that can be flintstoned gets flintstoned. The Scooter aka the Realists MVP The minimum product that delivers value. Usable, but no frills. This is the target. The Bicycle aka the Optimists MVP Stretch goals UX polish, sprinkles of delight , nonessential but high I/E features. Great if we get here, fine if we dont. The Motorcycle Post-launch highest priority items. The Car Our ultimate vision, taking current constraints into account. The Hovercar aka the North Star UI The ideal experience unconstrained by time, resources, or backwards compatibility. Unlikely to ship, but a guiding light for all of the above. Please note that the concept of a North Star UI has no relation to the North Star Metric . While both serve as a guiding light for product decisions, and both are important, the North Star UI guides you in designing the product, whereas the North Star Metric is about evaluating success. To avoid confusion, Ill refer to it as North Star UI, although its not about the UI per se, but the product vision on a deeper level. The first three stages are much more concrete and pragmatic, as they directly affect what is being worked on. The more we go down the list, the less fleshed out specs are, as they need to allow room for customer input. This also allows us to outline future vision, without having to invest in it prematurely. The most controversial of these is the last one: the hovercar , i.e. the North Star UI . It is the very antithesis of the MVP. The MVP describes what we can ship ASAP, whereas the North Star describes the most idealized goal, one we may never be able to ship. It is easy to dismiss that as a waste of time, a purely academic exercise. Were all about shipping. Why would we spend time on something that may not even be feasible? I hear you cry in Agile. Stay with me for a moment, and please try to keep an open mind. Paradoxical as it may sound, fleshing out your North Star can actually save you time . How? Start counting. Core Idea At its core, this framework is about breaking down tough product design problems into three more manageable components: North Star : What is the ideal solution? Constraints : What prevents us from getting there right now ? Compromises : How close can we reasonably get given these constraints? One way to frame it is is that 2 &amp; 3 are the product version of tech debt . [1] Its important to understand what constraints are fair game to ignore for 1 and which are not. I often call these ephemeral or situational constraints . They are constraints that are not fundamental to the product problem at hand, but relate to the environment in which the product is being built and could be lifted or change over time. Things like: Engineering resources Time Technical limitations (within reason) Performance Backwards compatibility Regulatory requirements Unlike ephemeral constraints, certain requirements are part of the problem description and cannot be ignored. Some examples from the case studies below: Context Chips Survey UI : Efficiency and discoverability CSS Nesting Syntax : Conciseness and readability While these may be addressed differently in different solutions, it would be an oxymoron to have a North Star that did not take them into account. Benefits 1. It makes hard product problems tractable Nearly every domain of human endeavor has a version of divide and conquer : instead of solving a complex problem all at once, break it down into smaller, manageable components and solve them separately. Product design is no different. This process really shines when youre dealing with the kinds of tough product problems where at least two of these questions are hard, so breaking it down can do wonders for reducing complexity. 2. It makes the product design process robust and adaptable By solving these components separately, our product design process becomes can more easily adapt to changes. I have often seen unimplementable solutions become implementable down the line, due to changes in internal or external factors, or simply because someone had a lightbulb moment. By addressing these components separately, when constraints get lifted all we need to reevaluate is our compromises. But without this modularization, our only solution is to go back to the drawing board. Unsurprisingly, companies often choose to simply miss out on the opportunity, because its cheaper (or seems cheaper) to do so. 3. It facilitates team alignment by making the implicit, explicit Every shipping goal is derived from the North Star, like peeling layers off an onion. This is whether you realize it or not. Whether you realize it or not, every shipping goal is always derived from the North Star , like peeling layers off an onion. In some contexts the process of breaking down a bigger shipping goal into milestones that can ship independently is even called layering . The process is so ingrained, so automatic, that most product designers dont realize they are doing it. They go from hovercar to car so quickly they barely realize the hovercar was there to begin with. Thinking about the North Star is taboo who has time for daydreaming? We must ship, yesterday ! But the hovercar is fundamental . Without it, there is no skateboard you cant reduce the unknown. When designing it is not an explicit part of the process, the result is that the main driver of all product design decisions is something that can never be explicitly discussed and debated like any other design decision. In what universe is that efficient? A skateboard might be a good MVP if your ultimate vision is a hovercar, but it would be a terrible minimum viable cruise ship you might want to try a wooden raft for that. A skateboard may be a great MVP for a car, but a terrible MVP for a cruise ship. Making the North Star taboo doesnt make it disappear (when did that ever work?). It just means that everyone is following a different version of it. And since MVPs are products of the North Star, this will manifest as difficulty reaching consensus at every step of the way. The product team will disagree on whether to ship a skateboard or a wooden raft, then on whether to build a scooter or a simple sailboat, then on whether to work on a speedboat or a yacht, and so on. It will seem like there is so much disconnect that every decision is hard, but there is actually only one root disconnect that manifests as multiple because it is never addressed head on. When the North Star is not clearly articulated, everyone has their own. Here is a story that will sound familiar to many readers: A product team is trying to design a feature to address a specific user pain point. Alice has designed an elegant solution that addresses not just the problem at hand, but several prevalent longstanding user pain points at once an eigensolution . She is aware it would be a little trickier to implement than other potential solutions, but the increase in implementation effort is very modest, and easily offset by the tremendous improvement in user experience. She has even outlined a staged deployment strategy that allows it to ship incrementally, adding value and getting customer feedback earlier. Excited, she presents her idea to the product team, only to hear engineering manager Bob dismiss it with this is scope creep and way too much work, its not worth doing . However, what Bob is actually thinking is this is a bad idea; any amount of work towards it is a waste . The design session is now derailed; instead of debating Alices idea on its merits, the discussion has shifted towards costing and/or reducing effort. But this is a dead end because the amount of work was never the real problem. In the end, Alice wants to be seen as a team player, so she backs off and concedes to Bobs simpler idea, despite her worries that it is overfit to the very specific use case being discussed, and the product is now worse. Arguing over effort feels safer and less confrontational than debating vision but is often a proxy war. Additionally, it is not productive. If the idea is poor, effort is irrelevant. And once we know an idea is good and believe it to our core, we have more incentive to figure out implementation, which often proves to be easier than expected once properly investigated. Explicitly fleshing out the Hovercar strips away the noise and brings clarity. When we answer the questions above in order and reach consensus on the North Star before moving on to the compromises, we know what is an actual design decision and what is a compromise driven by practical constraints. Articulating these separately, allows us to discuss them separately. It is very hard to evaluate tradeoffs collaboratively if you are not on the same page about what we are trading off and how much its worth. You need both the cost and the benefit to do a cost-benefit analysis! Additionally, fleshing the North Star out separately ensures that everyone is on the same page about what is being discussed . All too often have I seen early design sessions where one person is discussing the skateboard, another the bicycle, and a third one the hovercar, no-one realizing that the reason they cant reach consensus is that they are designing different things. 4. It can improve the MVP via user testing Conventional wisdom is that we strip down the North Star to an MVP, ship that, then iterate based on user input. With that process, our actual vision never really gets evaluated and by the time we get to it, it has already changed tremendously. But did you know you can actually get input from real users without writing a single line of code? Believe it or not, you dont need to wait until a UI is prototyped to user test it. You can even user test a low-fi paper prototype or even a wireframe. This is widely known in usability circles, yet somehow entirely unheard of outside the field. The user tells you where they would click or tap on every step, and you mock the UIs response by physically manipulating the prototype or showing them a wireframe of the next stage. Obviously, this works better for some types of products than others. It is notably hard to mock rich interactions or UIs with too many possible responses. But when it does work, its Impact/Effort ratio is very high; you get to see whether your core vision is on the right track, and adjust your MVP accordingly. It can be especially useful when there are different perspectives within a team about what the North Star might be, or when the problem is so novel that every potential solution is low-confidence. No-ones product intuition is always right, and there is no point in evaluating compromises if it turns out that even the perfect solution was not actually all that great . 5. It paves the way for future evolution So far, we have discussed the merits of designing our North Star, assuming we will never be able to ship it. However, in many cases, simply articulating what the North Star is can bring it within reach. Its not magic, just human psychology. Once we have a North Star, we can use it to evaluate proposed solutions : How do they relate to it? Are they a milestone along a path that ends at the North Star? Do they actively prevent us from ever getting there? Prioritizing solutions that get us closer to the North Star can be a powerful momentum building tool. Humans find it a lot easier to make one more step along a path they are already on, than to make the first step on an entirely new path. This is well-established in psychology and often used as a technique for managing depression or executive dysfunction. However, it applies on anything that involves humans and that includes product design. Once were partway there, it naturally begs the question: can we get closer? How much closer? Even if we cant get all the way there, maybe we can close enough that the remaining distance wont matter. And often, the closer you get, the more achievable the finish line gets. In fact, sometimes simply reframing the North Star as a sequence of milestones rather than a binary goal can be all that is needed to make it feasible. For an example of this, check out the CSS Nesting case study below. Case studies In my 20 years of product design, I have seen ephemeral constraints melt away so many times I have learned to interpret unimplementable as kinda hard; right now . Two examples from my own experience that I find particularly relevant below, one around Survey UI, and one around a CSS language feature. Context Chips and the Power of Engineering Momentum The case study is described at length in Context Chips in Survey Design: Okay, but how does it feel ? . In a nutshell, the relevant bits are: Originally, I needed to aggressively prioritize due to minimal engineering resources, which led me to design an extremely low-effort solution which still satisfied requirements. The engineer hated the low-effort idea so much, he prototyped a much higher-effort solution in a day, backend and all. Previously, this would have been entirely out of the question. Once I took the ephemeral constraints out of the question, I was able to design a much better, novel solution, but it got pushback on the basis of effort. Prototyping it allowed us to user test it, which revealed it performed way better than alternatives. Once user testing built engineering momentum and the implementation was more deeply looked into, it turned out it did not actually require as much effort as initially thought. Here is a dirty little secret about software engineering (and possibly any creative pursuit): neither feasibility nor effort are fixed for a given task. Engineers are not automatons that will implement everything with the same energy and enthusiasm. They may implement product vision they disagree with, but you will be getting very poor ROI out of their time. Investing the time and energy to get engineers excited can really pay dividends. When good engineers are excited, they become miracle workers. In fact, engineering momentum is often, all that is needed to make the infeasible, feasible. It may seem hard to fit this into the crunch of OKRs and KPIs but its worth it; the difference is not small, it is orders of magnitude. Things that were impossible or insurmountable become feasible, and things that would normally take weeks or months get done in days. One way to build engineering momentum is to demonstrate the value and utility of what is being built . All too often, product decisions are made in a vacuum, based on gut feelings and assumptions about user needs. Backing them up with data , such as usability testing sessions is an excellent way to demonstrate (and test!) their basis. When possible, having engineers observe user testing sessions firsthand can be much more powerful than secondhand reports. Relaxed CSS Nesting and the Power of Evolution Sometimes high effort things just take a lot of hard work and there is no way around it. Other times, feasibility is just one good idea away . One of my favorite examples, and something Im proud to have helped drive is the relaxed CSS Nesting syntax , now shipped in every browser . It is such an amazing case study on the importance of having an explicit and consensus-backed North Star UI [2] . In a nutshell, CSS nesting was a (then new) CSS syntax that let developers better organize their code through reducing repetition. #css-nesting-example { pre { font-size: 50%; font-weight: 600; width: auto; overflow: auto; } pre code { white-space: pre; } } table.browser-support { border-collapse: collapse; } table.browser-support th, table.browser-support td { border: 1px solid silver; } @media (width a { color: inherit; } table.browser-support { border-collapse: collapse; @media (width a { color: inherit; } } } Example of CSS code, with (right) and without (left) nesting. Which one is easier to read? This is one of the rare cases where the North Star was well known in advance, since the syntax was already well established in developer tooling (CSS preprocessors). Instead, the big challenge was navigating the practical constraints , since CSS implemented in browsers has different performance characteristics, so a syntax that is feasible for tooling may be out of reach for a browser. In this case, the North Star syntax had been ruled out by browser engineers due to prohibitive parsing performance [3] , so we had to design a different, more explicit syntax that could be parsed more efficiently. At this point, it is important to note that CSS Nesting is a feature that is very heavily used once available. Conciseness and readability are paramount, especially when conciseness is the sole purpose of the feature in the first place! Initial attempts for a syntax that satisfied these technical requirements introduced a lot of noise, making the syntax tedious to write and noisy to read. Even worse, these attempts were actively incompatible with the North Star syntax, as well as other parts of the language (namely, the @scope rule). This meant that even if the North Star syntax became feasible later, CSS would need to forever support syntax that would then have no purpose, and would only exist as a wart from the past, just like HTML doctypes. Once Google became very keen to ship Nesting (driven by State of CSS 2022 , which showed it as the top missing CSS feature), a small subset of the CSS Working Group, led by Elika Etemad and myself met to explore alternatives, and produced four competing proposals . The one that the group voted to adopt [4] was the one I designed explicitly to answer the question: If the North Star syntax is out of the question right now , what is the largest subset of it that is feasible? This highlights the importance of taking into account the long-term evolution of a product in addition to the short-term utility and usability (although they are more important). Some of the other proposals had slightly better ergonomics than that the intermediate milestone we went with, but were incompatible with the North Star so they would have ruled it out. We took a bet: we sacrificed a little bit of short-term usability , to have a chance at much better usability in the long term . Sacrificing a lot of short-term utility or usability for a better future evolution trajectory is generally a bad idea, because you are risking being stuck with the poor intermediate solution, but even that can be acceptable when you have high confidence that youll get there, especially if that future is not very far. Once we got consensus on this intermediate syntax, I started exploring whether we could get any closer to the , even proposing an algorithm that would reduce the number of cases that required the slower parsing to essentially an edge case. A few other WG members joined me, with my co-TAG member Peter Linss being most vocal. This is a big advantage of North Star compatible designs: it is much easier to convince people to move a little further along on the path they are already on, than to move to a completely different path. With a bit of luck, you may even find yourself implementing an infeasible North Star without even realizing it, one little step at a time. We initially faced a lot of resistance from browser engineers, until eventually a brilliant Google engineer, Anders Ruud and his team experimented with variations of my proposed algorithm and actually closed in on a way to implement the North Star syntax in Chrome. The rest, as they say, is history. Conclusion Hopefully by now youre convinced about the value of investing time in reaching alignment on an explicit North Star that has buy-in from the entire product team. A common misconception is that the North Star is a static goal that prevents you from adapting to new data, such as customer feedback. But often, your North Star will change a lot over time, and thats okay. Having an initial destination does not take away your ability to course correct. Thats not giving up, its adapting. And yes, its true that many product teams do use a vision-led approach they just start from the car, not the hovercar. While that confers some of the benefits above, there is still an implicit reduction happening, because the hovercar is still there in the back of their mind. Note that for this framework to be beneficial, it is important that everyone is on the same page and understands the steps, benefits, and goals of this approach. Co-designing a North Star with a team that sees the process as a pointless thought experiment will only add friction and will not confer any of these benefits. Also, this is a mindset that can only work when applied top-down. If you are not a decision-maker at your place of work and leadership is not on board, you will have a very hard time if you try to push this ad hoc, without first getting leadership buy-in. You can try sending them a link to this blog post! If this post resonated, please share your own case studies in the comments. Or, if you decide to give this framework a try, Id love to hear how it went! Indeed, looks like Im not the first to draw a parallel between the two! I even did an entire talk about it at Web Unleashed , with a lot more technical detail than what I have included here. for any Compilers geeks out there that want all the deets: it required potentially unbounded lookahead since there is no fixed number of tokens a parser can read and be able to tell the difference between a selector and a declaration. Originally dubbed Leas proposal , and later Non-letter start proposal , but became known as Option 3 from its position among the five options considered (including the original syntax).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Bluesky Likes Web Components</title>
  <link>https://lea.verou.me/blog/2025/bluesky-likes/</link>
  <pubDate>Sun, 17 May 2026 03:45:18 +0200</pubDate>
  <description>Just want the components? Here you go: Demo Repo NPM A love letter to the Bluesky API I’m old enough to remember the golden Web 2.0 era, when many of today’s big social media platforms grew up. A simpler time, when the Web was much more extroverted. It was common for websites to embed data from others (the peak of mashups ), and prominently feature widgets from various platforms to showcase a post’s likes or shares. Especially Twitter was so ubiquitous that the number of Twitter shares was my primary metric for how much people were interested in a blog post I wrote. Then, websites started progressively becoming walled gardens, guarding their data with more fervor than Gollum guarding the Precious. Features disappeared or got locked behind API keys, ridiculous rate limits, expensive paywalls, and other restrictions. Don’t get me wrong, I get it. A lot of it was reactionary, a response to abuse — the usual reason we can’t have nice things. And even when it was to stimulate profit — it is understandable that they want to monetize their platforms. People gotta eat. I was recently reading this interesting article by Salma Alam-Naylor. The article makes some great points, but it was something else that caught my eye: the widget of Bluesky likes at the bottom. Salma&#39;s Bluesky likes widget that inspired these I mentioned it to my trusty apprentice Dmitry who discovered the API was actually much simpler than what we’ve come to expect. Later, it turned out Salma has even written an entire post on how to implement the same thing on your own site. The openness of the API was so refreshing. Not only can you read public data without being authenticated, you don’t even need an API key! Major nostalgia vibes. It seemed the perfect candidate for a web component that you can just drop in to a page, give it a post URL, and it will display the likes for that post. I just had to make it, and of course use it right here. Web Components that use API data have been historically awkward. Let’s set aside private API keys or APIs that require authentication even for reading public data for a minute. Even for public API keys, where on Earth do you put them?! There is no established pattern for passing global options to components. Attributes need to be specified on every instance , which is very tedious. So every component invents their own pattern: some bite the bullet and use attributes, others use static class fields, data-* attributes on any element or on specific elements, separate ES module exports, etc. None of these are ideal, so components often do multiple. Not to mention the onboarding hassle of creating API keys if you want to try multiple APIs. The Bluesky API was a breath of fresh air: just straightforward HTTP GET requests with straightforward JSON data responses. Sing with me! 🎶 all you need is fetch 🎺🎺🎺 🎶 all you need is fetch 🎺🎺🎺 🎶 all you need is fetch , fetch 🎶 🎶 fetch is all you need 🎶 Building a component that used it was a breeze. Two Components for displaying Bluesky likes In the end I ended up building two separate components, published under the same bluesky-likes npm package : — displays the number of likes for a post, and — displays the list of users who liked a post. They can be used separately, or together. E.g. to get a display similar to Salma’s widget, the markup would look like this: likes on Bluesky Like this post on Bluesky to see your face on this page And the result would be similar to this: I started by making a single component that did both, but it quickly became clear that it was better to split them up. It provided a lot more flexibility with only a tiny bit more effort for the common case, and it allowed me to simplify the internal structure of each component. Requests are aggressively cached across component instances, so the fact that it’s two separate components doesn’t mean you’ll be making duplicate requests. Additionally, these ended up pretty lightweight: the whole package is ~2.5 KB minified &amp; gzipped and dependency-free. Aside: On dependencies Don’t get me wrong; I have nothing against dependencies . For nontrivial web components or web component libraries, they are typically necessary. In fact, one of my many unfinished side projects is a modular library of composable web component helpers . I don’t set out to make anything dependency-free; I think that’s the wrong goal. Abstractions are how technology moves forward. But abstractions come with a cost , so they need to add enough value to justify their existence in a project. As with most things in life, it’s all about the balance of tradeoffs. I tend to approach most dependencies in the same way I approached the epidural when I had my daughter: first try without, and if it gets too painful, go for it. It just never got to that point. They both got close but then it was over. 🤷🏽‍♀️ API Design for Web Components Design Principles Per my usual API design philosophy , I wanted these components to make common cases easy, complex cases possible, and not have usability cliffs, i.e. the progression from the former to the latter should be smooth. API design curve What does that mean for a web component? Common use cases should be easy You should have a good result by simply including the component and specifying the minimum input to communicate your intent, in this case, a Bluesky post URL. You should not need to write CSS to make it look decent You should not need to write JavaScript to make it work You should not need to slot content for things that could have sensible defaults You should not need to specify things it can figure out on its own from things you’ve already specified Complex use cases should be possible If you’re willing to put more work into it, the sky should be the limit. You should be able to completely restyle it, customize nearly every part of its UI etc, but the UX of these things doesn’t need to be optimized. For example: Extensibility over encapsulation : If something doesn’t need to be hidden away, expose it as a part . Don’t be frugal with your parts. The downsides of exposing too many parts are few and small, but not exposing enough parts can make certain styling impossible. Don’t be frugal with slots : use slots with fallback content liberally. That way people can customize content or even entire parts of the UI. Expose states for conditional styling. Yes, it’s Baseline now. You can style the element itself, to avoid adding (and thus, having to expose) additional wrapper divs. And yes, you can expose a via a part as well. Just be mindful that that part will be available whether the slot has slotted content or not. For these components, as a proof of concept, in addition to parts and slots all component styles and templates are exposed as static properties on the component class that you can modify or replace, either directly on it, or in your own subclass, for extreme customizability. No usability cliffs Making common things easy and complex things possible is not enough for a good API. Most use cases fall somewhere in between the two extremes. If a small increase in use case complexity throws users off the deep end in terms of API complexity, they’re gonna have a bad time . The API should have enough customization hooks that common customizations do not require going through the same flow as full customization and recreating everything. For web components, this might mean: Ideally, standard CSS properties on the host should work. This is also part of the principle of least astonishment . However, sometimes this is simply not feasible or it would require unacceptable tradeoffs, which brings us to… Exposing enough custom properties that basic, common customizations don’t require parts. Nest slots liberally: You should not have to replace an entire part of the UI just to customize its content. Nested slots allow you to provide UI extension points at different levels of abstraction. The 99-99 rule of Web Components The Ninety-Ninety Rule tells us that the last 10% of the work takes 90% of the time. I would argue that for web components, it’s more like a 99-99 Rule. Take these components as an example. They are the poster child for the kind of straightforward, simple component that does one thing well, right? But web components are a bit like children: if most people realized upfront how much work they are, way fewer would get made. 😅 Building a web component is always more work than it looks Even when the core functionality is straightforward, there are so many other things that need to be done: Dynamically responding to changes (in attributes, slots, nested content, etc) like regular HTML elements takes work, especially if you want to do it 100% properly , which is rarely a good idea (more on that below). Libraries like Lit make some of it easier, but not trivial. Accessibility and i18n often take orders of magnitude more work than core functionality, especially together. Designing &amp; implementing style and UI customization hooks Figuring out the right tradeoffs between performance and all of the above And this is without any additional functionality creeping up. Some battle scars examples below. Customizing the link in A good component has sensible defaults, but allows customization of everything users may reasonably want to customize. There is nothing more annoying than finding a web component that does almost what you want, but doesn’t allow you to customize the one thing you really need to customize. My first prototype of always had an internal link in its shadow DOM that opened the full list of likers in a new tab. This opened it up to usability, accessibility, and i18n issues: What if you want it to link to the post itself, or even an entirely different URL? How to customize the link attributes, e.g. rel or target ? a11y: The link did not have a title at the time, only the icon had alt text. This meant assistive technologies would read it like “Butterfly blue heart fifteen”. How to word the link title to best communicate what the link does to assistive technologies without excessive verbosity? And then, how to allow users to customize the link title for i18n ? Often components will solve these types of problems the brute force way, by replicating all attributes on the component itself, which is both heavyweight and a maintenance nightmare over time. Instead, we went with a somewhat unconventional solution: the component detects whether it’s inside a link , and removes its internal element in that case. This solves all four issues at once; the answer to all of them is to just wrap it with the link of your choice. This allowed us to just pick a good default title attribute, and not have to worry about it. It’s not perfect: now that :host-context() is removed , there is no way for a component to style itself differently when it’s inside a link, to e.g. control the focus outline. And the detection is not perfect, because doing it 100% perfectly would incur a performance penalty for little gain. But on balance, it so far seems the tradeoffs are worth it. The pain of creating accessible components My first prototype of wrapped all avatars with regular links (they just had rel=&quot;nofollow&quot; and target=_blank&quot; ). Quite reasonable, right? And then it dawned on me: this meant that if a keyboard user had the misfortune of stumbling across this component in their path, they would have needed to hit Tab 101 (!) times in the worst case to escape it. Yikes on bikes! 😱 So what to do? tabindex=&quot;-1&quot; would remove the links from the tabbing order, fixing the immediate problem. But then how would keyboard users actually access them? A bigger question is “Do they need to?” . These links are entirely auxiliary; in Salma’s original widget avatars were not links at all . Even if someone wants to explore the profiles of people who liked a post for some reason, the Bluesky “Liked By” page (already linked via ) is a much better fit for this. When using a pointing device, links are free. If you don’t interact with them, they don’t get in your way, so you may as well have them even if few users will need them. But when something is part of the tabbing order, there is now a cost to it. Is the value of being able to tab to it outweighed by the friction of having to tab past it? On the other hand, it feels wrong to have links that are not exposed at all to keyboard and assistive tech users. Even if they are auxiliary, making them entirely inaccessible feels like we’re talking away their agency. I decided to err on the side of exposing the links to keyboard users, and added a description, via a description slot with default fallback content, to explain to SR users what is being displayed, and a skip link after it, which is visible when focused. Why not use the default slot for the description? The default slot can be very convenient when nothing else is slotted. However, it is very annoying to slot things in other slots without slotting anything in the default slot. Consider this: No likers :( It may not look like it, but here we’ve also slotted a few blank text nodes to the default slot, which would obliterate the SR-accessible default description with no visible signs to the developer. And since 2/5 devs don’t test at all for screen readers , they would be unlikely to notice. Default slots are great because they allow users to specify content without having to understand slots — it’s just how HTML works. However, because of this issue, I mainly recommend using them for things one nearly always wants to specify when using the component. If actual content is slotted into it, the additional blank nodes are not a problem. You could also choose to go for the default slot if you don’t have any other slots, though that’s a little more dangerous, as you may always want to add more slots later. It’s still not an ideal user experience though. A skip link offers you the choice of skipping only at the beginning. What happens if you tab through 30 links, and then decide you’ve had too much? Or when you’re tabbing backwards, via Shift + Tab ? Then you’re still stuck wading through all links with no escape and no respite. In the end, perhaps I should bite the bullet and implement more sophisticated keyboard navigation , similar to how native form controls work (imagine a having tab stops for every !). But I have already spent more than is reasonable on these components, so it’s time to let them ride the trains, and leave the rest to PRs. For now, I implemented Home and End keys to jump to the first and last link respectively, so that at least users have an out. But as a former TAG member, I can’t help but see this as a gap in the platform . It should not be this hard to create accessible components . It should not require jumping through hoops, and the process should not be a minefield. Good keyboard accessibility benefits everyone, and the primitives the web platform currently provides to enable that are egregiously insufficient. The pain of creating localizable web components Difficulty jumps to eleven when you want to make a component localizable. As a minimum, it means any UI text, no matter where it appears, must be customizable. This is desirable anyway for customizability, but it becomes essential for localization. The quick and dirty way is to provide slots for element content and attributes for content that appears in attributes (e.g. titles, aria-labels, etc). Avoid providing attributes as the only way to customize content. This means they cannot contain HTML, which is often necessary for localization, and always desirable for customization. That said, attributes are totally fine as a shortcut for making common cases easy. E.g. a common pattern is to provide both an attribute and a label with the same name for commonly customizable things (e.g. labels). However, this is often not enough. For example, both components display formatted numbers : displays the total number of likes, and displays the number of likes not shown (if any). The web platform thankfully already provides a low-level primitive for formatting numbers: Intl.NumberFormat , which you can also access via number.toLocaleString() . For example, to format 1234567 as 1.2M , you can do // Try it in the console! (1234567).toLocaleString(&quot;en&quot;, {notation: &quot;compact&quot;}) This is great for English UIs, but what about other languages? If you answered “Oh, we’ll just pass this.lang to instead of a hardcoded &quot;en&quot; ” , you’d be wrong, at least for the general case. That gives you the element language only when it’s directly specified on the element via a lang attribute . However, usually the lang attribute is not specified on every element, but on an ancestor, and it inherits down. Something like is a good compromise: const lang = this.lang || this.parentNode.closest(&quot;[lang]&quot;)?.lang || this.ownerDocument.documentElement.lang || &quot;en&quot;; This gets you the element’s language correctly if it’s: specified on the element itself specified on an ancestor element within the same shadow tree specified on the root element of the document This is what these components use. It’s not perfect, but it covers a good majority of cases with minimal performance impact. Notably, the cases it misses is when the component is inside a shadow tree but is getting its language from an element outside that shadow tree, that is also not the root element. I’d wager that case is very rare, and there is always the escape hatch of specifying the lang attribute on the component itself. What would doing it 100% properly entail? If the route above is a shortcut and misses some cases, you may be wondering what it would take to cover every possible case. Maybe it’s just for the lulz, or maybe you’re working under very strict guidelines that require you to fully emulate how a native element would behave. I advise against following or even reading this section. Proceed at your own risk. Or save your mental health and skip it . Unless you’re in the WHATWG, in which case please, go ahead. So what would doing it 100% properly look like? First, we’d want to take nested shadow roots into account, using something like this, which you might want to abstract into a helper function. let lang = this.lang; if (!lang) { let langElement = this; while (!(langElement = langElement.closest(&quot;[lang]&quot;))) { let root = langElement.getRootNode(); let host = root.host ?? root.documentElement; langElement = host; } lang = langElement?.lang || &quot;en&quot;; } But, actually , if you really needed to do it properly, even now you wouldn’t be done ! What about dynamically reacting to changes? Any element’s lang attribute could change at any point. Er, take my advice and don’t go there. Pour yourself a glass of wine (replace with your vice of choice if wine is not your thing), watch an episode of your favorite TV show and try to forget about this. Some of you will foolishly continue. I hear some voices at the back crying “But what about mutation observers?” . Oh my sweet summer child. What are you going to observe? The element with the lang attribute you just found? WRONG. What if a lang attribute is added to an element between that ancestor and your component? I.e. you go from this: To this: Your component language is now es , but nothing changed in the element you were observing ( #a ), so nothing notified your component. What is your recourse? I told you to not think about it. You didn’t listen. It’s still not too late to skip this section and escape the horrors that lie ahead. Still here? Damn, you’re stubborn. Fine, here’s how to do it with mutation observers if you really need to. But be warned, it’s going to hurt. Mutation observers cannot observe ancestors, so the only way to detect changes that way would be to observe not just the element with the lang attribute but also its entire subtree . Oh and if the path from your component to that ancestor involves shadow trees, you need to observe them separately, because mutation observers don’t reach past shadow trees ( proposal to change that). 😮‍💨 Surely, that should do it, right? WRONG again. I told you it would hurt. Consider the scenario where the ancestor with the lang attribute is removed. Mutation observers cannot observe element removal ( proposal to fix that), so if you go from this: To this: …nothing will notify your component if you’re just observing #a and its descendants. So the only way to get it right in all cases is to observe the entire tree, from the document root down to your component, including all shadow trees between your component and the root. Feeling nauseous yet? There is one alternative. So, the browser knows what the element’s language is, but the only way it exposes it is the :lang() pseudo-class , which doesn’t allow you to read it, but only check whether an element matches a given language. While not ideal, we can hack this to observe language changes. Coupled with the earlier snippet to detect the current language, this allows us to detect changes to the component’s language without the huge performance impact of observing the entire page. How can we do that? Once you’ve detected the component language, generate a rule that sets a CSS variable. E.g. suppose you detected el , you’d add this to your shadow DOM: :host(:lang(el)) { --lang: el; } Then, we register the --lang property, and observe changes to it via Style Observer or just raw transition events. When a change is detected, run the detection snippet again and add another CSS rule. When registering component CSS properties, make sure to register them globally (e.g. via CSS.registerProperty() ), as @property does not currently work in shadow DOM. This is already spec’ed, but not yet implemented by browsers. Now, should you do this? Just because you can, doesn’t mean you should. In the vast majority of cases, a few false positives/negatives are acceptable, and the tradeoff and performance impact of introducing all this complexity is absolutely not worth it . I can only see it being a good idea in very specific cases, when you have a reason to strive for this kind of perfection. Most of web components development is about making exactly these kinds of tradeoffs between how close you want to get to the way a native element would behave, and how much complexity and performance impact you’re willing to sacrifice for it. But going all the way is rarely a good balance of tradeoffs. That said, this should be easier . Reading a component’s language should not require balancing tradeoffs for crying out loud! There is some progress on that front. In September at TPAC we got WHATWG consensus on standardizing a way to read the current language / direction and react to future changes. To my knowledge, not much has happened since, but it’s a start. Perhaps this dramatic reenactment generates some empathy among WHATWG folks on what web components developers have to go through. 🚢 it, squirrel! It’s all fun and games and then you ship. Hopefully, I have demonstrated that if you’re not careful, building a web component can become a potentially unbounded task. Some tasks are definitely necessary, e.g. accessibility, i18n, performance, etc, but there comes a point where you’re petting . So here they are: Demo Repo NPM They’re far from perfect. Yes, they could be improved in a number of ways . But they’re good enough to use here, and that will do for now. If you want to improve them, pull requests are welcome (check with me for big features though). And if you use them on a for-profit site, I do expect you to fund their development . That’s an ethical and social expectation, not a legal one (but it will help prioritization, and that’s in your best interest too). If you’ve used them, I’d love to see what you do with them! Thanks to Léonie Watson for some of the early a11y feedback, and to Dmitry Sharabin for helping with the initial API exploration.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Bluesky Likes Web Components</title>
  <link>https://lea.verou.me/blog/2025/bluesky-likes/</link>
  <pubDate>Sun, 17 May 2026 03:45:18 +0200</pubDate>
  <description>Just want the components? Here you go: Demo Repo NPM A love letter to the Bluesky API Im old enough to remember the golden Web 2.0 era, when many of todays big social media platforms grew up. A simpler time, when the Web was much more extroverted. It was common for websites to embed data from others (the peak of mashups ), and prominently feature widgets from various platforms to showcase a posts likes or shares. Especially Twitter was so ubiquitous that the number of Twitter shares was my primary metric for how much people were interested in a blog post I wrote. Then, websites started progressively becoming walled gardens, guarding their data with more fervor than Gollum guarding the Precious. Features disappeared or got locked behind API keys, ridiculous rate limits, expensive paywalls, and other restrictions. Dont get me wrong, I get it. A lot of it was reactionary, a response to abuse the usual reason we cant have nice things. And even when it was to stimulate profit it is understandable that they want to monetize their platforms. People gotta eat. I was recently reading this interesting article by Salma Alam-Naylor. The article makes some great points, but it was something else that caught my eye: the widget of Bluesky likes at the bottom. Salma&#39;s Bluesky likes widget that inspired these I mentioned it to my trusty apprentice Dmitry who discovered the API was actually much simpler than what weve come to expect. Later, it turned out Salma has even written an entire post on how to implement the same thing on your own site. The openness of the API was so refreshing. Not only can you read public data without being authenticated, you dont even need an API key! Major nostalgia vibes. It seemed the perfect candidate for a web component that you can just drop in to a page, give it a post URL, and it will display the likes for that post. I just had to make it, and of course use it right here. Web Components that use API data have been historically awkward. Lets set aside private API keys or APIs that require authentication even for reading public data for a minute. Even for public API keys, where on Earth do you put them?! There is no established pattern for passing global options to components. Attributes need to be specified on every instance , which is very tedious. So every component invents their own pattern: some bite the bullet and use attributes, others use static class fields, data-* attributes on any element or on specific elements, separate ES module exports, etc. None of these are ideal, so components often do multiple. Not to mention the onboarding hassle of creating API keys if you want to try multiple APIs. The Bluesky API was a breath of fresh air: just straightforward HTTP GET requests with straightforward JSON data responses. Sing with me! all you need is fetch all you need is fetch all you need is fetch , fetch fetch is all you need Building a component that used it was a breeze. Two Components for displaying Bluesky likes In the end I ended up building two separate components, published under the same bluesky-likes npm package : displays the number of likes for a post, and displays the list of users who liked a post. They can be used separately, or together. E.g. to get a display similar to Salmas widget, the markup would look like this: likes on Bluesky Like this post on Bluesky to see your face on this page And the result would be similar to this: I started by making a single component that did both, but it quickly became clear that it was better to split them up. It provided a lot more flexibility with only a tiny bit more effort for the common case, and it allowed me to simplify the internal structure of each component. Requests are aggressively cached across component instances, so the fact that its two separate components doesnt mean youll be making duplicate requests. Additionally, these ended up pretty lightweight: the whole package is ~2.5 KB minified &amp; gzipped and dependency-free. Aside: On dependencies Dont get me wrong; I have nothing against dependencies . For nontrivial web components or web component libraries, they are typically necessary. In fact, one of my many unfinished side projects is a modular library of composable web component helpers . I dont set out to make anything dependency-free; I think thats the wrong goal. Abstractions are how technology moves forward. But abstractions come with a cost , so they need to add enough value to justify their existence in a project. As with most things in life, its all about the balance of tradeoffs. I tend to approach most dependencies in the same way I approached the epidural when I had my daughter: first try without, and if it gets too painful, go for it. It just never got to that point. They both got close but then it was over. API Design for Web Components Design Principles Per my usual API design philosophy , I wanted these components to make common cases easy, complex cases possible, and not have usability cliffs, i.e. the progression from the former to the latter should be smooth. API design curve What does that mean for a web component? Common use cases should be easy You should have a good result by simply including the component and specifying the minimum input to communicate your intent, in this case, a Bluesky post URL. You should not need to write CSS to make it look decent You should not need to write JavaScript to make it work You should not need to slot content for things that could have sensible defaults You should not need to specify things it can figure out on its own from things youve already specified Complex use cases should be possible If youre willing to put more work into it, the sky should be the limit. You should be able to completely restyle it, customize nearly every part of its UI etc, but the UX of these things doesnt need to be optimized. For example: Extensibility over encapsulation : If something doesnt need to be hidden away, expose it as a part . Dont be frugal with your parts. The downsides of exposing too many parts are few and small, but not exposing enough parts can make certain styling impossible. Dont be frugal with slots : use slots with fallback content liberally. That way people can customize content or even entire parts of the UI. Expose states for conditional styling. Yes, its Baseline now. You can style the element itself, to avoid adding (and thus, having to expose) additional wrapper divs. And yes, you can expose a via a part as well. Just be mindful that that part will be available whether the slot has slotted content or not. For these components, as a proof of concept, in addition to parts and slots all component styles and templates are exposed as static properties on the component class that you can modify or replace, either directly on it, or in your own subclass, for extreme customizability. No usability cliffs Making common things easy and complex things possible is not enough for a good API. Most use cases fall somewhere in between the two extremes. If a small increase in use case complexity throws users off the deep end in terms of API complexity, theyre gonna have a bad time . The API should have enough customization hooks that common customizations do not require going through the same flow as full customization and recreating everything. For web components, this might mean: Ideally, standard CSS properties on the host should work. This is also part of the principle of least astonishment . However, sometimes this is simply not feasible or it would require unacceptable tradeoffs, which brings us to Exposing enough custom properties that basic, common customizations dont require parts. Nest slots liberally: You should not have to replace an entire part of the UI just to customize its content. Nested slots allow you to provide UI extension points at different levels of abstraction. The 99-99 rule of Web Components The Ninety-Ninety Rule tells us that the last 10% of the work takes 90% of the time. I would argue that for web components, its more like a 99-99 Rule. Take these components as an example. They are the poster child for the kind of straightforward, simple component that does one thing well, right? But web components are a bit like children: if most people realized upfront how much work they are, way fewer would get made. Building a web component is always more work than it looks Even when the core functionality is straightforward, there are so many other things that need to be done: Dynamically responding to changes (in attributes, slots, nested content, etc) like regular HTML elements takes work, especially if you want to do it 100% properly , which is rarely a good idea (more on that below). Libraries like Lit make some of it easier, but not trivial. Accessibility and i18n often take orders of magnitude more work than core functionality, especially together. Designing &amp; implementing style and UI customization hooks Figuring out the right tradeoffs between performance and all of the above And this is without any additional functionality creeping up. Some battle scars examples below. Customizing the link in A good component has sensible defaults, but allows customization of everything users may reasonably want to customize. There is nothing more annoying than finding a web component that does almost what you want, but doesnt allow you to customize the one thing you really need to customize. My first prototype of always had an internal link in its shadow DOM that opened the full list of likers in a new tab. This opened it up to usability, accessibility, and i18n issues: What if you want it to link to the post itself, or even an entirely different URL? How to customize the link attributes, e.g. rel or target ? a11y: The link did not have a title at the time, only the icon had alt text. This meant assistive technologies would read it like Butterfly blue heart fifteen. How to word the link title to best communicate what the link does to assistive technologies without excessive verbosity? And then, how to allow users to customize the link title for i18n ? Often components will solve these types of problems the brute force way, by replicating all attributes on the component itself, which is both heavyweight and a maintenance nightmare over time. Instead, we went with a somewhat unconventional solution: the component detects whether its inside a link , and removes its internal element in that case. This solves all four issues at once; the answer to all of them is to just wrap it with the link of your choice. This allowed us to just pick a good default title attribute, and not have to worry about it. Its not perfect: now that :host-context() is removed , there is no way for a component to style itself differently when its inside a link, to e.g. control the focus outline. And the detection is not perfect, because doing it 100% perfectly would incur a performance penalty for little gain. But on balance, it so far seems the tradeoffs are worth it. The pain of creating accessible components My first prototype of wrapped all avatars with regular links (they just had rel=&quot;nofollow&quot; and target=_blank&quot; ). Quite reasonable, right? And then it dawned on me: this meant that if a keyboard user had the misfortune of stumbling across this component in their path, they would have needed to hit Tab 101 (!) times in the worst case to escape it. Yikes on bikes! So what to do? tabindex=&quot;-1&quot; would remove the links from the tabbing order, fixing the immediate problem. But then how would keyboard users actually access them? A bigger question is Do they need to? . These links are entirely auxiliary; in Salmas original widget avatars were not links at all . Even if someone wants to explore the profiles of people who liked a post for some reason, the Bluesky Liked By page (already linked via ) is a much better fit for this. When using a pointing device, links are free. If you dont interact with them, they dont get in your way, so you may as well have them even if few users will need them. But when something is part of the tabbing order, there is now a cost to it. Is the value of being able to tab to it outweighed by the friction of having to tab past it? On the other hand, it feels wrong to have links that are not exposed at all to keyboard and assistive tech users. Even if they are auxiliary, making them entirely inaccessible feels like were talking away their agency. I decided to err on the side of exposing the links to keyboard users, and added a description, via a description slot with default fallback content, to explain to SR users what is being displayed, and a skip link after it, which is visible when focused. Why not use the default slot for the description? The default slot can be very convenient when nothing else is slotted. However, it is very annoying to slot things in other slots without slotting anything in the default slot. Consider this: No likers :( It may not look like it, but here weve also slotted a few blank text nodes to the default slot, which would obliterate the SR-accessible default description with no visible signs to the developer. And since 2/5 devs dont test at all for screen readers , they would be unlikely to notice. Default slots are great because they allow users to specify content without having to understand slots its just how HTML works. However, because of this issue, I mainly recommend using them for things one nearly always wants to specify when using the component. If actual content is slotted into it, the additional blank nodes are not a problem. You could also choose to go for the default slot if you dont have any other slots, though thats a little more dangerous, as you may always want to add more slots later. Its still not an ideal user experience though. A skip link offers you the choice of skipping only at the beginning. What happens if you tab through 30 links, and then decide youve had too much? Or when youre tabbing backwards, via Shift + Tab ? Then youre still stuck wading through all links with no escape and no respite. In the end, perhaps I should bite the bullet and implement more sophisticated keyboard navigation , similar to how native form controls work (imagine a having tab stops for every !). But I have already spent more than is reasonable on these components, so its time to let them ride the trains, and leave the rest to PRs. For now, I implemented Home and End keys to jump to the first and last link respectively, so that at least users have an out. But as a former TAG member, I cant help but see this as a gap in the platform . It should not be this hard to create accessible components . It should not require jumping through hoops, and the process should not be a minefield. Good keyboard accessibility benefits everyone, and the primitives the web platform currently provides to enable that are egregiously insufficient. The pain of creating localizable web components Difficulty jumps to eleven when you want to make a component localizable. As a minimum, it means any UI text, no matter where it appears, must be customizable. This is desirable anyway for customizability, but it becomes essential for localization. The quick and dirty way is to provide slots for element content and attributes for content that appears in attributes (e.g. titles, aria-labels, etc). Avoid providing attributes as the only way to customize content. This means they cannot contain HTML, which is often necessary for localization, and always desirable for customization. That said, attributes are totally fine as a shortcut for making common cases easy. E.g. a common pattern is to provide both an attribute and a label with the same name for commonly customizable things (e.g. labels). However, this is often not enough. For example, both components display formatted numbers : displays the total number of likes, and displays the number of likes not shown (if any). The web platform thankfully already provides a low-level primitive for formatting numbers: Intl.NumberFormat , which you can also access via number.toLocaleString() . For example, to format 1234567 as 1.2M , you can do // Try it in the console! (1234567).toLocaleString(&quot;en&quot;, {notation: &quot;compact&quot;}) This is great for English UIs, but what about other languages? If you answered Oh, well just pass this.lang to instead of a hardcoded &quot;en&quot; , youd be wrong, at least for the general case. That gives you the element language only when its directly specified on the element via a lang attribute . However, usually the lang attribute is not specified on every element, but on an ancestor, and it inherits down. Something like is a good compromise: const lang = this.lang || this.parentNode.closest(&quot;[lang]&quot;)?.lang || this.ownerDocument.documentElement.lang || &quot;en&quot;; This gets you the elements language correctly if its: specified on the element itself specified on an ancestor element within the same shadow tree specified on the root element of the document This is what these components use. Its not perfect, but it covers a good majority of cases with minimal performance impact. Notably, the cases it misses is when the component is inside a shadow tree but is getting its language from an element outside that shadow tree, that is also not the root element. Id wager that case is very rare, and there is always the escape hatch of specifying the lang attribute on the component itself. What would doing it 100% properly entail? If the route above is a shortcut and misses some cases, you may be wondering what it would take to cover every possible case. Maybe its just for the lulz, or maybe youre working under very strict guidelines that require you to fully emulate how a native element would behave. I advise against following or even reading this section. Proceed at your own risk. Or save your mental health and skip it . Unless youre in the WHATWG, in which case please, go ahead. So what would doing it 100% properly look like? First, wed want to take nested shadow roots into account, using something like this, which you might want to abstract into a helper function. let lang = this.lang; if (!lang) { let langElement = this; while (!(langElement = langElement.closest(&quot;[lang]&quot;))) { let root = langElement.getRootNode(); let host = root.host ?? root.documentElement; langElement = host; } lang = langElement?.lang || &quot;en&quot;; } But, actually , if you really needed to do it properly, even now you wouldnt be done ! What about dynamically reacting to changes? Any elements lang attribute could change at any point. Er, take my advice and dont go there. Pour yourself a glass of wine (replace with your vice of choice if wine is not your thing), watch an episode of your favorite TV show and try to forget about this. Some of you will foolishly continue. I hear some voices at the back crying But what about mutation observers? . Oh my sweet summer child. What are you going to observe? The element with the lang attribute you just found? WRONG. What if a lang attribute is added to an element between that ancestor and your component? I.e. you go from this: To this: Your component language is now es , but nothing changed in the element you were observing ( #a ), so nothing notified your component. What is your recourse? I told you to not think about it. You didnt listen. Its still not too late to skip this section and escape the horrors that lie ahead. Still here? Damn, youre stubborn. Fine, heres how to do it with mutation observers if you really need to. But be warned, its going to hurt. Mutation observers cannot observe ancestors, so the only way to detect changes that way would be to observe not just the element with the lang attribute but also its entire subtree . Oh and if the path from your component to that ancestor involves shadow trees, you need to observe them separately, because mutation observers dont reach past shadow trees ( proposal to change that). Surely, that should do it, right? WRONG again. I told you it would hurt. Consider the scenario where the ancestor with the lang attribute is removed. Mutation observers cannot observe element removal ( proposal to fix that), so if you go from this: To this: nothing will notify your component if youre just observing #a and its descendants. So the only way to get it right in all cases is to observe the entire tree, from the document root down to your component, including all shadow trees between your component and the root. Feeling nauseous yet? There is one alternative. So, the browser knows what the elements language is, but the only way it exposes it is the :lang() pseudo-class , which doesnt allow you to read it, but only check whether an element matches a given language. While not ideal, we can hack this to observe language changes. Coupled with the earlier snippet to detect the current language, this allows us to detect changes to the components language without the huge performance impact of observing the entire page. How can we do that? Once youve detected the component language, generate a rule that sets a CSS variable. E.g. suppose you detected el , youd add this to your shadow DOM: :host(:lang(el)) { --lang: el; } Then, we register the --lang property, and observe changes to it via Style Observer or just raw transition events. When a change is detected, run the detection snippet again and add another CSS rule. When registering component CSS properties, make sure to register them globally (e.g. via CSS.registerProperty() ), as @property does not currently work in shadow DOM. This is already speced, but not yet implemented by browsers. Now, should you do this? Just because you can, doesnt mean you should. In the vast majority of cases, a few false positives/negatives are acceptable, and the tradeoff and performance impact of introducing all this complexity is absolutely not worth it . I can only see it being a good idea in very specific cases, when you have a reason to strive for this kind of perfection. Most of web components development is about making exactly these kinds of tradeoffs between how close you want to get to the way a native element would behave, and how much complexity and performance impact youre willing to sacrifice for it. But going all the way is rarely a good balance of tradeoffs. That said, this should be easier . Reading a components language should not require balancing tradeoffs for crying out loud! There is some progress on that front. In September at TPAC we got WHATWG consensus on standardizing a way to read the current language / direction and react to future changes. To my knowledge, not much has happened since, but its a start. Perhaps this dramatic reenactment generates some empathy among WHATWG folks on what web components developers have to go through. it, squirrel! Its all fun and games and then you ship. Hopefully, I have demonstrated that if youre not careful, building a web component can become a potentially unbounded task. Some tasks are definitely necessary, e.g. accessibility, i18n, performance, etc, but there comes a point where youre petting . So here they are: Demo Repo NPM Theyre far from perfect. Yes, they could be improved in a number of ways . But theyre good enough to use here, and that will do for now. If you want to improve them, pull requests are welcome (check with me for big features though). And if you use them on a for-profit site, I do expect you to fund their development . Thats an ethical and social expectation, not a legal one (but it will help prioritization, and thats in your best interest too). If youve used them, Id love to see what you do with them! Thanks to Léonie Watson for some of the early a11y feedback, and to Dmitry Sharabin for helping with the initial API exploration.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Construction Lines</title>
  <link>https://lea.verou.me/blog/2025/construction-lines/</link>
  <pubDate>Sun, 17 May 2026 03:45:16 +0200</pubDate>
  <description>I recently stumbled across The Oatmeal’s series on Creativity . While all of it is spot on, the part on erasers hit especially hard. “There is a lot of shame associated with backpedaling; things like quitting your job, getting a divorce, or simply starting over are considered shameful . But forward isn’t always progress. And backward isn’t always regress. Sometimes going down the wrong path isn’t a mistake — it’s a construction line. ” — Matthew Inman (The Oatmeal) It was exactly what I needed to hear. You see, only a few days prior, Font Awesome and I had parted ways — the end of a short, but transformative chapter. I’m proud of what we built together, and grateful for what I learned along the way. But it was time to move on. Jobs are a lot like relationships. They often start with infatuation — and end with the realization that you’re simply not compatible, and that’s no-one’s fault. Letting go always stings, even when it’s the right call. There’s always grief: when you’re not ready to move on, you grieve the bond; when you are, you grieve your expectations. But every ending leaves behind clarity — about who you are, and what makes you happy. #logo-with-lines { max-height: 50vh; margin: auto; background: radial-gradient(white 30%, transparent 80%); display: block; } The pursuit of happiness Today is my 39th birthday — and this summer marks 20 years since I first dipped my toes into this industry. Naturally, I’ve been doing a lot of reflection. As is typical for ADHDers, I have done a ton of different things, and built a diverse skillset as a result. But what made me happiest? The list of highs went a bit like this: Entrepreneurship: Co-founding a startup and driving it to become a household name (in Greece — this was 2008!) Consulting: Being a full-time consultant, speaker, and author, traveling the world and jumping from one exciting gig to another Academia: [1] Pushing the boundaries of Human-Computer Interaction at MIT and teaching MIT CS students to care about people. All had three things in common: autonomy , breadth , and impact . These three things have been the biggest predictors of happiness for me — far more than income [2] or work-life balance , which are the usual suspects. I used to aspire to work-life balance in the same way I aspired to frequent exercise — because it was good for me, not because it gave me joy. Eventually I realized that what makes me happy isn’t working less, it’s loving what I do, and feeling it matters. Working less cannot transform how your work makes you feel ; it can only dampen the effects. But dilution doesn’t turn misery into joy — at best it just makes it tolerable. Don’t get me wrong, poor WLB can absolutely make you miserable; when long hours are an externally imposed expectation, not an internal drive fueled by passion. As with many things in life, it’s enthusiastic consent that makes all the difference. Then there’s breadth . Most jobs try to box you in: PM or engineer? Scientist or practitioner? UX Researcher or designer? DevRel or standards? And I’m like… Aurora is my spirit animal 🫶🏼 Source It’s silly that people are forced to choose, and present themselves as less than to seem more attractive to hiring managers. To use myself as an example: Web architecture: I have designed several web technologies that have shipped across all browsers. I’ve spent 4 years in the TAG , reviewing new web technologies across the web platform, and eventually leading the Web Platform Design Principles effort. Product/usability/HCI: Prior to working as Product Lead at Font Awesome, I’ve earned a PhD at MIT in Human-Computer Interaction with a minor in Entrepreneurship &amp; Innovation. I published peer reviewed papers in top-tier HCI conferences, and co-created/taught a course on usability &amp; web technologies that is now a permanent subject. I have run user research for scientific and industry projects. I have started several open source projects, some used by millions. In the more distant past, I co-founded a (then) well-known social startup in my home country, Greece and ran product, engineering, and design for three years (six if you count pre-incorporation). DevRel: I’ve given over 100 conference talks, published a bestselling book on CSS, and was the first devrel hire at W3C back in 2012. I have built dozens of apps and polyfills that stimulated developer interest in new Web features and drove browser adoption. Should I present myself as a web architecture expert, a usability/product person, an HCI researcher, or a Developer Advocate? What a pointless dilemma if I ever saw one! Combining skills across different areas is a strength to be celebrated, not a weakness to be swept under the rug. The crossover between skills is where the magic happens. Off the top of my head: Understanding usability principles has made me a far better web standards designer. Web standards work is product design on hard mode. After the impossibly hard constraints and tradeoffs you deal with when designing APIs for the Web platform, regular product problems seem like a cakewalk ( we have versions? And we can actually change things?? And we have reliable metrics?!? And the stakeholders all work at the same company?!? 🤯). They often feed into each other : DevRel work made me a better communicator in everything I do. Usability made me a better speaker and educator [3] , so a better developer advocate too. Leading the Web Platform Design Principles convinced me that explicit design principles are immensely useful for all product work . Web standards taught me that contrary to popular belief, you do not need a benevolent dictator to ship . But then you do need a good process. Consensus does not magically grow on trees, building consensus is its own art . Lastly, impact does not have to be about solving world hunger or curing cancer. Making people’s lives a little better is meaningful impact too. It all boils down to: Impact = Individual Impact × Reach You can achieve the same total impact by improving the lives of a few people a lot, or the lives of many people a little. For example, my work on web standards has been some of the most fulfilling work I’ve ever done. Its Individual Impact is small, but the Reach is millions, since all front-end developers out there use the same web platform. What’s next? Since consulting and entrepreneurship have been my happiness peaks, I figured I’d try them again. Yes, both at once, because after all, we’ve already established that WLB is a foreign concept 🤣 My apprentice Dmitry and I have been in high gear building some exciting things, which I hope to be able to share soon, and I feel more exhilarated than I have in years. I had missed drawing my own lines. In parallel, I’m taking on select consulting work , so if you need help with certain challenges, or to level up your team around web architecture, CSS, or usability, get in touch . Don’t get me wrong, I’m not closing the door to full-time roles. I know there are roles out there that value passion and offer the kind of autonomy, breadth, and impact that would let me thrive. It’s the ROI of digging through rubble to find them that gives me pause — as a product person at heart, I/E tradeoffs are top of mind. But if you have such a unicorn, I’m all ears . I also finally took a few small steps to make my pro bono work financially sustainable, a long overdue todo item. Both pages still need work, but you can now support my writing via ko-fi [4] , and my open source work via GitHub Sponsors . I made separate pages for my two most popular projects, Prism (nearing 1.8 billion total npm installs! 🤯) and Color.js . This is as much about prioritization as it is about sustainability : money is an excellent signal about what truly matters to people . It should go without saying that if you’re a for-profit company depending on open source work, you are expected to fund it. This is not a legal expectation, mainly because enforcement is a headache, but it absolutely is an ethical one. It is also in your best interest — funding the open source software you use ensures that it continues to be maintained. Don’t be the reason we can’t have nice things . I don’t have a polished “next” to announce yet. But I’m exactly where I need to be. Sometimes the clearest lines are the ones drawn after you erase. Many things wrong with academia, but the intellectual freedom is unparalleled, and it makes up for a lot. ↩︎ See also Alan Watts’ “What if money was no object?” — a classic, but still relevant. ↩︎ Teaching is absolutely a form of UI design — a UI that exposes your knowledge to students — the users. There are many similarities between how good educators design their material and how good UI designers design interfaces. ↩︎ Thanks Dan Abramov for the wording inspiration (with permission). These things are so hard. ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Construction Lines</title>
  <link>https://lea.verou.me/blog/2025/construction-lines/</link>
  <pubDate>Sun, 17 May 2026 03:45:16 +0200</pubDate>
  <description>I recently stumbled across The Oatmeals series on Creativity . While all of it is spot on, the part on erasers hit especially hard. There is a lot of shame associated with backpedaling; things like quitting your job, getting a divorce, or simply starting over are considered shameful . But forward isnt always progress. And backward isnt always regress. Sometimes going down the wrong path isnt a mistake its a construction line. Matthew Inman (The Oatmeal) It was exactly what I needed to hear. You see, only a few days prior, Font Awesome and I had parted ways the end of a short, but transformative chapter. Im proud of what we built together, and grateful for what I learned along the way. But it was time to move on. Jobs are a lot like relationships. They often start with infatuation and end with the realization that youre simply not compatible, and thats no-ones fault. Letting go always stings, even when its the right call. Theres always grief: when youre not ready to move on, you grieve the bond; when you are, you grieve your expectations. But every ending leaves behind clarity about who you are, and what makes you happy. #logo-with-lines { max-height: 50vh; margin: auto; background: radial-gradient(white 30%, transparent 80%); display: block; } The pursuit of happiness Today is my 39th birthday and this summer marks 20 years since I first dipped my toes into this industry. Naturally, Ive been doing a lot of reflection. As is typical for ADHDers, I have done a ton of different things, and built a diverse skillset as a result. But what made me happiest? The list of highs went a bit like this: Entrepreneurship: Co-founding a startup and driving it to become a household name (in Greece this was 2008!) Consulting: Being a full-time consultant, speaker, and author, traveling the world and jumping from one exciting gig to another Academia: [1] Pushing the boundaries of Human-Computer Interaction at MIT and teaching MIT CS students to care about people. All had three things in common: autonomy , breadth , and impact . These three things have been the biggest predictors of happiness for me far more than income [2] or work-life balance , which are the usual suspects. I used to aspire to work-life balance in the same way I aspired to frequent exercise because it was good for me, not because it gave me joy. Eventually I realized that what makes me happy isnt working less, its loving what I do, and feeling it matters. Working less cannot transform how your work makes you feel ; it can only dampen the effects. But dilution doesnt turn misery into joy at best it just makes it tolerable. Dont get me wrong, poor WLB can absolutely make you miserable; when long hours are an externally imposed expectation, not an internal drive fueled by passion. As with many things in life, its enthusiastic consent that makes all the difference. Then theres breadth . Most jobs try to box you in: PM or engineer? Scientist or practitioner? UX Researcher or designer? DevRel or standards? And Im like Aurora is my spirit animal Source Its silly that people are forced to choose, and present themselves as less than to seem more attractive to hiring managers. To use myself as an example: Web architecture: I have designed several web technologies that have shipped across all browsers. Ive spent 4 years in the TAG , reviewing new web technologies across the web platform, and eventually leading the Web Platform Design Principles effort. Product/usability/HCI: Prior to working as Product Lead at Font Awesome, Ive earned a PhD at MIT in Human-Computer Interaction with a minor in Entrepreneurship &amp; Innovation. I published peer reviewed papers in top-tier HCI conferences, and co-created/taught a course on usability &amp; web technologies that is now a permanent subject. I have run user research for scientific and industry projects. I have started several open source projects, some used by millions. In the more distant past, I co-founded a (then) well-known social startup in my home country, Greece and ran product, engineering, and design for three years (six if you count pre-incorporation). DevRel: Ive given over 100 conference talks, published a bestselling book on CSS, and was the first devrel hire at W3C back in 2012. I have built dozens of apps and polyfills that stimulated developer interest in new Web features and drove browser adoption. Should I present myself as a web architecture expert, a usability/product person, an HCI researcher, or a Developer Advocate? What a pointless dilemma if I ever saw one! Combining skills across different areas is a strength to be celebrated, not a weakness to be swept under the rug. The crossover between skills is where the magic happens. Off the top of my head: Understanding usability principles has made me a far better web standards designer. Web standards work is product design on hard mode. After the impossibly hard constraints and tradeoffs you deal with when designing APIs for the Web platform, regular product problems seem like a cakewalk ( we have versions? And we can actually change things?? And we have reliable metrics?!? And the stakeholders all work at the same company?!? ). They often feed into each other : DevRel work made me a better communicator in everything I do. Usability made me a better speaker and educator [3] , so a better developer advocate too. Leading the Web Platform Design Principles convinced me that explicit design principles are immensely useful for all product work . Web standards taught me that contrary to popular belief, you do not need a benevolent dictator to ship . But then you do need a good process. Consensus does not magically grow on trees, building consensus is its own art . Lastly, impact does not have to be about solving world hunger or curing cancer. Making peoples lives a little better is meaningful impact too. It all boils down to: Impact = Individual Impact Reach You can achieve the same total impact by improving the lives of a few people a lot, or the lives of many people a little. For example, my work on web standards has been some of the most fulfilling work Ive ever done. Its Individual Impact is small, but the Reach is millions, since all front-end developers out there use the same web platform. Whats next? Since consulting and entrepreneurship have been my happiness peaks, I figured Id try them again. Yes, both at once, because after all, weve already established that WLB is a foreign concept My apprentice Dmitry and I have been in high gear building some exciting things, which I hope to be able to share soon, and I feel more exhilarated than I have in years. I had missed drawing my own lines. In parallel, Im taking on select consulting work , so if you need help with certain challenges, or to level up your team around web architecture, CSS, or usability, get in touch . Dont get me wrong, Im not closing the door to full-time roles. I know there are roles out there that value passion and offer the kind of autonomy, breadth, and impact that would let me thrive. Its the ROI of digging through rubble to find them that gives me pause as a product person at heart, I/E tradeoffs are top of mind. But if you have such a unicorn, Im all ears . I also finally took a few small steps to make my pro bono work financially sustainable, a long overdue todo item. Both pages still need work, but you can now support my writing via ko-fi [4] , and my open source work via GitHub Sponsors . I made separate pages for my two most popular projects, Prism (nearing 1.8 billion total npm installs! ) and Color.js . This is as much about prioritization as it is about sustainability : money is an excellent signal about what truly matters to people . It should go without saying that if youre a for-profit company depending on open source work, you are expected to fund it. This is not a legal expectation, mainly because enforcement is a headache, but it absolutely is an ethical one. It is also in your best interest funding the open source software you use ensures that it continues to be maintained. Dont be the reason we cant have nice things . I dont have a polished next to announce yet. But Im exactly where I need to be. Sometimes the clearest lines are the ones drawn after you erase. Many things wrong with academia, but the intellectual freedom is unparalleled, and it makes up for a lot. See also Alan Watts What if money was no object? a classic, but still relevant. Teaching is absolutely a form of UI design a UI that exposes your knowledge to students the users. There are many similarities between how good educators design their material and how good UI designers design interfaces. Thanks Dan Abramov for the wording inspiration (with permission). These things are so hard.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Style-observer: JS to observe CSS property changes, for reals</title>
  <link>https://lea.verou.me/blog/2025/style-observer/</link>
  <pubDate>Sun, 17 May 2026 03:45:15 +0200</pubDate>
  <description>I cannot count the number of times in my career I wished I could run JS in response to CSS property changes, regardless of what triggered them: media queries, user actions, or even other JS. Use cases abound. Here are some of mine: Implement higher level custom properties in components, where one custom property changes multiple others in nontrivial ways (e.g. a --variant: danger that sets 10 color tokens). Polyfill missing CSS features Change certain HTML attributes via CSS (hello --aria-expanded !) Set CSS properties based on other CSS properties without having to mirror them as custom properties The most recent time I needed this was to prototype an idea I had for Web Awesome , and I decided this was it: I’d either find a good, bulletproof solution, or I would build it myself. Spoiler alert: Oops, I did it again A Brief History of Style Observers The quest for a JS style observer has been long and torturous. Many have tried to slay this particular dragon, each getting us a little bit closer. The earliest attempts relied on polling , and thus were also prohibitively slow. Notable examples were ComputedStyleObserver by Keith Clark in 2018 and StyleObserver by PixelsCommander in 2019. Jane Ori first asked “Can we do better than polling?” with her css-var-listener in 2019. It parsed the selectors of relevant CSS rules, and used a combination of observers and event listeners to detect changes to the matched elements. Artem Godin was the first to try using transition events such as transitionstart to detect changes, with his css-variable-observer in 2020. In fact, for CSS properties that are animatable, such as color or font-size , using transition events is already enough. But what about the rest, especially custom properties which are probably the top use case? In addition to pioneering transition events for this purpose, Artem also concocted a brilliant hack to detect changes to custom properties: he stuffed them into font-variation-settings , which is animatable regardless of whether the axes specified corresponded to any real axes in any actual variable font, and then listened to transitions on that property. It was brilliant, but also quite limited: it only supported observing changes to custom properties whose values were numbers (otherwise they would make font-variation-settings invalid). The next breakthrough came four years later, when Bramus Van Damme pioneered a way to do it “properly”, using the (then) newly Baseline transition-behavior: allow-discrete after an idea by Jake Archibald . His @bramus/style-observer was the closest we’ve ever gotten to a “proper” general solution. Releasing his work as open source was already a great service to the community, but he didn’t stop there. He stumbled on a ton of browser bugs , which he did an incredible job of documenting and then filing. His conclusion was: Right now, the only cross-browser way to observe Custom Properties with @bramus/style-observer is to register the property with a syntax of “ ”. Note that values can not start with a number, so you can’t use this type to store numeric values. Wait, what? That was still quite the limitation! My brain started racing with ideas for how to improve on this. What if, instead of trying to work around all of these bugs at once, we detect them so we only have to work around the ones that are actually present? World, meet style-observer At first I considered just sending a bunch of PRs, but I wanted to iterate fast, and change too many things. I took the fact that the domain observe.style was available as a sign from the universe, and decided the time had come for me to take my own crack at this age-old problem, armed with the knowledge of those who came before me and with the help of my trusty apprentice Dmitry Sharabin (hiring him to work full-time on our open source projects is a whole separate blog post) . One of the core ways style-observer achieves better browser support is that it performs feature detection for many of the bugs Bramus identified. This way, code can work around them in a targeted way, rather than the same code having to tiptoe around all possible bugs. As a result, it basically works in every browser that supports transition-behavior: allow-discrete , i.e. 90% globally. Safari transition loop bug ( #279012 ) : StyleObserver detects this and works around it by debouncing. Chrome unregistered transition bug ( #360159391 ) : StyleObserver detects this bug and works around it by registering the property, if unregistered. Firefox no initial transitionstart bug ( #1916214 ) : By design, StyleObserver does not fire its callback immediately (i.e. works more like MutationObserver than like ResizeObserver ). In browsers that do fire an initial transitionstart event, it is ignored. In addition, while working on this, we found a couple more bugs . Additionally, besides browser support, this supports throttling, aggregation, and plays more nicely with existing transitions. Since this came out of a real need, to (potentially) ship in a real product, it has been exhaustively tested, and comes with a testsuite of &gt; 150 unit tests (thanks to Dmitry’s hard work). If you want to contribute, one area we could use help with is benchmarking. That’s all for now! Try it out and let us know what you think! Gotta end with a call to action, amirite? Docs: observe.style Repo: leaverou/style-observer NPM: style-observer</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Style-observer: JS to observe CSS property changes, for reals</title>
  <link>https://lea.verou.me/blog/2025/style-observer/</link>
  <pubDate>Sun, 17 May 2026 03:45:15 +0200</pubDate>
  <description>I cannot count the number of times in my career I wished I could run JS in response to CSS property changes, regardless of what triggered them: media queries, user actions, or even other JS. Use cases abound. Here are some of mine: Implement higher level custom properties in components, where one custom property changes multiple others in nontrivial ways (e.g. a --variant: danger that sets 10 color tokens). Polyfill missing CSS features Change certain HTML attributes via CSS (hello --aria-expanded !) Set CSS properties based on other CSS properties without having to mirror them as custom properties The most recent time I needed this was to prototype an idea I had for Web Awesome , and I decided this was it: Id either find a good, bulletproof solution, or I would build it myself. Spoiler alert: Oops, I did it again A Brief History of Style Observers The quest for a JS style observer has been long and torturous. Many have tried to slay this particular dragon, each getting us a little bit closer. The earliest attempts relied on polling , and thus were also prohibitively slow. Notable examples were ComputedStyleObserver by Keith Clark in 2018 and StyleObserver by PixelsCommander in 2019. Jane Ori first asked Can we do better than polling? with her css-var-listener in 2019. It parsed the selectors of relevant CSS rules, and used a combination of observers and event listeners to detect changes to the matched elements. Artem Godin was the first to try using transition events such as transitionstart to detect changes, with his css-variable-observer in 2020. In fact, for CSS properties that are animatable, such as color or font-size , using transition events is already enough. But what about the rest, especially custom properties which are probably the top use case? In addition to pioneering transition events for this purpose, Artem also concocted a brilliant hack to detect changes to custom properties: he stuffed them into font-variation-settings , which is animatable regardless of whether the axes specified corresponded to any real axes in any actual variable font, and then listened to transitions on that property. It was brilliant, but also quite limited: it only supported observing changes to custom properties whose values were numbers (otherwise they would make font-variation-settings invalid). The next breakthrough came four years later, when Bramus Van Damme pioneered a way to do it properly, using the (then) newly Baseline transition-behavior: allow-discrete after an idea by Jake Archibald . His @bramus/style-observer was the closest weve ever gotten to a proper general solution. Releasing his work as open source was already a great service to the community, but he didnt stop there. He stumbled on a ton of browser bugs , which he did an incredible job of documenting and then filing. His conclusion was: Right now, the only cross-browser way to observe Custom Properties with @bramus/style-observer is to register the property with a syntax of . Note that values can not start with a number, so you cant use this type to store numeric values. Wait, what? That was still quite the limitation! My brain started racing with ideas for how to improve on this. What if, instead of trying to work around all of these bugs at once, we detect them so we only have to work around the ones that are actually present? World, meet style-observer At first I considered just sending a bunch of PRs, but I wanted to iterate fast, and change too many things. I took the fact that the domain observe.style was available as a sign from the universe, and decided the time had come for me to take my own crack at this age-old problem, armed with the knowledge of those who came before me and with the help of my trusty apprentice Dmitry Sharabin (hiring him to work full-time on our open source projects is a whole separate blog post) . One of the core ways style-observer achieves better browser support is that it performs feature detection for many of the bugs Bramus identified. This way, code can work around them in a targeted way, rather than the same code having to tiptoe around all possible bugs. As a result, it basically works in every browser that supports transition-behavior: allow-discrete , i.e. 90% globally. Safari transition loop bug ( #279012 ) : StyleObserver detects this and works around it by debouncing. Chrome unregistered transition bug ( #360159391 ) : StyleObserver detects this bug and works around it by registering the property, if unregistered. Firefox no initial transitionstart bug ( #1916214 ) : By design, StyleObserver does not fire its callback immediately (i.e. works more like MutationObserver than like ResizeObserver ). In browsers that do fire an initial transitionstart event, it is ignored. In addition, while working on this, we found a couple more bugs . Additionally, besides browser support, this supports throttling, aggregation, and plays more nicely with existing transitions. Since this came out of a real need, to (potentially) ship in a real product, it has been exhaustively tested, and comes with a testsuite of &gt; 150 unit tests (thanks to Dmitrys hard work). If you want to contribute, one area we could use help with is benchmarking. Thats all for now! Try it out and let us know what you think! Gotta end with a call to action, amirite? Docs: observe.style Repo: leaverou/style-observer NPM: style-observer</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Context Chips in Survey Design: “Okay, but how does it _feel_?”</title>
  <link>https://lea.verou.me/blog/2024/context-chips/</link>
  <pubDate>Sun, 17 May 2026 03:45:14 +0200</pubDate>
  <description>One would think that we’ve more or less figured survey UI out by now. Multiple choice questions, checkbox questions, matrix questions, dropdown questions, freeform textfields, numerical scales, what more could one possibly need ?! And yet, every time Google sponsored me to lead one of the State Of … surveys , and especially the inaugural State of HTML 2023 Survey , I kept hitting the same wall; I kept feeling that the established options for answering UIs were woefully inadequate for balancing the collection good insights with minimal friction for end-users . The State Of surveys used a completely custom survey infrastructure, so I could often (but not always) convince engineering to implement new question UIs. After joining Font Awesome , I somehow found myself leading yet another survey , despite swearing never to do this again. 🥲 Alas, building a custom survey UI was simply not an option in this case; I had to make do with the existing options out there [1] , so I felt this kind of pain to my core once again. So what are these cases where the existing answering UIs are inadequate, and how could better ones help? I’m hoping this case study to be Part 1 of a series around how survey UI innovations can help balance tradeoffs between user experience and data quality, though this is definitely the one I’m most proud of, as it was such a bumpy ride, but it was all worth it in the end. The Problem For context, the body of State Of surveys is a series of “Feature questions” , which present the respondent with a certain web platform feature and ask if they had heard of it or used it. Feature questions look like this: An example of a feature question from the State of CSS 2022 survey. Respondents get a score in the end, based on how many of these they had heard of or used. Each survey had dozens of these questions. Based on initial estimates, State of HTML was going to have at least fifty . This was my score. We revamped the scoring system for this iteration and switched from a percentage to a point-based score, since not all questions were equally weighted. Respondents love these questions. They learn about new things they may not have heard of, and get to test their knowledge. But also, from the survey designer’s perspective, they gamify a (very long) survey, increasing completion rates, and provide users incentive to share their score on social media, spreading the word. One would expect that they also provide valuable data, yet browser vendors had repeatedly mentioned that this data was largely useless to them. Surveys were all about what people felt , not what they knew or had used — they had better ways to gauge those. Instead, the reason they funneled thousands into funding these surveys every year was the 1-2 pain points questions towards the end. That was it. Survey data on experience and awareness could be useful, but only if it was accompanied with subjective sentiment data: if they hadn’t used it or heard about it, were they interested? If they had used it, how did it feel ? Optional freeform comments had been added the year prior, but got an abysmally low response rate, and being entirely freeform, were hard to analyze. As an attempt to address this feedback, a button that opened a freeform comment field had been introduced the year prior, but response rates were abysmally low, starting from 0.9% for the first question [2] and dropping further along the way. This was no surprise to me: freeform questions have a dramatically lower response rate than structured questions, and hidden controls get less interaction ( “out of sight, out of mind” ). But even if they had a high response rate, freeform comments are notoriously hard to analyze, especially when they are so domain specific. Ideation Essentially, the data we needed to collect was a combination of two variables: experience and sentiment . Collecting data on two variables is common in survey design, and typically implemented as a matrix question. 🤷 👍 👎 Never heard of it Heard of it Used it If user experience and cognitive load were not a concern, the same data could actually be collected with a matrix question. Indeed, if there were only a couple such questions, a matrix could have been acceptable. But …could you imagine filling out 50 of these? An acceptable solution needed to add minimal friction for end-users : there were at least 50 such questions, so any increase in friction would quickly add up — even one extra click was pushing it. And we needed a sufficiently high response rate to have a good CI . But it also needed to facilitate quantitative data analysis. Oh, and all of that should involve minimal engineering effort , as the (tiny) engineering team was already stretched thin. Did I hear anyone say overconstrained ? 😅 Idea 1: Quick context Initially, I took these constraints to heart. Misguided as it may have been, the comment field and the infrastructure around it already existed, so I designed a UI that revealed relevant positive/negative sentiment options using contextual progressive disclosure . These inserted predefined responses into the comment field with a single click. Being a purely client-side interaction meant it could be implemented in a day, and it still kept end-user friction at bay: providing sentiment was optional and only required a single click. In theory, quantitative data analysis was not optimally covered, as freeform responses are notoriously hard to analyze. However, based in the psychology of user behavior, I hypothesized that the vast majority of users would not edit these at all , a minority would append context, and an even tinier minority would actually edit the responses. This meant we could analyze them via simple string matching and only lose a few false negatives. Mockup of the quick context idea. I was very proud of myself: I had managed to design a solution that satisfied all constraints, a feat that initially seemed impossible! Not to mention this design gently guided users towards using the comment field, which could motivate them to add even more context. Yet, when I presented my mocks to the team, engineering hated it with a passion. The lead engineer (who was also the project founder) found the idea of turning a structured interaction into unstructured data deeply unsettling . So much it motivated him to implement a whole backend to store these followups properly, something I had initially thought was out of the question. So now what? Back to the drawing board, but with one constraint lifted! Ideas 2 &amp; 3: Followups and sentiment radios Mockups of intermediate ideas. Left: Followups (by lead engineer) Right: Sentiment radios (by Google PM) This new backend came with a UI proposal that raised red flags for both me and the Google PM I was collaborating with (one of the survey’s core stakeholders, but not the main one). Even seeing the followup UI required an extra click, so it was guaranteed to have a low response rate. It would have been better than the 0.9% of the comment field (clicking is easier than typing!), but still pretty low (I would estimate two clicks and a popover was a steep price to pay. “Two clicks and a popover” Perceived friction is not just about the number of clicks and keystrokes, but also about the number of context switches . The bigger the UI shift, the more cognitive overhead it adds, as the action is perceived as more substantial, even when in terms of user interaction it involves exactly the same number of steps (using simplistic models like KLM ). This is the reason why it feels smoother to have a text field that expands when you click on it, rather than a button that makes a text field appear: an existing control changing form is perceived as a smaller UI shift than one control disappearing and another appearing. Another idea came from the Google PM: sentiment radios . It was an attempt to simplify the interaction by framing it as a two step process: first experience, then sentiment, through radio buttons that slid down once a main answer was selected. However, I was very concerned that such a major UI shift after every single answer would quickly become overwhelming over the course of the survey. Idea 4: Context chips Back to the drawing board, I asked myself: if I had infinite engineering resources, what UI would I design? The biggest challenge was reducing friction. All ideas so far had required at least one extra (optional) click to select sentiment. Could we do better? What if users could select both experience and sentiment with a single click? Guided by this, I designed a UI where selecting sentiment is done via “context chips” which are actually part of the answer , so clicking them also selects the answer they are accompanying, allowing users to express an answer across both variables with a single click, or just select the answer itself to express no sentiment. To reduce visual clutter, these only faded in on hover. Additionally, clicking on the selected chip a second time would deselect it, fixing a longstanding UX issue with radio buttons [3] . Over the course of designing this, I became so convinced it was the right solution, that I implemented a high fidelity prototype myself, complete with code that could be easily adapted to the infrastructure used by the survey app. The context chips prototype on desktop and mobile. There were so many things I loved about this design, even beyond the core idea of answering both variables with a single click. There were no layout shifts, the followups were in close proximity to the main answer, and the styling of the chips helped build a visual association to reduce friction even more as you go. I was not a huge fan of the mobile version, but I couldn’t think of a much better way to adapt this UI to mobile. Early alternative concept that supported followups. This was deemed too complicated and was abandoned early on. Reception of context chips was not what I had hoped at first. I had expected pushback due to the engineering effort needed, but folks also had other concerns: that users would find things appearing on hover distracting and feel “ambushed”, that the UI was too “weird”, that users would not discover the 1-click interaction and use it as a two-step process anyway, and that response rate would be low because these chips were not visible upfront. Mini-feature questions: Context Chips + Checkboxes? Around the same time as designing context chips, I had a relevant realization: we don’t actually need to know both awareness and usage for all features . For old, widely supported features, awareness doesn’t matter, because even when it’s low, it has plateaued. And for features that are so new they have not yet been implemented in browsers, usage is largely meaningless. For these cases, each feature only has two states, and thus experience can be expressed with a checkbox! This would allow us to combine questions about multiple features in one, and we could still use context chips, albeit a little differently: The mini features prototype on desktop and mobile. While these could be used for questions that either discern usage or awareness, we decided to stick to the former, as there was a (valid) concern that having mini-feature questions whose checked state meant different things could be confusing and lead to errors. That way, only old, lower-priority features would be relegated to this template, and new features which tend to be higher priority for browser vendors would still get the full UI, comments and all. Instead, to improve the experience for cutting edge features, we introduced a “Not implemented” tag next to the “Used it” option. One disadvantage of the mini feature UI is that due to the way context chips work, it is not possible to select sentiment for features you have not used: once you click on a chip, it also selects the feature, as if you had clicked on its label. I guess it could be possible to click on a chip and then uncheck the feature, but that would be a very weird interaction. Idea 5: Existing 5-point question template At this point, the lead engineer dredged up a question template that had been used in other surveys to ask about the respondent’s experience with various types of tooling. Instead of separating experience and sentiment, it used a 5-point scale where each answer except the first answered both questions. The existing 5-point question template. The eng lead was sold: zero engineering effort! The Google PM was also sold: 100% response rate! (since it was not possible to avoid expressing sentiment for features you had heard or used). I had serious reservations. There are arguments for even numbered Likert scales (no neutral option), but these always involve scales of at least 4 points. If you force people to select between two states, positive or negative, you’re simply going to get garbage data. Neutral votes get pushed into positive votes, and the data around positive sentiment becomes useless. These did not allow users to express sentiment for features they had not heard of, despite these questions often including enough info for users to know whether they were interested. I was worried that increasing the number of upfront answers to 5 would increase cognitive load too much — and even scrolling distance, by 66%! A UX researcher we were working with even did a heuristic evaluation that somewhat favored the 5-point template mainly on the basis of being a more familiar UI. The odds seemed stacked against context chips, but the upcoming usability testing could still tip the scales in their favor. Usability Testing to the Rescue! Despite the lead engineer being unconvinced about the merits of context chips and being adamant that even adapting my fully functional prototype was too much work, since the prototype existed, we decided to user test it against the 5-point question and see how it compared. We ran a within-subjects usability study with 6 participants ( no, they are not too few ) recruited via social media. Half of the survey used the 5-point template, the other half context chips. The order of the conditions was randomized to avoid order effects. In addition to their actual experience, we also collected subjective feedback at the end of the survey, showing them a screenshot of each answering UI and asking how each felt. What worked well: Context Chips I have run many usability studies in the last ten years, and I have never seen results as resounding as this one. So much that we unanimously agreed to switch to context chips after the 5th participant, because the scales were so tipped in favor of context chips that nothing that happened in the last session could have tipped them the other way. The lead engineer observed some of the sessions, and this was instrumental in changing his mind. This was not a coincidence: when engineering is unconvinced that a certain UI is worth the implementation complexity, it can be a good strategy to have them observe usability testing sessions. Not only does it help prove the value to them, it also builds long-term user empathy, which makes future consensus easier. Given the unfortunate lack of HCI prioritization in Computer Science curricula, this may even be their first exposure to usability testing. All of my concerns about the 5-point template were brought up by participants on their own accord, repeatedly: All participants really liked being able to express sentiment, and were vocal about their frustration when they could not express it . All but one participant (4/5) complained about being forced into selecting a sentiment when they had no opinion . Some participants even mentioned that the 5-point template felt overwhelming. Furthermore, none of the concerns about context chips were validated: No-one found the chips appearing on hover distracting or felt “ambushed”. No-one struggled to understand how to use them. Everyone discovered the 1-click interaction pretty fast (typically within the first 2-3 questions). But interestingly, they still chose to use it as a two-step process for some of the questions , presumably to reduce cognitive load for difficult to answer questions by breaking down the decision into two smaller ones. The fact that this UI allowed users to make their own efficiency vs cognitive load tradeoffs was an advantage I had not even considered when designing it! Response rate was generally high — when people did not select sentiment, it was because they genuinely had no opinion, not because they couldn’t be bothered. What worked okay: Mini-feature Questions Mini-feature questions did successfully help cut down response time per feature by 75% , though this came at a cost: Once more, we saw that participants really wanted to express sentiment, and were frustrated when they couldn’t, which was the case for features they had not used. Regardless, we agreed that the tradeoff was worth it for the low-priority questions we were planning to use mini-features for. What did not work: Context Chips on Mobile A blind spot in our testing was that we did not test the UI on mobile. Usability tests were conducted remotely via video call, so it was a lot easier to get participants to use their regular computers. Additionally, stats for previous surveys showed that mobile use was a much smaller percentage in these surveys than for the web in general (~25%), so we did not prioritize it. This was a mistake in itself: using current usage stats to inform prioritization may seem like a great idea, but it can be prone to reverse causality bias . Meaning, are people not using these surveys on mobile very much because they genuinely don’t need to, or because the experience on mobile is subpar? We often see this with accessibility too: people claim that it doesn’t matter because they don’t have users with disabilities, but often they don’t have users with disabilities because their site is not accessible! But even if 75% of users genuinely preferred to take these surveys on desktop (which is plausible, since they are long and people often do them in increments), we should at least have done a few sessions on mobile — 25% is not negligible! Once responses started coming in, we realized that participants had trouble understanding what the up and down arrows meant, since on mobile these are shown without labels until selected. This would have been an easy fix had it had been caught early, e.g. thumbs up/down icons could have been used instead. This was not a huge issue, as their purpose becomes clear when selected, but it definitely adds some friction. Aftermath: Context Chips in the Wild In our usability testing, we had seen a high response rate for sentiment (% of question respondents who selected sentiment), but that is no guarantee things will play out that way post-launch as well. When participants know they are being watched they are always more willing to engage and pay a lot more attention, no matter how much you emphasize that they should act naturally when briefing them. That’s not their failing; it’s simply human nature. Indeed, sentiment response rates in the real-world were lower than those observed in the usability study, but still high — ranging from 24% to 59% and averaging 38% (with the same median) per question , meaning that out of every ten participants that answered each question, approximately four also provided a sentiment. This was more than enough to draw conclusions. In fact, context chips were deemed such a success they were later adopted by all other State Of surveys , even at the cost of continuity with previous years. Against expectations, participants were just as likely to express sentiment for features they had never heard of , and in fact marginally more likely than for features they had simply heard of. In general response rates were pretty uniform across all experiences: Experience Sentiment response rate (average) Sentiment response rate (median) Never heard of it 37.3% 37.4% Heard of it 37.3% 36.9% Used it 39.0% 40.1% Overall 37.6% 37.9% Sentiment response rates overall and by experience (per question). As we had observed in the user study as well, participants were far more likely to express positive rather than negative sentiment (possibly a case of acquiescence bias ). Here are some interesting stats: The feature with the most negative sentiment overall across all experiences ( ) still only had 10% of respondents expressing negative sentiment for it, and still had 2.4x more positive sentiment than negative (24.7% vs 10.4%)! In contrast, the feature with the most positive sentiment ( ) got a whopping 55% of respondents expressing positive sentiment (and only 4% negative). In fact, even the feature with the least positive sentiment ( Imperative Slot Assignment 🤔) still had way more positive sentiment (17.24%) than had negative sentiment (10%)! If we look at the ratio of positive over negative sentiment , it ranged from 64x (!) more positive than negative sentiment (45% vs 0.7% for landmark elements) to a mere 2.4x times more positive sentiment (24.7% vs 10.4% for ), and was 11x on average ( 8x median). The analysis presented in this section includes data from 20K respondents , which was most of the whole dataset (around 90%) but not all, as it was done before the survey closed. Results Visualization Presenting all this data was another challenge. When you have two variables, ideally you want to be able to group results by either . E.g. you may be more interested in the total negative sentiment for a feature, or how many people had used it, and the results visualization should support both. How do we design a results display that facilitates this? Thankfully the visualizations for State Of surveys were already very interactive so interactivity was not out of the question. I was no longer involved by then but consulted in a volunteer capacity. My main advice was to use proximity for clear visual grouping , and to use a consistent visual association for bars that represented the same bit of data, both of which they followed. This was the rendering they settled on for the results: Interactive bar chart presenting two variables at once. I think in terms of functionality this works really well. The visual design could be improved to communicate IA better and appear less busy at first glance, but given there is no dedicated designer in the team, I think they did a fantastic job. Generalizability While this UI was originally designed to collect sentiment about the selected option in a multiple choice question, I think it could be generalized to improving UX for other types of two variable questions . Generally, it can be a good fit when we have questions that collect data across two variables and: The second variable is optional and lower priority than the first The first variable is exclusive (single) choice, i.e. not checkbox questions. The core benefit of this approach is the reduction in cognitive load . It is well established that matrix questions are more overwhelming . This design allows questions to initially appear like a simple multiple choice question, and only reveal the UI for the second variable upon interaction. Additionally, while matrix questions force participants to decide on both variables at once, this design allows them to make their own tradeoff of cognitive load vs efficiency, treating the UI as single step or two step as they see fit. Another benefit of this design is that it allows for option labels to be context-dependent for the second variable, whereas a matrix limits you to a single column header. In the sentiment case, the labels varied depending on whether the feature had been used ( “Positive experience” / “Negative experience” ) or not ( “Interested” / “Not interested” ). The more such questions a survey has, the bigger the benefits — if it’s only about a couple questions, it may not be worth the implementation complexity, though I’m hoping that survey software may eventually provide this out of the box so that this is no longer a tradeoff. That said, matrix questions do have their benefits, when used appropriately, i.e. when the two requirements listed above are not met. Matrix questions have an big edge when users need to make a single selection per row , especially when this may be the same answer for multiple rows, which means they can just tick down a whole column . Context chips do not allow users to build this positional association, as they are not aligned vertically. THey are placed right after the answer text, and thus their horizontal position varies per answer. To mitigate this, it can be useful to color-code them and maintain the same color coding consistently throughout the survey so that participants can build a visual association [4] . Context chips also enforce a clear prioritization across the two variables: the question is presented as a multiple choice question across the first variable, with the chips allowing the respondent to provide optional additional context across a second variable. Matrix questions allow presenting the two variables with the same visual weight, which could be desirable in certain cases. For example, there are cases when we don’t want to allow the respondent to provide an answer to the first variable without also providing an answer to the second. Lessons Learned In addition to any generalizable knowledge around survey design, I think this is also an interesting product management case study, and teaches us several lessons. Never skimp on articulating the north star UI Start any product design task by ignoring ephemeral constraints (e.g. engineering resources) and first reach consensus on what the optimal UI is, before you start applying constraints. Yes, you read that right. I want to write a whole post about the importance of north star UIs, because this is one of many cases over the course of my career where tight implementation constraints were magically lifted, either due to a change of mind, a change in the environment, or simply someone’s brilliant idea. Without consensus on what the north star UI is (or even a clear idea about it) you then have to go back to the drawing board when this happens. User testing is also a consensus-building tool You probably already know that usability testing is a great tool for improving user experience, but there is a second, more strategic hidden utility to it: consensus building. I’ve been in way, way too many teams where UI decisions were made by hypothesizing about user behavior , which could not only be missing the mark, but also there is no way forwards for disagreements. What do you do, hypothesize harder? When there is user testing data, it is much harder to argue against it. This is especially useful in convincing engineering that a certain UI is worth the implementation complexity, and having engineers observe usability testing sessions can be an educational experience for many. For many engineers, user needs are an unknown unknown since HCI courses are elective in most CS curricula and not talked about very much in engineering resources. One of the most common comments we got after the first lecture of our usability &amp; web programming class at MIT was “Wow, I had never thought about software from the user’s perspective before!”. Believe it or not, these comments would often came from CS majors about to graduate! Heuristic evaluations are not a substitute for usability testing There are many things to like about heuristic evaluations and design reviews . They can be done by a usability expert alone and can uncover numerous issues that would have taken multiple rounds of usability testing, especially if they are also a domain expert. Fixing the low hanging fruit issues means user testing time can be spent more efficiently, uncovering less obvious problems. That are are also certain types of issues that can only be uncovered by a heuristic evaluation, such as _“death by a thousand paper cuts” type issues: small issues that are not a big deal on their own and are too small to observe in a user study, but add up to more friction. However, a big downside of these is that they are inherently prone to bias . They can be excellent for finding problems that may have been overlooked and are often obvious once pointed out. However (unless the number of evaluators is large), they are not a good way to decide between alternatives. Unlike Devographics, surveys are not FA’s core business, so the Impact/Effort tradeoff simply wasn’t there for a custom UI, at least at this point in time. I ended up going with Tally , mainly due to the flexibility of its conditional logic and its support for code injection (which among other things, allowed me to use FA icons — a whopping 120 different ones!). ↩︎ Meaning out of the people who responded to that question about their experience with a feature, only 0.9% left a comment. ↩︎ A radio group with all buttons off cannot be returned to that state by user interaction. ↩︎ The ~9% of people with atypical color vision won’t benefit, but that’s okay in this case, as color is used to add an extra cue, and not an essential part of the interface. ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Context Chips in Survey Design: Okay, but how does it _feel_?</title>
  <link>https://lea.verou.me/blog/2024/context-chips/</link>
  <pubDate>Sun, 17 May 2026 03:45:14 +0200</pubDate>
  <description>One would think that weve more or less figured survey UI out by now. Multiple choice questions, checkbox questions, matrix questions, dropdown questions, freeform textfields, numerical scales, what more could one possibly need ?! And yet, every time Google sponsored me to lead one of the State Of surveys , and especially the inaugural State of HTML 2023 Survey , I kept hitting the same wall; I kept feeling that the established options for answering UIs were woefully inadequate for balancing the collection good insights with minimal friction for end-users . The State Of surveys used a completely custom survey infrastructure, so I could often (but not always) convince engineering to implement new question UIs. After joining Font Awesome , I somehow found myself leading yet another survey , despite swearing never to do this again. Alas, building a custom survey UI was simply not an option in this case; I had to make do with the existing options out there [1] , so I felt this kind of pain to my core once again. So what are these cases where the existing answering UIs are inadequate, and how could better ones help? Im hoping this case study to be Part 1 of a series around how survey UI innovations can help balance tradeoffs between user experience and data quality, though this is definitely the one Im most proud of, as it was such a bumpy ride, but it was all worth it in the end. The Problem For context, the body of State Of surveys is a series of Feature questions , which present the respondent with a certain web platform feature and ask if they had heard of it or used it. Feature questions look like this: An example of a feature question from the State of CSS 2022 survey. Respondents get a score in the end, based on how many of these they had heard of or used. Each survey had dozens of these questions. Based on initial estimates, State of HTML was going to have at least fifty . This was my score. We revamped the scoring system for this iteration and switched from a percentage to a point-based score, since not all questions were equally weighted. Respondents love these questions. They learn about new things they may not have heard of, and get to test their knowledge. But also, from the survey designers perspective, they gamify a (very long) survey, increasing completion rates, and provide users incentive to share their score on social media, spreading the word. One would expect that they also provide valuable data, yet browser vendors had repeatedly mentioned that this data was largely useless to them. Surveys were all about what people felt , not what they knew or had used they had better ways to gauge those. Instead, the reason they funneled thousands into funding these surveys every year was the 1-2 pain points questions towards the end. That was it. Survey data on experience and awareness could be useful, but only if it was accompanied with subjective sentiment data: if they hadnt used it or heard about it, were they interested? If they had used it, how did it feel ? Optional freeform comments had been added the year prior, but got an abysmally low response rate, and being entirely freeform, were hard to analyze. As an attempt to address this feedback, a button that opened a freeform comment field had been introduced the year prior, but response rates were abysmally low, starting from 0.9% for the first question [2] and dropping further along the way. This was no surprise to me: freeform questions have a dramatically lower response rate than structured questions, and hidden controls get less interaction ( out of sight, out of mind ). But even if they had a high response rate, freeform comments are notoriously hard to analyze, especially when they are so domain specific. Ideation Essentially, the data we needed to collect was a combination of two variables: experience and sentiment . Collecting data on two variables is common in survey design, and typically implemented as a matrix question. Never heard of it Heard of it Used it If user experience and cognitive load were not a concern, the same data could actually be collected with a matrix question. Indeed, if there were only a couple such questions, a matrix could have been acceptable. But could you imagine filling out 50 of these? An acceptable solution needed to add minimal friction for end-users : there were at least 50 such questions, so any increase in friction would quickly add up even one extra click was pushing it. And we needed a sufficiently high response rate to have a good CI . But it also needed to facilitate quantitative data analysis. Oh, and all of that should involve minimal engineering effort , as the (tiny) engineering team was already stretched thin. Did I hear anyone say overconstrained ? Idea 1: Quick context Initially, I took these constraints to heart. Misguided as it may have been, the comment field and the infrastructure around it already existed, so I designed a UI that revealed relevant positive/negative sentiment options using contextual progressive disclosure . These inserted predefined responses into the comment field with a single click. Being a purely client-side interaction meant it could be implemented in a day, and it still kept end-user friction at bay: providing sentiment was optional and only required a single click. In theory, quantitative data analysis was not optimally covered, as freeform responses are notoriously hard to analyze. However, based in the psychology of user behavior, I hypothesized that the vast majority of users would not edit these at all , a minority would append context, and an even tinier minority would actually edit the responses. This meant we could analyze them via simple string matching and only lose a few false negatives. Mockup of the quick context idea. I was very proud of myself: I had managed to design a solution that satisfied all constraints, a feat that initially seemed impossible! Not to mention this design gently guided users towards using the comment field, which could motivate them to add even more context. Yet, when I presented my mocks to the team, engineering hated it with a passion. The lead engineer (who was also the project founder) found the idea of turning a structured interaction into unstructured data deeply unsettling . So much it motivated him to implement a whole backend to store these followups properly, something I had initially thought was out of the question. So now what? Back to the drawing board, but with one constraint lifted! Ideas 2 &amp; 3: Followups and sentiment radios Mockups of intermediate ideas. Left: Followups (by lead engineer) Right: Sentiment radios (by Google PM) This new backend came with a UI proposal that raised red flags for both me and the Google PM I was collaborating with (one of the surveys core stakeholders, but not the main one). Even seeing the followup UI required an extra click, so it was guaranteed to have a low response rate. It would have been better than the 0.9% of the comment field (clicking is easier than typing!), but still pretty low (I would estimate two clicks and a popover was a steep price to pay. Two clicks and a popover Perceived friction is not just about the number of clicks and keystrokes, but also about the number of context switches . The bigger the UI shift, the more cognitive overhead it adds, as the action is perceived as more substantial, even when in terms of user interaction it involves exactly the same number of steps (using simplistic models like KLM ). This is the reason why it feels smoother to have a text field that expands when you click on it, rather than a button that makes a text field appear: an existing control changing form is perceived as a smaller UI shift than one control disappearing and another appearing. Another idea came from the Google PM: sentiment radios . It was an attempt to simplify the interaction by framing it as a two step process: first experience, then sentiment, through radio buttons that slid down once a main answer was selected. However, I was very concerned that such a major UI shift after every single answer would quickly become overwhelming over the course of the survey. Idea 4: Context chips Back to the drawing board, I asked myself: if I had infinite engineering resources, what UI would I design? The biggest challenge was reducing friction. All ideas so far had required at least one extra (optional) click to select sentiment. Could we do better? What if users could select both experience and sentiment with a single click? Guided by this, I designed a UI where selecting sentiment is done via context chips which are actually part of the answer , so clicking them also selects the answer they are accompanying, allowing users to express an answer across both variables with a single click, or just select the answer itself to express no sentiment. To reduce visual clutter, these only faded in on hover. Additionally, clicking on the selected chip a second time would deselect it, fixing a longstanding UX issue with radio buttons [3] . Over the course of designing this, I became so convinced it was the right solution, that I implemented a high fidelity prototype myself, complete with code that could be easily adapted to the infrastructure used by the survey app. The context chips prototype on desktop and mobile. There were so many things I loved about this design, even beyond the core idea of answering both variables with a single click. There were no layout shifts, the followups were in close proximity to the main answer, and the styling of the chips helped build a visual association to reduce friction even more as you go. I was not a huge fan of the mobile version, but I couldnt think of a much better way to adapt this UI to mobile. Early alternative concept that supported followups. This was deemed too complicated and was abandoned early on. Reception of context chips was not what I had hoped at first. I had expected pushback due to the engineering effort needed, but folks also had other concerns: that users would find things appearing on hover distracting and feel ambushed, that the UI was too weird, that users would not discover the 1-click interaction and use it as a two-step process anyway, and that response rate would be low because these chips were not visible upfront. Mini-feature questions: Context Chips + Checkboxes? Around the same time as designing context chips, I had a relevant realization: we dont actually need to know both awareness and usage for all features . For old, widely supported features, awareness doesnt matter, because even when its low, it has plateaued. And for features that are so new they have not yet been implemented in browsers, usage is largely meaningless. For these cases, each feature only has two states, and thus experience can be expressed with a checkbox! This would allow us to combine questions about multiple features in one, and we could still use context chips, albeit a little differently: The mini features prototype on desktop and mobile. While these could be used for questions that either discern usage or awareness, we decided to stick to the former, as there was a (valid) concern that having mini-feature questions whose checked state meant different things could be confusing and lead to errors. That way, only old, lower-priority features would be relegated to this template, and new features which tend to be higher priority for browser vendors would still get the full UI, comments and all. Instead, to improve the experience for cutting edge features, we introduced a Not implemented tag next to the Used it option. One disadvantage of the mini feature UI is that due to the way context chips work, it is not possible to select sentiment for features you have not used: once you click on a chip, it also selects the feature, as if you had clicked on its label. I guess it could be possible to click on a chip and then uncheck the feature, but that would be a very weird interaction. Idea 5: Existing 5-point question template At this point, the lead engineer dredged up a question template that had been used in other surveys to ask about the respondents experience with various types of tooling. Instead of separating experience and sentiment, it used a 5-point scale where each answer except the first answered both questions. The existing 5-point question template. The eng lead was sold: zero engineering effort! The Google PM was also sold: 100% response rate! (since it was not possible to avoid expressing sentiment for features you had heard or used). I had serious reservations. There are arguments for even numbered Likert scales (no neutral option), but these always involve scales of at least 4 points. If you force people to select between two states, positive or negative, youre simply going to get garbage data. Neutral votes get pushed into positive votes, and the data around positive sentiment becomes useless. These did not allow users to express sentiment for features they had not heard of, despite these questions often including enough info for users to know whether they were interested. I was worried that increasing the number of upfront answers to 5 would increase cognitive load too much and even scrolling distance, by 66%! A UX researcher we were working with even did a heuristic evaluation that somewhat favored the 5-point template mainly on the basis of being a more familiar UI. The odds seemed stacked against context chips, but the upcoming usability testing could still tip the scales in their favor. Usability Testing to the Rescue! Despite the lead engineer being unconvinced about the merits of context chips and being adamant that even adapting my fully functional prototype was too much work, since the prototype existed, we decided to user test it against the 5-point question and see how it compared. We ran a within-subjects usability study with 6 participants ( no, they are not too few ) recruited via social media. Half of the survey used the 5-point template, the other half context chips. The order of the conditions was randomized to avoid order effects. In addition to their actual experience, we also collected subjective feedback at the end of the survey, showing them a screenshot of each answering UI and asking how each felt. What worked well: Context Chips I have run many usability studies in the last ten years, and I have never seen results as resounding as this one. So much that we unanimously agreed to switch to context chips after the 5th participant, because the scales were so tipped in favor of context chips that nothing that happened in the last session could have tipped them the other way. The lead engineer observed some of the sessions, and this was instrumental in changing his mind. This was not a coincidence: when engineering is unconvinced that a certain UI is worth the implementation complexity, it can be a good strategy to have them observe usability testing sessions. Not only does it help prove the value to them, it also builds long-term user empathy, which makes future consensus easier. Given the unfortunate lack of HCI prioritization in Computer Science curricula, this may even be their first exposure to usability testing. All of my concerns about the 5-point template were brought up by participants on their own accord, repeatedly: All participants really liked being able to express sentiment, and were vocal about their frustration when they could not express it . All but one participant (4/5) complained about being forced into selecting a sentiment when they had no opinion . Some participants even mentioned that the 5-point template felt overwhelming. Furthermore, none of the concerns about context chips were validated: No-one found the chips appearing on hover distracting or felt ambushed. No-one struggled to understand how to use them. Everyone discovered the 1-click interaction pretty fast (typically within the first 2-3 questions). But interestingly, they still chose to use it as a two-step process for some of the questions , presumably to reduce cognitive load for difficult to answer questions by breaking down the decision into two smaller ones. The fact that this UI allowed users to make their own efficiency vs cognitive load tradeoffs was an advantage I had not even considered when designing it! Response rate was generally high when people did not select sentiment, it was because they genuinely had no opinion, not because they couldnt be bothered. What worked okay: Mini-feature Questions Mini-feature questions did successfully help cut down response time per feature by 75% , though this came at a cost: Once more, we saw that participants really wanted to express sentiment, and were frustrated when they couldnt, which was the case for features they had not used. Regardless, we agreed that the tradeoff was worth it for the low-priority questions we were planning to use mini-features for. What did not work: Context Chips on Mobile A blind spot in our testing was that we did not test the UI on mobile. Usability tests were conducted remotely via video call, so it was a lot easier to get participants to use their regular computers. Additionally, stats for previous surveys showed that mobile use was a much smaller percentage in these surveys than for the web in general (~25%), so we did not prioritize it. This was a mistake in itself: using current usage stats to inform prioritization may seem like a great idea, but it can be prone to reverse causality bias . Meaning, are people not using these surveys on mobile very much because they genuinely dont need to, or because the experience on mobile is subpar? We often see this with accessibility too: people claim that it doesnt matter because they dont have users with disabilities, but often they dont have users with disabilities because their site is not accessible! But even if 75% of users genuinely preferred to take these surveys on desktop (which is plausible, since they are long and people often do them in increments), we should at least have done a few sessions on mobile 25% is not negligible! Once responses started coming in, we realized that participants had trouble understanding what the up and down arrows meant, since on mobile these are shown without labels until selected. This would have been an easy fix had it had been caught early, e.g. thumbs up/down icons could have been used instead. This was not a huge issue, as their purpose becomes clear when selected, but it definitely adds some friction. Aftermath: Context Chips in the Wild In our usability testing, we had seen a high response rate for sentiment (% of question respondents who selected sentiment), but that is no guarantee things will play out that way post-launch as well. When participants know they are being watched they are always more willing to engage and pay a lot more attention, no matter how much you emphasize that they should act naturally when briefing them. Thats not their failing; its simply human nature. Indeed, sentiment response rates in the real-world were lower than those observed in the usability study, but still high ranging from 24% to 59% and averaging 38% (with the same median) per question , meaning that out of every ten participants that answered each question, approximately four also provided a sentiment. This was more than enough to draw conclusions. In fact, context chips were deemed such a success they were later adopted by all other State Of surveys , even at the cost of continuity with previous years. Against expectations, participants were just as likely to express sentiment for features they had never heard of , and in fact marginally more likely than for features they had simply heard of. In general response rates were pretty uniform across all experiences: Experience Sentiment response rate (average) Sentiment response rate (median) Never heard of it 37.3% 37.4% Heard of it 37.3% 36.9% Used it 39.0% 40.1% Overall 37.6% 37.9% Sentiment response rates overall and by experience (per question). As we had observed in the user study as well, participants were far more likely to express positive rather than negative sentiment (possibly a case of acquiescence bias ). Here are some interesting stats: The feature with the most negative sentiment overall across all experiences ( ) still only had 10% of respondents expressing negative sentiment for it, and still had 2.4x more positive sentiment than negative (24.7% vs 10.4%)! In contrast, the feature with the most positive sentiment ( ) got a whopping 55% of respondents expressing positive sentiment (and only 4% negative). In fact, even the feature with the least positive sentiment ( Imperative Slot Assignment ) still had way more positive sentiment (17.24%) than had negative sentiment (10%)! If we look at the ratio of positive over negative sentiment , it ranged from 64x (!) more positive than negative sentiment (45% vs 0.7% for landmark elements) to a mere 2.4x times more positive sentiment (24.7% vs 10.4% for ), and was 11x on average ( 8x median). The analysis presented in this section includes data from 20K respondents , which was most of the whole dataset (around 90%) but not all, as it was done before the survey closed. Results Visualization Presenting all this data was another challenge. When you have two variables, ideally you want to be able to group results by either . E.g. you may be more interested in the total negative sentiment for a feature, or how many people had used it, and the results visualization should support both. How do we design a results display that facilitates this? Thankfully the visualizations for State Of surveys were already very interactive so interactivity was not out of the question. I was no longer involved by then but consulted in a volunteer capacity. My main advice was to use proximity for clear visual grouping , and to use a consistent visual association for bars that represented the same bit of data, both of which they followed. This was the rendering they settled on for the results: Interactive bar chart presenting two variables at once. I think in terms of functionality this works really well. The visual design could be improved to communicate IA better and appear less busy at first glance, but given there is no dedicated designer in the team, I think they did a fantastic job. Generalizability While this UI was originally designed to collect sentiment about the selected option in a multiple choice question, I think it could be generalized to improving UX for other types of two variable questions . Generally, it can be a good fit when we have questions that collect data across two variables and: The second variable is optional and lower priority than the first The first variable is exclusive (single) choice, i.e. not checkbox questions. The core benefit of this approach is the reduction in cognitive load . It is well established that matrix questions are more overwhelming . This design allows questions to initially appear like a simple multiple choice question, and only reveal the UI for the second variable upon interaction. Additionally, while matrix questions force participants to decide on both variables at once, this design allows them to make their own tradeoff of cognitive load vs efficiency, treating the UI as single step or two step as they see fit. Another benefit of this design is that it allows for option labels to be context-dependent for the second variable, whereas a matrix limits you to a single column header. In the sentiment case, the labels varied depending on whether the feature had been used ( Positive experience / Negative experience ) or not ( Interested / Not interested ). The more such questions a survey has, the bigger the benefits if its only about a couple questions, it may not be worth the implementation complexity, though Im hoping that survey software may eventually provide this out of the box so that this is no longer a tradeoff. That said, matrix questions do have their benefits, when used appropriately, i.e. when the two requirements listed above are not met. Matrix questions have an big edge when users need to make a single selection per row , especially when this may be the same answer for multiple rows, which means they can just tick down a whole column . Context chips do not allow users to build this positional association, as they are not aligned vertically. THey are placed right after the answer text, and thus their horizontal position varies per answer. To mitigate this, it can be useful to color-code them and maintain the same color coding consistently throughout the survey so that participants can build a visual association [4] . Context chips also enforce a clear prioritization across the two variables: the question is presented as a multiple choice question across the first variable, with the chips allowing the respondent to provide optional additional context across a second variable. Matrix questions allow presenting the two variables with the same visual weight, which could be desirable in certain cases. For example, there are cases when we dont want to allow the respondent to provide an answer to the first variable without also providing an answer to the second. Lessons Learned In addition to any generalizable knowledge around survey design, I think this is also an interesting product management case study, and teaches us several lessons. Never skimp on articulating the north star UI Start any product design task by ignoring ephemeral constraints (e.g. engineering resources) and first reach consensus on what the optimal UI is, before you start applying constraints. Yes, you read that right. I want to write a whole post about the importance of north star UIs, because this is one of many cases over the course of my career where tight implementation constraints were magically lifted, either due to a change of mind, a change in the environment, or simply someones brilliant idea. Without consensus on what the north star UI is (or even a clear idea about it) you then have to go back to the drawing board when this happens. User testing is also a consensus-building tool You probably already know that usability testing is a great tool for improving user experience, but there is a second, more strategic hidden utility to it: consensus building. Ive been in way, way too many teams where UI decisions were made by hypothesizing about user behavior , which could not only be missing the mark, but also there is no way forwards for disagreements. What do you do, hypothesize harder? When there is user testing data, it is much harder to argue against it. This is especially useful in convincing engineering that a certain UI is worth the implementation complexity, and having engineers observe usability testing sessions can be an educational experience for many. For many engineers, user needs are an unknown unknown since HCI courses are elective in most CS curricula and not talked about very much in engineering resources. One of the most common comments we got after the first lecture of our usability &amp; web programming class at MIT was Wow, I had never thought about software from the users perspective before!. Believe it or not, these comments would often came from CS majors about to graduate! Heuristic evaluations are not a substitute for usability testing There are many things to like about heuristic evaluations and design reviews . They can be done by a usability expert alone and can uncover numerous issues that would have taken multiple rounds of usability testing, especially if they are also a domain expert. Fixing the low hanging fruit issues means user testing time can be spent more efficiently, uncovering less obvious problems. That are are also certain types of issues that can only be uncovered by a heuristic evaluation, such as _death by a thousand paper cuts type issues: small issues that are not a big deal on their own and are too small to observe in a user study, but add up to more friction. However, a big downside of these is that they are inherently prone to bias . They can be excellent for finding problems that may have been overlooked and are often obvious once pointed out. However (unless the number of evaluators is large), they are not a good way to decide between alternatives. Unlike Devographics, surveys are not FAs core business, so the Impact/Effort tradeoff simply wasnt there for a custom UI, at least at this point in time. I ended up going with Tally , mainly due to the flexibility of its conditional logic and its support for code injection (which among other things, allowed me to use FA icons a whopping 120 different ones!). Meaning out of the people who responded to that question about their experience with a feature, only 0.9% left a comment. A radio group with all buttons off cannot be returned to that state by user interaction. The ~9% of people with atypical color vision wont benefit, but thats okay in this case, as color is used to add an extra cue, and not an essential part of the interface.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Web Components are not Framework Components — and That’s Okay</title>
  <link>https://lea.verou.me/blog/2024/wcs-vs-frameworks/</link>
  <pubDate>Sun, 17 May 2026 03:45:13 +0200</pubDate>
  <description>Disclaimer: This post expresses my opinions, which do not necessarily reflect consensus by the whole Web Components community. A blog post by Ryan Carniato titled “Web Components Are Not the Future” has recently stirred a lot of controversy. A few other JS framework authors pitched in, expressing frustration and disillusionment around Web Components. Some Web Components folks wrote rebuttals , while others repeatedly tried to get to the bottom of the issues, so they could be addressed in the future. When you are on the receiving end of such an onslaught, the initial reaction is to feel threatened and become defensive. However, these kinds of posts can often end up shaking things up and pushing a technology forwards in the end. I have some personal experience: after I published my 2020 post titled “The failed promise of Web Components” which also made the rounds at the time, I was approached by a bunch of folks (Justin Fagnani, Gray Norton, Kevin Schaaf) about teaming up to fix the issues I described. The result of these brainstorming sessions was the Web Components CG which now has a life of its own and has become a vibrant Web Components community that has helped move several specs of strategic importance forwards. As someone who deeply cares about Web Components, my initial response was also to push back . I was reminded of how many times I have seen this pattern before. It is common for new web platform features to face pushback and resistance for many years ; we tend to compare them to current userland practices, and their ergonomics often fare poorly at the start. Especially when there is no immediately apparent 80/20 solution , making things possible tends to precede making them easy . Web platform features operate under a whole different set of requirements and constraints: They need to last decades , not just until the next major release. They need to not only cater to the current version of the web platform, but anticipate its future evolution and be compatible with it. They need to be backwards compatible with the web as it was 20 years ago. They need to be compatible with a slew of accessibility and internationalization needs that userland libraries often ignore at first. They are developed in a distributed way, by people across many different organizations, with different needs and priorities. Usually, the result is more robust, but takes a lot longer . That’s why I’ve often said that web standards are “product work on hard mode” — they include most components of regular product work (collecting user needs, designing ergonomic solutions, balancing impact over effort, leading without authority, etc.), but with the added constraints of a distributed, long-term, and compatibility-focused development process that would make most PMs pull their hair out in frustration and run screaming. I’m old enough to remember this pattern playing out with CSS itself : huge pushback when it was introduced in the mid 90s. It was clunky for layout and had terrible browser support — “why fix something that wasn’t broken?” folks cried. Embarrassingly, I was one of the last holdouts: I liked CSS for styling, but was among the last to switch to floats for layout — tables were just so much more ergonomic! The majority resistance lasted until the mid &#39;00s when it went from “this will never work” to “this was clearly the solution all along” almost overnight. And the rest, as they say, is history. 🙂 But the more I thought about this, the more I realized that — as often happens in these kinds of heated debates — the truth lies somewhere in the middle . Having used both several frameworks, and several web components, and having authored both web components (most of them experimental and unreleased) and even one framework over the course of my PhD , both sides do have some valid points. Frankly, if framework authors were sold the idea that web components would be a compile target for their frameworks, and then got today’s WC APIs, I understand their frustration. Worse yet, if every time they tried to explain that this sucks as a compile target they were told “no you just don’t get it”, heck I’d feel gaslit too! Web Components are still far from being a good compile target for all framework components, but that is not a prerequisite for them being useful . They simply solve different problems. Let me explain. Not all component use cases are the same. I think the crux of this debate is that the community has mixed two very different categories of use cases , largely because frameworks do not differentiate between them; “component” has become the hammer with which to hammer every nail. Conceptually, there are two core categories of components: Generalizable elements that extend what HTML can do and can be used in the same way as native HTML elements across a wide range of diverse projects. Things like tabs, rating widgets, comboboxes, dialogs, menus, charts, etc. Another way to think about these is “if resources were infinite, elements that would make sense as native HTML elements” . Reactive templating : UI modules that have project-specific purposes and are not required to make sense in a different project. For example, a font foundry may have a component to demo a font family with child components to demo a font family style, but the uses of such components outside the very niche font foundry use case are very limited. Of course, it’s a spectrum; few things in life fit neatly in completely distinct categories. For example an component may be somewhat niche, but would be useful across any site that wants to demo HTML snippets (e.g. a web components library, a documentation site around web technologies, a book teaching how to implement UI patterns, etc.). But the fact that it’s a spectrum does not mean the distinction does not exist. WCs primarily benefit the use case of generalizable elements that extend HTML, and are still painful to use for reactive templating. Fundamentally, it’s about the ratio of potential consumers to authors . The huge benefit of Web Components is interoperability : you write it once, it works forever, and you can use it with any framework (or none at all). It makes no sense to fragment efforts to reimplement e.g. tabs or a rating widget separately for each framework-specific silo, it is simply duplicated busywork. As a personal anecdote, a few weeks ago I found this amazing JSON viewer component , but I couldn’t use it because I don’t use React (I prefer Vue and Svelte). To this day, I have not found anything comparable for Vue, Svelte, or vanilla JS. This kind of fragmentation is sadly an everyday occurrence for most devs. But when it comes to project-specific components, the importance of interop decreases: you typically pick a framework and stick to it across your entire project. Reusing project-specific components across different projects is not a major need, so the value proposition of interop is smaller. Additionally, the ergonomics of consuming vs authoring web components are vastly different. Consuming WCs is already pretty smooth, and the APIs are largely there to demystify most of the magic of built-in elements and expose it to web components (with a few small gaps being actively plugged as we speak). However, authoring web components is a whole different story. Especially without a library like Lit , authoring WCs is still painful, tedious, and riddled with footguns. For generalizable elements, this is an acceptable tradeoff, as their potential consumers are a much larger group than their authors. As an extreme example of this, nobody complains about the ergonomics of implementing native elements in browsers using C++ or Rust. But when using components as a templating mechanism, authoring ergonomics are crucial , since the overlap between consumers and authors is nearly 100%. This was the motivation behind this Twitter poll I posted a while back . I asked if people mostly consumed web components, used WCs that others have made, or both. Note that many people who use WCs are not aware of it, so the motivation was not to gauge adoption, but to see if the community has caught on to this distinction between use cases. The fact that &gt; 80% of people who knowingly use web components are also web components authors is indicative of the problem. WCs are meant to empower folks to do more, not to be consumed by expert web developers who can also write them. Until this number becomes a lot smaller, Web Components will not have reached their full potential. This was one of the reasons I joined the Web Awesome project ; I think that is the right direction for WCs: encapsulating complexity into beautiful, generalizable, customizable elements that give people superpowers by extending what HTML can do: they can be used by developers to author gorgeous UIs, designers to do more without having to learn JS, or even hobbyists that struggle with both (since HTML is the most approachable web platform language). So IMO making it about frameworks vs web components is a false dichotomy. Frameworks already use native HTML elements in their components. Web components extend what native elements can do, and thus make crafting project-specific components easier across all frameworks (as well as no frameworks). I wonder if this narrative could resonate across both sides and reconcile them. Basically “yes, we may still need frameworks for nontrivial apps, but web components make their job easier” rather than pitting them against each other in a pointless comparison where everyone loses. We will certainly eventually get to the point where web components are more ergonomic to author, but we first need to get the low-level foundations right. At this point the focus is still on making things possible rather than making them easy . The last remaining pieces of the puzzle are things like Reference Target for cross-root ARIA or ElementInternals.type to allow custom elements to become popover targets or submit buttons, both of which saw a lot of progress at W3C TPAC last week. After that, perhaps eventually web components will even become viable for reactive templating use cases; things like the open-stylable shadow roots proposal, declarative elements, or DOM Parts are some early beginnings in that direction, and declarative shadow DOM paved the way for SSR (among other things). Then, and only then, they may make sense as a compile target for frameworks. However, that is quite far off. And even if we get there, frameworks would still be useful for complex use cases, as they do a lot more than let you use and define components. Components are not even the best reuse mechanism for every project-specific use case — e.g. for list rendering, components are overkill compared to something like v-for . And by then frameworks will be doing even more. It is by definition that frameworks are always a step ahead of the web platform, not a failing of the web platform. As Cory said , “Frameworks are a testbed for new ideas that may or may not work out.” . The bottom line is, web components reduce the number of use cases where we need to reach for a framework, but complex large applications will likely still benefit from one. So how about we conclude that frameworks are useful, web components are also useful, stop fighting and go make awesome sh!t using whatever tools we find most productive? Thanks to Michael Warren, Nolan Lawson, Cory LaViska, Steve Orvell, and others for their feedback on earlier drafts of this post.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Web Components are not Framework Components and Thats Okay</title>
  <link>https://lea.verou.me/blog/2024/wcs-vs-frameworks/</link>
  <pubDate>Sun, 17 May 2026 03:45:13 +0200</pubDate>
  <description>Disclaimer: This post expresses my opinions, which do not necessarily reflect consensus by the whole Web Components community. A blog post by Ryan Carniato titled Web Components Are Not the Future has recently stirred a lot of controversy. A few other JS framework authors pitched in, expressing frustration and disillusionment around Web Components. Some Web Components folks wrote rebuttals , while others repeatedly tried to get to the bottom of the issues, so they could be addressed in the future. When you are on the receiving end of such an onslaught, the initial reaction is to feel threatened and become defensive. However, these kinds of posts can often end up shaking things up and pushing a technology forwards in the end. I have some personal experience: after I published my 2020 post titled The failed promise of Web Components which also made the rounds at the time, I was approached by a bunch of folks (Justin Fagnani, Gray Norton, Kevin Schaaf) about teaming up to fix the issues I described. The result of these brainstorming sessions was the Web Components CG which now has a life of its own and has become a vibrant Web Components community that has helped move several specs of strategic importance forwards. As someone who deeply cares about Web Components, my initial response was also to push back . I was reminded of how many times I have seen this pattern before. It is common for new web platform features to face pushback and resistance for many years ; we tend to compare them to current userland practices, and their ergonomics often fare poorly at the start. Especially when there is no immediately apparent 80/20 solution , making things possible tends to precede making them easy . Web platform features operate under a whole different set of requirements and constraints: They need to last decades , not just until the next major release. They need to not only cater to the current version of the web platform, but anticipate its future evolution and be compatible with it. They need to be backwards compatible with the web as it was 20 years ago. They need to be compatible with a slew of accessibility and internationalization needs that userland libraries often ignore at first. They are developed in a distributed way, by people across many different organizations, with different needs and priorities. Usually, the result is more robust, but takes a lot longer . Thats why Ive often said that web standards are product work on hard mode they include most components of regular product work (collecting user needs, designing ergonomic solutions, balancing impact over effort, leading without authority, etc.), but with the added constraints of a distributed, long-term, and compatibility-focused development process that would make most PMs pull their hair out in frustration and run screaming. Im old enough to remember this pattern playing out with CSS itself : huge pushback when it was introduced in the mid 90s. It was clunky for layout and had terrible browser support why fix something that wasnt broken? folks cried. Embarrassingly, I was one of the last holdouts: I liked CSS for styling, but was among the last to switch to floats for layout tables were just so much more ergonomic! The majority resistance lasted until the mid &#39;00s when it went from this will never work to this was clearly the solution all along almost overnight. And the rest, as they say, is history. But the more I thought about this, the more I realized that as often happens in these kinds of heated debates the truth lies somewhere in the middle . Having used both several frameworks, and several web components, and having authored both web components (most of them experimental and unreleased) and even one framework over the course of my PhD , both sides do have some valid points. Frankly, if framework authors were sold the idea that web components would be a compile target for their frameworks, and then got todays WC APIs, I understand their frustration. Worse yet, if every time they tried to explain that this sucks as a compile target they were told no you just dont get it, heck Id feel gaslit too! Web Components are still far from being a good compile target for all framework components, but that is not a prerequisite for them being useful . They simply solve different problems. Let me explain. Not all component use cases are the same. I think the crux of this debate is that the community has mixed two very different categories of use cases , largely because frameworks do not differentiate between them; component has become the hammer with which to hammer every nail. Conceptually, there are two core categories of components: Generalizable elements that extend what HTML can do and can be used in the same way as native HTML elements across a wide range of diverse projects. Things like tabs, rating widgets, comboboxes, dialogs, menus, charts, etc. Another way to think about these is if resources were infinite, elements that would make sense as native HTML elements . Reactive templating : UI modules that have project-specific purposes and are not required to make sense in a different project. For example, a font foundry may have a component to demo a font family with child components to demo a font family style, but the uses of such components outside the very niche font foundry use case are very limited. Of course, its a spectrum; few things in life fit neatly in completely distinct categories. For example an component may be somewhat niche, but would be useful across any site that wants to demo HTML snippets (e.g. a web components library, a documentation site around web technologies, a book teaching how to implement UI patterns, etc.). But the fact that its a spectrum does not mean the distinction does not exist. WCs primarily benefit the use case of generalizable elements that extend HTML, and are still painful to use for reactive templating. Fundamentally, its about the ratio of potential consumers to authors . The huge benefit of Web Components is interoperability : you write it once, it works forever, and you can use it with any framework (or none at all). It makes no sense to fragment efforts to reimplement e.g. tabs or a rating widget separately for each framework-specific silo, it is simply duplicated busywork. As a personal anecdote, a few weeks ago I found this amazing JSON viewer component , but I couldnt use it because I dont use React (I prefer Vue and Svelte). To this day, I have not found anything comparable for Vue, Svelte, or vanilla JS. This kind of fragmentation is sadly an everyday occurrence for most devs. But when it comes to project-specific components, the importance of interop decreases: you typically pick a framework and stick to it across your entire project. Reusing project-specific components across different projects is not a major need, so the value proposition of interop is smaller. Additionally, the ergonomics of consuming vs authoring web components are vastly different. Consuming WCs is already pretty smooth, and the APIs are largely there to demystify most of the magic of built-in elements and expose it to web components (with a few small gaps being actively plugged as we speak). However, authoring web components is a whole different story. Especially without a library like Lit , authoring WCs is still painful, tedious, and riddled with footguns. For generalizable elements, this is an acceptable tradeoff, as their potential consumers are a much larger group than their authors. As an extreme example of this, nobody complains about the ergonomics of implementing native elements in browsers using C++ or Rust. But when using components as a templating mechanism, authoring ergonomics are crucial , since the overlap between consumers and authors is nearly 100%. This was the motivation behind this Twitter poll I posted a while back . I asked if people mostly consumed web components, used WCs that others have made, or both. Note that many people who use WCs are not aware of it, so the motivation was not to gauge adoption, but to see if the community has caught on to this distinction between use cases. The fact that &gt; 80% of people who knowingly use web components are also web components authors is indicative of the problem. WCs are meant to empower folks to do more, not to be consumed by expert web developers who can also write them. Until this number becomes a lot smaller, Web Components will not have reached their full potential. This was one of the reasons I joined the Web Awesome project ; I think that is the right direction for WCs: encapsulating complexity into beautiful, generalizable, customizable elements that give people superpowers by extending what HTML can do: they can be used by developers to author gorgeous UIs, designers to do more without having to learn JS, or even hobbyists that struggle with both (since HTML is the most approachable web platform language). So IMO making it about frameworks vs web components is a false dichotomy. Frameworks already use native HTML elements in their components. Web components extend what native elements can do, and thus make crafting project-specific components easier across all frameworks (as well as no frameworks). I wonder if this narrative could resonate across both sides and reconcile them. Basically yes, we may still need frameworks for nontrivial apps, but web components make their job easier rather than pitting them against each other in a pointless comparison where everyone loses. We will certainly eventually get to the point where web components are more ergonomic to author, but we first need to get the low-level foundations right. At this point the focus is still on making things possible rather than making them easy . The last remaining pieces of the puzzle are things like Reference Target for cross-root ARIA or ElementInternals.type to allow custom elements to become popover targets or submit buttons, both of which saw a lot of progress at W3C TPAC last week. After that, perhaps eventually web components will even become viable for reactive templating use cases; things like the open-stylable shadow roots proposal, declarative elements, or DOM Parts are some early beginnings in that direction, and declarative shadow DOM paved the way for SSR (among other things). Then, and only then, they may make sense as a compile target for frameworks. However, that is quite far off. And even if we get there, frameworks would still be useful for complex use cases, as they do a lot more than let you use and define components. Components are not even the best reuse mechanism for every project-specific use case e.g. for list rendering, components are overkill compared to something like v-for . And by then frameworks will be doing even more. It is by definition that frameworks are always a step ahead of the web platform, not a failing of the web platform. As Cory said , Frameworks are a testbed for new ideas that may or may not work out. . The bottom line is, web components reduce the number of use cases where we need to reach for a framework, but complex large applications will likely still benefit from one. So how about we conclude that frameworks are useful, web components are also useful, stop fighting and go make awesome sh!t using whatever tools we find most productive? Thanks to Michael Warren, Nolan Lawson, Cory LaViska, Steve Orvell, and others for their feedback on earlier drafts of this post.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Making the Web more Awesome — for everyone</title>
  <link>https://lea.verou.me/blog/2024/awesome/</link>
  <pubDate>Sun, 17 May 2026 03:45:11 +0200</pubDate>
  <description>Folks, I have some exciting news to share. 🤩 Today I start a new chapter in my career. After a decade at MIT , teaching and doing research at the intersection of usability and programming language design, I wrapped up my PhD two weeks ago (yes, I’m a Dr now ! And damn right I will — once it actually sinks in) and today I start my new role as Product Lead at Font Awesome . I will be evaluating user needs and improving product design and usability across all company products, with an emphasis on Web Awesome , the product we are launching early next year to revolutionize how Web UIs are built by using web components and CSS in ways you’ve never seen before. Beyond improving the products themselves (all of which include extensive free &amp; open source versions ), part of my role will utilize my web standards experience to collect web platform pain points from across the company and translating them to new and existing web standards proposals. Yes, I know, it’s a match made in heaven. 😍 There is even a small chance I may have been the first to create an icon font for use in a web UI via @font-face , which would make it even more wonderfully poetic that I’m joining the company that has become synonymous with icon fonts on the Web. However, it was not my MIT PhD that led me to this role, but an email from Dave Gandy (creator &amp; CEO of Font Awesome) about Color.js , that turned into hours of chats, and eventually a job offer for a role I could not refuse, one that was literally molded around my skills and interests. The role is not the only reason I’m excited to join Font Awesome, though. The company itself is a breath of fresh air: open source friendly (as Dave says, “literally the only reason we have Pro versions is that we need to sustain this somehow” 😅), already profitable (= no scrambling to meet VC demands by cramming AI features nobody wants into our products), fully remote, huge emphasis on work-life balance, and an interview process that did not feel like an interview — or even a process. In fact, they did not even want to look at my resume (despite my efforts 🤣). It is telling that in their 10 years of existence, not a single person has left the company, and they have never had to let anyone go . Moreover, it bridges the best of both worlds: despite having existed for a decade, branching out to new products [1] and markets gives it a startup-like energy and excitement. I had been extremely selective in the job opportunities I pursued, so it took a while to find the perfect role. Having ADHD (diagnosed only last year — I want to write a blog post about that too at some point), I knew it was crucial to find a job I could be passionate about: ADHD folks are unstoppable machines in jobs they love (I have literally built my career by directing my hyperfocus to things that are actually productive), but struggle way more than neurotypicals in jobs they hate. It took a while, but when I started talking with Dave, I knew Font Awesome was it . I’m still reeling from the mad rush of spending the past couple of months averaging 100-hour weeks to wrap up my PhD before starting, but I couldn’t be more excited about this new chapter. I’m hoping to write a series of blog posts in the coming weeks about about my journey to this point. Things like: How I decided that academia was not for me — but persisted to the finish line anyway because I’m stubborn AF 😅 How I realized that product work is my real calling, not software engineering per se (as much as I love both) How I used web technologies instead of LaTeX to write my PhD thesis (and print it to PDF for submission), with 11ty plus several open source plugins, many of which I wrote, an ecosystem I hope to one day free more people from the tyranny of LaTeX (which was amazing in the 70s, but its ergonomics are now showing their age). But for now, I just wanted to share the news, and go off to make the web more awesome — for everyone . 🚀 My excitement grew even stronger when a week before my start date, I learned that 11ty (and its creator, Zach Leatherman) had also joined Font Awesome — I think at this point every tool I use regularly is officially Awesome 😅. Yes, this site is built on 11ty as well. And even my PhD thesis ! ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Making the Web more Awesome for everyone</title>
  <link>https://lea.verou.me/blog/2024/awesome/</link>
  <pubDate>Sun, 17 May 2026 03:45:11 +0200</pubDate>
  <description>Folks, I have some exciting news to share. Today I start a new chapter in my career. After a decade at MIT , teaching and doing research at the intersection of usability and programming language design, I wrapped up my PhD two weeks ago (yes, Im a Dr now ! And damn right I will once it actually sinks in) and today I start my new role as Product Lead at Font Awesome . I will be evaluating user needs and improving product design and usability across all company products, with an emphasis on Web Awesome , the product we are launching early next year to revolutionize how Web UIs are built by using web components and CSS in ways youve never seen before. Beyond improving the products themselves (all of which include extensive free &amp; open source versions ), part of my role will utilize my web standards experience to collect web platform pain points from across the company and translating them to new and existing web standards proposals. Yes, I know, its a match made in heaven. There is even a small chance I may have been the first to create an icon font for use in a web UI via @font-face , which would make it even more wonderfully poetic that Im joining the company that has become synonymous with icon fonts on the Web. However, it was not my MIT PhD that led me to this role, but an email from Dave Gandy (creator &amp; CEO of Font Awesome) about Color.js , that turned into hours of chats, and eventually a job offer for a role I could not refuse, one that was literally molded around my skills and interests. The role is not the only reason Im excited to join Font Awesome, though. The company itself is a breath of fresh air: open source friendly (as Dave says, literally the only reason we have Pro versions is that we need to sustain this somehow ), already profitable (= no scrambling to meet VC demands by cramming AI features nobody wants into our products), fully remote, huge emphasis on work-life balance, and an interview process that did not feel like an interview or even a process. In fact, they did not even want to look at my resume (despite my efforts ). It is telling that in their 10 years of existence, not a single person has left the company, and they have never had to let anyone go . Moreover, it bridges the best of both worlds: despite having existed for a decade, branching out to new products [1] and markets gives it a startup-like energy and excitement. I had been extremely selective in the job opportunities I pursued, so it took a while to find the perfect role. Having ADHD (diagnosed only last year I want to write a blog post about that too at some point), I knew it was crucial to find a job I could be passionate about: ADHD folks are unstoppable machines in jobs they love (I have literally built my career by directing my hyperfocus to things that are actually productive), but struggle way more than neurotypicals in jobs they hate. It took a while, but when I started talking with Dave, I knew Font Awesome was it . Im still reeling from the mad rush of spending the past couple of months averaging 100-hour weeks to wrap up my PhD before starting, but I couldnt be more excited about this new chapter. Im hoping to write a series of blog posts in the coming weeks about about my journey to this point. Things like: How I decided that academia was not for me but persisted to the finish line anyway because Im stubborn AF How I realized that product work is my real calling, not software engineering per se (as much as I love both) How I used web technologies instead of LaTeX to write my PhD thesis (and print it to PDF for submission), with 11ty plus several open source plugins, many of which I wrote, an ecosystem I hope to one day free more people from the tyranny of LaTeX (which was amazing in the 70s, but its ergonomics are now showing their age). But for now, I just wanted to share the news, and go off to make the web more awesome for everyone . My excitement grew even stronger when a week before my start date, I learned that 11ty (and its creator, Zach Leatherman) had also joined Font Awesome I think at this point every tool I use regularly is officially Awesome . Yes, this site is built on 11ty as well. And even my PhD thesis !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Forget “show, don’t tell”. Engage, don’t show!</title>
  <link>https://lea.verou.me/blog/2024/engage-dont-show/</link>
  <pubDate>Sun, 17 May 2026 03:45:10 +0200</pubDate>
  <description>A few days ago, I gave a very well received talk about API design at dotJS titled “API Design is UI Design” [1] . One of the points I made was that good UIs (and thus, good APIs) have a smooth UI complexity to Use case complexity curve. This means that incremental user effort results in incremental value ; at no point going just a little bit further requires a disproportionately big chunk of upfront work [2] . Observing my daughter’s second ever piano lesson today made me realize how this principle extends to education and most other kinds of knowledge transfer (writing, presentations, etc.). Her (generally wonderful) teacher spent 40 minutes teaching her notation, longer and shorter notes, practicing drawing clefs, etc. Despite his playful demeanor and her general interest in the subject, she was clearly distracted by the end of it. It’s easy to dismiss this as a 5 year old’s short attention span, but I could tell what was going on: she did not understand why these were useful, nor how they connect to her end goal, which is to play music . To her, notation was just an assortment of arbitrary symbols and lines, some of which she got to draw. Note lengths were just isolated sounds with no connection to actual music. Once I connected note lengths to songs she has sung with me and suggested they try something more hands on, her focus returned instantly . I mentioned to her teacher that kids that age struggle to learn theory for that long without practicing it. He agreed, and said that many kids are motivated to get through the theory because they’ve heard their teacher play nice music and want to get there too. The thing is… sure, that’s motivating. But as far as motivations go, it’s pretty weak. Humans are animals, and animals don’t play the long game, or they would die . We are programmed to optimize for quick, easy dopamine hits . The farther into the future the reward, the more discipline it takes to stay motivated and put effort towards it. This applies to all humans, but even more to kids and ADHD folks [3] . That’s why it’s so hard for teenagers to study so they can improve their career opportunities and why you struggle to eat well and exercise so you can be healthy and fit. So how does this apply to knowledge transfer? It highlights how essential it is for students to a) understand why what they are learning is useful and b) put it in practice ASAP . You can’t retain information that is not connected to an obvious purpose [4] — your brain will treat it as noise and discard it. The thing is, the more expert you are on a topic, the harder these are to do when conveying knowledge to others. I get it. I’ve done it too. First, the purpose of concepts feels obvious to you, so it’s easy to forget to articulate it. You overestimate the student’s interest in the minutiae of your field of expertise. Worse yet, so many concepts feel essential that you are convinced nothing is possible without learning them (or even if it is, it’s just not The Right Way™ ). Looking back on some of my earlier CSS lectures, I’ve definitely been guilty of this. As educators, it’s very tempting to say “they can’t possibly practice before understanding X, Y, Z, they must learn it properly ”. Except …they won’t. At best they will skim over it until it’s time to practice, which is when the actual learning happens. At worst, they will give up. You will get much better retention if you frequently get them to see the value of their incremental imperfect knowledge than by expecting a big upfront attention investment before they can reap the rewards. There is another reason to avoid long chunks of upfront theory: humans are goal oriented. When we have a goal, we are far more motivated to absorb information that helps us towards that goal. The value of the new information is clear, we are practicing it immediately, and it is already connected to other things we know. This means that explaining things in context as they become relevant is infinitely better for retention and comprehension than explaining them upfront. When knowledge is a solution to a problem the student is already facing, its purpose is clear, and it has already been filtered by relevance. Furthermore, learning it provides immediate value and instant gratification: it explains what they are experiencing or helps them achieve an immediate goal. Even if you don’t teach , this still applies to you. I would go as far as to say it applies to every kind of knowledge transfer: teaching, writing documentation, giving talks, even just explaining a tricky concept to your colleague over lunch break. Literally any activity that involves interfacing with other humans benefits from empathy and understanding of human nature and its limitations. To sum up: Always explain why something is useful. Yes, even when it’s obvious to you. Minimize the amount of knowledge you convey before the next opportunity to practice it. For non-interactive forms of knowledge transfer (e.g. a book), this may mean showing an example, whereas for interactive ones it could mean giving the student a small exercise or task. Even in non-interactive forms, you can ask questions — the receiver will still pause and think what they would answer even if you are not there to hear it. Prefer explaining in context rather than explaining upfront. “Show, don’t tell” ? Nah. More like “Engage, don’t show” . (In the interest of time, I’m posting this without citations to avoid going down the rabbit hole of trying to find the best source for each claim, especially since I believe they’re pretty uncontroversial in the psychology / cognitive science literature. That said, I’d love to add references if you have good ones!) The video is now available on YouTube: API Design is UI Design ↩︎ When it does, this is called a usability cliff. ↩︎ I often say that optimizing UX for people with ADHD actually creates delightful experiences even for those with neurotypical attention spans. Just because you could focus your attention on something you don’t find interesting doesn’t mean you enjoy it. Yet another case of accessibility helping everyone! ↩︎ I mean, you can memorize anything if you try hard enough, but by optimizing teaching we can keep rote memorization down to the bare minimum. ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Forget show, dont tell. Engage, dont show!</title>
  <link>https://lea.verou.me/blog/2024/engage-dont-show/</link>
  <pubDate>Sun, 17 May 2026 03:45:10 +0200</pubDate>
  <description>A few days ago, I gave a very well received talk about API design at dotJS titled API Design is UI Design [1] . One of the points I made was that good UIs (and thus, good APIs) have a smooth UI complexity to Use case complexity curve. This means that incremental user effort results in incremental value ; at no point going just a little bit further requires a disproportionately big chunk of upfront work [2] . Observing my daughters second ever piano lesson today made me realize how this principle extends to education and most other kinds of knowledge transfer (writing, presentations, etc.). Her (generally wonderful) teacher spent 40 minutes teaching her notation, longer and shorter notes, practicing drawing clefs, etc. Despite his playful demeanor and her general interest in the subject, she was clearly distracted by the end of it. Its easy to dismiss this as a 5 year olds short attention span, but I could tell what was going on: she did not understand why these were useful, nor how they connect to her end goal, which is to play music . To her, notation was just an assortment of arbitrary symbols and lines, some of which she got to draw. Note lengths were just isolated sounds with no connection to actual music. Once I connected note lengths to songs she has sung with me and suggested they try something more hands on, her focus returned instantly . I mentioned to her teacher that kids that age struggle to learn theory for that long without practicing it. He agreed, and said that many kids are motivated to get through the theory because theyve heard their teacher play nice music and want to get there too. The thing is sure, thats motivating. But as far as motivations go, its pretty weak. Humans are animals, and animals dont play the long game, or they would die . We are programmed to optimize for quick, easy dopamine hits . The farther into the future the reward, the more discipline it takes to stay motivated and put effort towards it. This applies to all humans, but even more to kids and ADHD folks [3] . Thats why its so hard for teenagers to study so they can improve their career opportunities and why you struggle to eat well and exercise so you can be healthy and fit. So how does this apply to knowledge transfer? It highlights how essential it is for students to a) understand why what they are learning is useful and b) put it in practice ASAP . You cant retain information that is not connected to an obvious purpose [4] your brain will treat it as noise and discard it. The thing is, the more expert you are on a topic, the harder these are to do when conveying knowledge to others. I get it. Ive done it too. First, the purpose of concepts feels obvious to you, so its easy to forget to articulate it. You overestimate the students interest in the minutiae of your field of expertise. Worse yet, so many concepts feel essential that you are convinced nothing is possible without learning them (or even if it is, its just not The Right Way ). Looking back on some of my earlier CSS lectures, Ive definitely been guilty of this. As educators, its very tempting to say they cant possibly practice before understanding X, Y, Z, they must learn it properly . Except they wont. At best they will skim over it until its time to practice, which is when the actual learning happens. At worst, they will give up. You will get much better retention if you frequently get them to see the value of their incremental imperfect knowledge than by expecting a big upfront attention investment before they can reap the rewards. There is another reason to avoid long chunks of upfront theory: humans are goal oriented. When we have a goal, we are far more motivated to absorb information that helps us towards that goal. The value of the new information is clear, we are practicing it immediately, and it is already connected to other things we know. This means that explaining things in context as they become relevant is infinitely better for retention and comprehension than explaining them upfront. When knowledge is a solution to a problem the student is already facing, its purpose is clear, and it has already been filtered by relevance. Furthermore, learning it provides immediate value and instant gratification: it explains what they are experiencing or helps them achieve an immediate goal. Even if you dont teach , this still applies to you. I would go as far as to say it applies to every kind of knowledge transfer: teaching, writing documentation, giving talks, even just explaining a tricky concept to your colleague over lunch break. Literally any activity that involves interfacing with other humans benefits from empathy and understanding of human nature and its limitations. To sum up: Always explain why something is useful. Yes, even when its obvious to you. Minimize the amount of knowledge you convey before the next opportunity to practice it. For non-interactive forms of knowledge transfer (e.g. a book), this may mean showing an example, whereas for interactive ones it could mean giving the student a small exercise or task. Even in non-interactive forms, you can ask questions the receiver will still pause and think what they would answer even if you are not there to hear it. Prefer explaining in context rather than explaining upfront. Show, dont tell ? Nah. More like Engage, dont show . (In the interest of time, Im posting this without citations to avoid going down the rabbit hole of trying to find the best source for each claim, especially since I believe theyre pretty uncontroversial in the psychology / cognitive science literature. That said, Id love to add references if you have good ones!) The video is now available on YouTube: API Design is UI Design When it does, this is called a usability cliff. I often say that optimizing UX for people with ADHD actually creates delightful experiences even for those with neurotypical attention spans. Just because you could focus your attention on something you dont find interesting doesnt mean you enjoy it. Yet another case of accessibility helping everyone! I mean, you can memorize anything if you try hard enough, but by optimizing teaching we can keep rote memorization down to the bare minimum.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Inline conditionals in CSS, now?</title>
  <link>https://lea.verou.me/blog/2024/css-conditionals-now/</link>
  <pubDate>Sun, 17 May 2026 03:45:09 +0200</pubDate>
  <description>The CSS WG resolved to add if() to CSS, but that won’t be in browsers for a while. What are our options in the meantime? A couple days ago, I posted about the recent CSS WG resolution to add an if() function to CSS . Great as it may be, this is still a long way off, two years if everything goes super smoothly, more if not. So what can you do when you need conditionals right now ? You may be pleased to find that you’re not completely out of luck. There is a series of brilliant, horrible hacks that enable you to expose the kinds of higher level custom properties that conditionals typically enable. Using hacks in production?! The instinctive reaction many developers have when seeing hacks like these is “Nice hack, but can’t possibly ever use this in production”. This sounds reasonable on the surface (keeping the codebase maintainable is a worthy goal!) but when examined deeply, it reflects the wrong order of priorities, prioritizing developer convenience over user convenience. The TAG maintains a Web Platform Design Principles document [1] that everyone designing APIs for the web platform is supposed to read and follow. I’m a strong believer in having published Design Principles, for any product [2] . They help stay on track, and remember what the big picture vision is, which is otherwise easy to lose sight of in the day to day minutiae. One of the core principles in the document is the Priority of Constituencies . The core of it is: User needs come before the needs of web page authors, which come before the needs of user agent implementors, which come before the needs of specification writers, which come before theoretical purity. Obviously in most projects there are far fewer stakeholders than for the whole web platform, but the spirit of the principle still applies: the higher the abstraction, the higher priority the user needs . Or, in other words, consumers above producers . For a more relatable example, in a web app using a framework like e.g. Vue and several Vue components, the user needs of website users come before the needs of the web app developers, which come before the needs of the developers of its Vue components, which come before the needs of the Vue framework developers (sorry Evan :). The TAG did not invent this principle; it is well known in UX and Product circles with a number of different wordings: “Put the pain on those who can bear it” Prefer internal complexity over external complexity Why is that? Several reasons: It is far easier to change the implementation than to change the user-facing API, so it’s worth making sacrifices to keep it clean from the get go. Most products have way more users than developers, so this minimizes collective pain. Internal complexity can be managed far more easily, with tooling or even good comments. Managing complexity internally localizes it and contains it better. Once the underlying platform improves, only one codebase needs to be changed to reap the benefits. The corollary is that if hacks allow you to expose a nicer API to component users, it may be worth the increase in internal complexity (to a degree). Just make sure that part of the code is well commented, and keep track of it so you can return to it once the platform has evolved to not require a hack anymore. Like all principles, this isn’t absolute . A small gain in user convenience is not a good tradeoff when it requires tremendous implementation complexity. But it’s a good north star to follow. As to whether custom properties are a better option to control styling than e.g. attributes, I listed several arguments for that in my previous article . Although, there are also cases where using custom properties is not a good idea… When is it not a good idea to do this? In a nutshell, when the abstraction is likely to leak. Ugliness is only acceptable if it’s encapsulated and not exposed to component users. If there is a high chance they may come into contact with it, it might be a better idea to simply use attributes and call it a day. Example callouts with three variants. In many of the examples below, I use variants as the canonical example of a custom property that a component may want to expose. However, if component consumers may need to customize each variant, it may be better to use attributes so they can just use e.g. [variant=&quot;success&quot;] instead of having to understand whatever crazy hack was used to expose a --variant custom property. And even from a philosophical purity perspective, variants are on the brink of presentational vs semantic anyway. The current state of the art There is a host of hacks and workarounds that people have come up with to make up for the lack of inline conditionals in CSS, with the first ones dating back to as early as 2015. 1. Binary Linear Interpolation This was first documented by Roma Komarov in 2016 , and has since been used in a number of creative ways. The gist of this method is to use essentially the linear interpolation formula for mapping [ 0 , 1 ] to [ a , b ] : p × a + ( 1 − p ) × b However, instead of using this to map a range to another range, we use it to map two points to two other points, basically the two extremes of both ranges: p = 0 and p = 1 to select a and b respectively. This was Roma’s original example: :root { --is-big: 0; } .is-big { --is-big: 1; } .block { padding: calc( 25px * var(--is-big) + 10px * (1 - var(--is-big)) ); border-width: calc( 3px * var(--is-big) + 1px * (1 - var(--is-big)) ); } He even expands it to multiple conditions by multiplying the interpolation factors. E.g. this code snippet to map 0 to 100px , 1 to 20px , and 2 to 3px : .block { padding: calc( 100px * (1 - var(--foo)) * (2 - var(--foo)) * 0.5 + 20px * var(--foo) * (2 - var(--foo)) + 3px * var(--foo) * (1 - var(--foo)) * -0.5 ); } Which these days could be rewritten as this, which also makes the boolean logic at play clearer: .block { --if-not-0: min(max(0 - var(--foo), var(--foo) - 0), 1); --if-not-1: min(max(1 - var(--foo), var(--foo) - 1), 1); --if-not-2: min(max(2 - var(--foo), var(--foo) - 2), 1); --if-0: var(--if-not-1) * var(--if-not-2); --if-1: var(--if-not-0) * var(--if-not-2); --if-2: var(--if-not-0) * var(--if-not-1); padding: calc( 100px * var(--if-0) + 20px * var(--if-1) + 3px * var(--if-2) ); } Back then, min() and max() were not available, so he had to divide each factor by an obscure constant to make it equal to 1 when it was not 0 . Once abs() ships this will be even simpler (the inner max() is basically getting the absolute value of N - var(--foo) ) Ana Tudor also wrote about this in 2018, in this very visual article: DRY Switching with CSS Variables . Pretty sure she was also using boolean algebra on these too (multiplication = AND, addition = OR), but I couldn’t find the exact post. 2. Toggles (Space Toggle, Cyclic Toggles) This was independently discovered by Ana Tudor ( c. 2017 ), Jane Ori in April 2020 (who gave it the name “Space Toggle”), David Khoursid (aka David K Piano) in June 2020 (he called it prop-and-lock), and yours truly in Oct 2020 (I called it the --var: ; hack, arguably the worst name of the three 😅). The core idea is that var(--foo, fallback) is actually a very limited form of conditional: if --foo is initial (or IACVT), it falls back to fallback , otherwise it’s var(--foo) . Furthermore, we can set custom properties (or their fallbacks) to empty values to get them to be ignored when used as part of a property value. It looks like this: :root { --if-success: ; --if-warning: ; } .success { --if-success: initial; } .warning { --if-warning: initial; } .callout { background: var(--if-success, var(--color-success-90)) var(--if-warning, var(--color-warning-90)); } One of the downsides of this version is that it only supports two states per variable. Note how we needed two variables for the two states. Another downside is that there is no way to specify a fallback if none of the relevant variables are set. In the example above, if neither --if-success nor --if-warning are set, the background declaration will be empty, and thus become IACVT which will make it transparent . Cyclic Toggles In 2023, Roma Komarov expanded the technique into what he called “Cyclic Dependency Space Toggles” which addresses both limitations: it supports any number of states, and allows for a default value. The core idea is that variables do not only become initial when they are not set, or are explicitly set to initial , but also when cycles are encountered. What is a cycle? A cycle is when a variable references itself, either directly or indirectly. The most trivial cycle is --foo: var(--foo); but they can have any number of steps, e.g.: --a1: var(--a2); --a2: var(--a3); --a3: var(--a1); Roma’s technique depends on this behavior by producing cycles on all but one of the variables used for the values. It looks like this: .info { --variant: var(--variant-default); --variant-default: var(--variant,); --variant-success: var(--variant,); --variant-warning: var(--variant,); --variant-error: var(--variant,); background: var(--variant-default, lavender) var(--variant-success, palegreen) var(--variant-warning, khaki) var(--variant-error, lightpink); } And is used like this: .my-warning { --variant: var(--variant-warning); } A downside of this method is that since the values behind the --variant-success , --variant-warning , etc variables are specific to the --variant variable they need to be namespaced to avoid clashes. Layered Toggles A big downside of most of these methods (except for the animation-based ones) is that you need to specify all values of the property in one place, and the declaration gets applied whether your custom property has a value or not, which makes it difficult to layer composable styles leading to some undesirable couplings. Roma Komarov’s “Layered Toggles” method addresses this for some cases by allowing us to decouple the different values by taking advantage of Cascade Layers. The core idea is that Cascade Layers include a revert-layer keyword that will cause the current layer to be ignored wrt the declaration it’s used on. Given that we can use unnamed layers, we can simply user a @layer {} rule for every block of properties we want to apply conditionally. This approach does have some severe limitations which made it rather unpractical for my use cases. The biggest one is that anything in a layer has lower priority than any unlayered styles, which makes it prohibitive for many use cases. Also, this doesn’t really simplify cyclic toggles, you still need to set all values in one place. Still, worth a look as there are some use cases it can be helpful for. 3. Paused animations The core idea behind this method is that paused animations ( animation-play-state: paused ) can still be advanced by setting animation-delay to a negative value. For example in an animation like animation: 100s foo , you can access the 50% mark by setting animation-delay: -50s . It’s trivial to transform raw numbers to values, so this can be abstracted to plain numbers for the user-facing API. Here is a simple example to illustrate how this works: @keyframes color-mixin { 0% { background: var(--color-neutral-90); border-color: var(--color-neutral-80); } 25% { background: var(--color-success-90); border-color: var(--color-success-80); } 50% { background: var(--color-warning-90); border-color: var(--color-warning-80); } 75% { background: var(--color-danger-90); border-color: var(--color-danger-80); } } button { animation: foo 100s calc(var(--variant) * -100s / 4 ) infinite paused; } Used like: .error button { --variant: 2; } This is merely to illustrate the core idea, having a --variant property that takes numbers is not a good API! Though the numbers could be aliased to variables, so that users would set --variant: var(--success) . This technique seems to have been first documented by me in 2015 , during a talk about …pie charts (I would swear I showed it in an earlier talk but I cannot find it). I never bothered writing about it, but someone else did , 4 years later. To ensure you don’t get slightly interpolated values due to precision issues, you could also slap a steps() in there: button { animation: foo 100s calc(var(--variant) * -100s / 4 ) infinite paused steps(4); } This is especially useful when 100 divided by your number of values produces repeating decimals, e.g. 3 steps means your keyframes are at increments of 33.33333% . A benefit of this method is that defining each state is done with regular declarations, not involving any weirdness, and that . It does also have some obvious downsides: Values restricted to numbers Takes over the animation property, so you can’t use it for actual animations. 4. Type Grinding So far all of these methods impose constraints on the API exposed by these custom properties: numbers by the linear interpolation method and weird values that have to be hidden behind variables for the space toggle and cyclic toggle methods. In October 2022, Jane Ori was the first one to discover a method that actually allows us to support plain keywords, which is what the majority of these use cases needs. She called it “CSS-Only Type Grinding” . Its core idea is if a custom property is registered (via either @property or CSS.registerProperty() ), assigning values to it that are not valid for its syntax makes it IACVT (Invalid at computed value time) and it falls back to its initial (or inherited) value. She takes advantage of that to progressively transform keywords to other keywords or numbers through a series of intermediate registered custom properties, each substituting one more value for another. I was recently independently experimenting with a similar idea. It started from a use case of one of my components where I wanted to implement a --size property with two values: normal and large . Style queries could almost get me there, but I also needed to set flex-flow: column on the element itself when --size was large . The end result takes N + 1 @property rules, where N is the number of distinct values you need to support. The first one is the rule defining the syntax of your actual property: @property --size { syntax: &quot;normal | large&quot;, initial-value: normal; inherits: true; } Then, you define N more rules, each progressively substituting one value for another: @property --size-step-1 { syntax: &quot;row | large&quot;; initial-value: row; inherits: false; } @property --size-step-end { syntax: &quot;row | column&quot;; initial-value: column; inherits: false; } And at the component host you daisy chain them like this: :host { --size-step-1: var(--size); --size-step-end: var(--size-step-1); flex-flow: var(--size-step-end); } And component consumers get a really nice API: .my-component { --size: large; } You can see it in action in this codepen : See the Pen Transform keywords to other keywords (2 keyword version) by Lea Verou ( @leaverou ) on CodePen . You can use the same general idea to transform more keywords or to transform keywords into different sets of keywords for use in different properties . We can also transform keywords to numbers, by replacing successive keywords with in the syntax, one at a time, with different initial values each time. Here is the --variant example using this method: @property --variant { syntax: &quot;none | success | warning | danger&quot;; initial-value: none; inherits: true; } @property --variant-step-1 { syntax: &quot;none | | warning | danger&quot;; initial-value: 1; inherits: false; } @property --variant-step-2 { syntax: &quot;none | | danger&quot;; initial-value: 2; inherits: false; } @property --variant-step-3 { syntax: &quot;none | &quot;; initial-value: 3; inherits: false; } @property --variant-index { syntax: &quot; &quot;; initial-value: 0; inherits: false; } .callout { --variant-step-1: var(--variant); --variant-step-2: var(--variant-step-1); --variant-step-3: var(--variant-step-2); --variant-index: var(--variant-step-3); /* Now use --variant-index to set other values */ } Then, we can use techniques like linear range mapping to transform it to a length or a percentage ( generator ) or recursive color-mix() to use that number to select an appropriate color. 5. Variable animation name In 2018, Roma Komarov discovered another method that allows plain keywords to be used as the custom property API, forgot about it, then rediscovered it in June 2023 😅. He still never wrote about it, so these codepens are the only documentation we have. It’s a variation of the previous method: instead of using a single @keyframes rule and switching between them via animation-delay , define several separate @keyframes rules, each named after the keyword we want to use: @keyframes success { from, to { background-color: var(--color-success-90); border-color: var(--color-success-80); } } @keyframes warning { from, to { background-color: var(--color-warning-90); border-color: var(--color-warning-80); } } @keyframes danger { from, to { background-color: var(--color-danger-90); border-color: var(--color-danger-80); } } .callout { padding: 1em; margin: 1rem; border: 3px solid var(--color-neutral-80); background: var(--color-neutral-90); animation: var(--variant) 0s paused both; } Used like: .warning { --variant: warning; } The obvious downsides of this method are: Impractical to use outside of Shadow DOM due to the potential for name clashes. Takes over the animation property, so you can’t use it for actual animations. Improvements Every one of these methods has limitations, some of which are inerent in its nature, but others can be improved upon. In this section I will discuss some improvements that me or others have thought of. I decided to include these in a separate section, since they affect more than one method. Making animation-based approaches cascade better A big downside with the animation-based approaches (3 and 5) is the place of animations in the cascade: properties applied via animation keyframes can only be overridden via other animations or !important . One way to deal with that is to set custom properties in the animation keyframes, that you apply in regular rules. To use the example from Variable animation name : @keyframes success { from, to { --background-color: var(--color-success-90); --border-color: var(--color-success-80); } } @keyframes warning { from, to { --background-color: var(--color-warning-90); --border-color: var(--color-warning-80); } } @keyframes danger { from, to { --background-color: var(--color-danger-90); --border-color: var(--color-danger-80); } } .callout { padding: 1em; margin: 1rem; border: 3px solid var(--border-color, var(--color-neutral-80)); background-color: var(--background-color, var(--color-neutral-90)); animation: var(--variant) 0s paused both; } Note that you can combine the two approaches (variable animation-name and paused animations ) when you have two custom properties where each state of the first corresponds to N distinct states of the latter. For example, a --variant that sets colors, and a light/dark mode within each variant that sets different colors. Making animation-based approaches compose better with author code Another downside of the animation-based approaches is that they take over the animation property. If authors want to apply an animation to your component, suddenly a bunch of unrelated things stop working, which is not great user experience. There isn’t that much to do here to prevent this experience, but you can at least offer a way out: instead of defining your animations directly on animation , define them on a custom property, e.g. --core-animations . Then, if authors want to apply their own animations, they just make sure to also include var(--core-animations) before or after. Discrete color scales Many of the approaches above are based on numerical values, which are then mapped to the value we actually want. For numbers or dimensions , this is easy. But what about colors? I linked to Noah Liebman’s post above on recursive color-mix() , where he presents a rather complex method to select among a continuous color scale based on a 0-1 number. However, if you don’t care about any intermediate colors and just want to select among a few discrete colors, the method can be a lot simpler. Simple enough to be specified inline. Let me explain: Since color-mix() only takes two colors, we need to nest them to select among more than 2, no way around that. However, the percentages we calculate can be very simple: 100% when we want to select the first color and 0% otherwise. I plugged these numbers into my CSS range mapping tool ( example ) and noticed a pattern: If we want to output 100% when our variable (e.g. --variant-index ) is N-1 and 0% when it’s N, we can use 100% * (N - var(--variant-index)) . We can use this on every step of the mixing: background: color-mix(in oklab, var(--stone-2) calc(100% * (1 - var(--color-index, 0))), /* default color */ color-mix(in oklab, var(--green-2) calc(100% * (2 - var(--color-index))), color-mix(in oklab, var(--yellow-2) calc(100% * (3 - var(--color-index))), var(--red-2) ) ) ); But what happens when the resulting percentage is 100%? Generally, percentages outside [0%, 100%] make color-mix() invalid , which would indicate that we need to take care to keep our percentages within that range (via clamp() or max() ). However, within math functions there is no parse-time range-checking , so values are simply clamped to the allowed range. Here is a simple example that you can play with ( codepen ): See the Pen Discrete color scales with simpler recursive color-mix() by Lea Verou ( @leaverou ) on CodePen . And here is a more realistic one, using the Type Grinding method to transform keywords to numbers, and then using the above technique to select among 4 colors for backgrounds and borders ( codepen ). Combining approaches There are two components to each method: the input values it supports, i.e. your custom property API that you will expose, e.g. numbers, keywords, etc., and the output values it supports ( , keywords, etc.). Even without doing anything, we can combine methods that support the same type of input values, e.g. Binary Linear Interpolation and Paused animations or Type Grinding and Variable animation names . If we can transform the input values of one method to the input values of another, we can mix and match approaches to maximize flexibility. For example, we can use type grinding to transform keywords to numbers, and then use paused animations or binary linear interpolation to select among a number of quantitative values based on that number. Keywords → Numbers Type grinding Numbers → Keywords We can use paused animations to select among a number of keywords based on a number (which we transform to a negative animation-delay ). Space toggles → Numbers Easy: --number: calc(0 var(--toggle, + 1)) Numbers → Space toggles Once again, Roma Komarov has come up with a very cool trick : he conditionally applies an animation which interpolates two custom properties from initial to the empty value and vice versa — basically variable animation names but used on an internal value. Unfortunately a Firefox bug prevents it from working interoperably. He also tried a variant for space toggles but that has even worse compatibility, limited to Chrome only. I modified his idea a bit to use paused animations instead, and it looks like my attempt works on Firefox as well. 🎉 So, which one is better? I’ve summarized the pros and cons of each method below: Method Input values Output values Pros Cons Binary Linear Interpolation Numbers Quantitative Lightweight Requires no global rules Limited output range Toggles var(--alias) (actual values are too weird to expose raw) Any Can be used in part of a value Weird values that need to be aliased Paused animations Numbers Any Normal, decoupled declarations Takes over animation property Cascade weirdness Type Grinding Keywords Any value supported by the syntax descriptor High flexibility for exposed API Good encapsulation Must insert CSS into light DOM Tedious code (though can be automated with build tools) No Firefox support (though that’s changing ) Variable animation name Keywords Any Normal, decoupled declarations Impractical outside of Shadow DOM due to name clashes Takes over animation property Cascade weirdness The most important consideration is the API we want to expose to component users. After all, exposing a nicer API is the whole point of this, right? If your custom property makes sense as a number without degrading usability (e.g. --size may make sense as a number, but small | medium | large is still better than 0 | 1 | 2 ), then Binary Linear Interpolation is probably the most flexible method to start with, and as we have seen in Combining approaches section, numbers can be converted to inputs for every other method. However, in the vast majority of cases I have seen, the north star API is a set of plain, high-level keywords. This is only possible via Type Grinding and Variable animation names . Between the two, Type Grinding is the one providing the best encapsulation, since it relies entirely on custom properties and does not hijack any native properties. Unfortunately, the fact that @property is not yet supported in Shadow DOM throws a spanner in the works, but since these intermediate properties are only used for internal calculations, we can just give them obscure names and insert them in the light DOM. On the other hand, @keyframes are not only allowed, but also properly scoped when used in Shadow DOM , so Variable animation name might be a good choice when you don’t want to use the same keywords for multiple custom properties on the same component and its downsides are not dealbreakers for your particular use case. Conclusion Phew! That was a long one. If you’re aware of any other techniques, let me know so I can add them. And I think after all of this, if you had any doubt that we need if() in CSS, the sheer number and horribleness of these hacks must have dispelled it by now. 😅 Thanks to Roma Komarov for reviewing earlier drafts of this article. I’ve always thought this was our most important deliverable, and pushed for prioritizing it. Recently, I even became editor of it. 🙃 ↩︎ I’m using product here in the general sense, of any software product, technology, or API, not just for-profit or commercial ones. ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Inline conditionals in CSS, now?</title>
  <link>https://lea.verou.me/blog/2024/css-conditionals-now/</link>
  <pubDate>Sun, 17 May 2026 03:45:09 +0200</pubDate>
  <description>The CSS WG resolved to add if() to CSS, but that wont be in browsers for a while. What are our options in the meantime? A couple days ago, I posted about the recent CSS WG resolution to add an if() function to CSS . Great as it may be, this is still a long way off, two years if everything goes super smoothly, more if not. So what can you do when you need conditionals right now ? You may be pleased to find that youre not completely out of luck. There is a series of brilliant, horrible hacks that enable you to expose the kinds of higher level custom properties that conditionals typically enable. Using hacks in production?! The instinctive reaction many developers have when seeing hacks like these is Nice hack, but cant possibly ever use this in production. This sounds reasonable on the surface (keeping the codebase maintainable is a worthy goal!) but when examined deeply, it reflects the wrong order of priorities, prioritizing developer convenience over user convenience. The TAG maintains a Web Platform Design Principles document [1] that everyone designing APIs for the web platform is supposed to read and follow. Im a strong believer in having published Design Principles, for any product [2] . They help stay on track, and remember what the big picture vision is, which is otherwise easy to lose sight of in the day to day minutiae. One of the core principles in the document is the Priority of Constituencies . The core of it is: User needs come before the needs of web page authors, which come before the needs of user agent implementors, which come before the needs of specification writers, which come before theoretical purity. Obviously in most projects there are far fewer stakeholders than for the whole web platform, but the spirit of the principle still applies: the higher the abstraction, the higher priority the user needs . Or, in other words, consumers above producers . For a more relatable example, in a web app using a framework like e.g. Vue and several Vue components, the user needs of website users come before the needs of the web app developers, which come before the needs of the developers of its Vue components, which come before the needs of the Vue framework developers (sorry Evan :). The TAG did not invent this principle; it is well known in UX and Product circles with a number of different wordings: Put the pain on those who can bear it Prefer internal complexity over external complexity Why is that? Several reasons: It is far easier to change the implementation than to change the user-facing API, so its worth making sacrifices to keep it clean from the get go. Most products have way more users than developers, so this minimizes collective pain. Internal complexity can be managed far more easily, with tooling or even good comments. Managing complexity internally localizes it and contains it better. Once the underlying platform improves, only one codebase needs to be changed to reap the benefits. The corollary is that if hacks allow you to expose a nicer API to component users, it may be worth the increase in internal complexity (to a degree). Just make sure that part of the code is well commented, and keep track of it so you can return to it once the platform has evolved to not require a hack anymore. Like all principles, this isnt absolute . A small gain in user convenience is not a good tradeoff when it requires tremendous implementation complexity. But its a good north star to follow. As to whether custom properties are a better option to control styling than e.g. attributes, I listed several arguments for that in my previous article . Although, there are also cases where using custom properties is not a good idea When is it not a good idea to do this? In a nutshell, when the abstraction is likely to leak. Ugliness is only acceptable if its encapsulated and not exposed to component users. If there is a high chance they may come into contact with it, it might be a better idea to simply use attributes and call it a day. Example callouts with three variants. In many of the examples below, I use variants as the canonical example of a custom property that a component may want to expose. However, if component consumers may need to customize each variant, it may be better to use attributes so they can just use e.g. [variant=&quot;success&quot;] instead of having to understand whatever crazy hack was used to expose a --variant custom property. And even from a philosophical purity perspective, variants are on the brink of presentational vs semantic anyway. The current state of the art There is a host of hacks and workarounds that people have come up with to make up for the lack of inline conditionals in CSS, with the first ones dating back to as early as 2015. 1. Binary Linear Interpolation This was first documented by Roma Komarov in 2016 , and has since been used in a number of creative ways. The gist of this method is to use essentially the linear interpolation formula for mapping [ 0 , 1 ] to [ a , b ] : p a + ( 1 p ) b However, instead of using this to map a range to another range, we use it to map two points to two other points, basically the two extremes of both ranges: p = 0 and p = 1 to select a and b respectively. This was Romas original example: :root { --is-big: 0; } .is-big { --is-big: 1; } .block { padding: calc( 25px * var(--is-big) + 10px * (1 - var(--is-big)) ); border-width: calc( 3px * var(--is-big) + 1px * (1 - var(--is-big)) ); } He even expands it to multiple conditions by multiplying the interpolation factors. E.g. this code snippet to map 0 to 100px , 1 to 20px , and 2 to 3px : .block { padding: calc( 100px * (1 - var(--foo)) * (2 - var(--foo)) * 0.5 + 20px * var(--foo) * (2 - var(--foo)) + 3px * var(--foo) * (1 - var(--foo)) * -0.5 ); } Which these days could be rewritten as this, which also makes the boolean logic at play clearer: .block { --if-not-0: min(max(0 - var(--foo), var(--foo) - 0), 1); --if-not-1: min(max(1 - var(--foo), var(--foo) - 1), 1); --if-not-2: min(max(2 - var(--foo), var(--foo) - 2), 1); --if-0: var(--if-not-1) * var(--if-not-2); --if-1: var(--if-not-0) * var(--if-not-2); --if-2: var(--if-not-0) * var(--if-not-1); padding: calc( 100px * var(--if-0) + 20px * var(--if-1) + 3px * var(--if-2) ); } Back then, min() and max() were not available, so he had to divide each factor by an obscure constant to make it equal to 1 when it was not 0 . Once abs() ships this will be even simpler (the inner max() is basically getting the absolute value of N - var(--foo) ) Ana Tudor also wrote about this in 2018, in this very visual article: DRY Switching with CSS Variables . Pretty sure she was also using boolean algebra on these too (multiplication = AND, addition = OR), but I couldnt find the exact post. 2. Toggles (Space Toggle, Cyclic Toggles) This was independently discovered by Ana Tudor ( c. 2017 ), Jane Ori in April 2020 (who gave it the name Space Toggle), David Khoursid (aka David K Piano) in June 2020 (he called it prop-and-lock), and yours truly in Oct 2020 (I called it the --var: ; hack, arguably the worst name of the three ). The core idea is that var(--foo, fallback) is actually a very limited form of conditional: if --foo is initial (or IACVT), it falls back to fallback , otherwise its var(--foo) . Furthermore, we can set custom properties (or their fallbacks) to empty values to get them to be ignored when used as part of a property value. It looks like this: :root { --if-success: ; --if-warning: ; } .success { --if-success: initial; } .warning { --if-warning: initial; } .callout { background: var(--if-success, var(--color-success-90)) var(--if-warning, var(--color-warning-90)); } One of the downsides of this version is that it only supports two states per variable. Note how we needed two variables for the two states. Another downside is that there is no way to specify a fallback if none of the relevant variables are set. In the example above, if neither --if-success nor --if-warning are set, the background declaration will be empty, and thus become IACVT which will make it transparent . Cyclic Toggles In 2023, Roma Komarov expanded the technique into what he called Cyclic Dependency Space Toggles which addresses both limitations: it supports any number of states, and allows for a default value. The core idea is that variables do not only become initial when they are not set, or are explicitly set to initial , but also when cycles are encountered. What is a cycle? A cycle is when a variable references itself, either directly or indirectly. The most trivial cycle is --foo: var(--foo); but they can have any number of steps, e.g.: --a1: var(--a2); --a2: var(--a3); --a3: var(--a1); Romas technique depends on this behavior by producing cycles on all but one of the variables used for the values. It looks like this: .info { --variant: var(--variant-default); --variant-default: var(--variant,); --variant-success: var(--variant,); --variant-warning: var(--variant,); --variant-error: var(--variant,); background: var(--variant-default, lavender) var(--variant-success, palegreen) var(--variant-warning, khaki) var(--variant-error, lightpink); } And is used like this: .my-warning { --variant: var(--variant-warning); } A downside of this method is that since the values behind the --variant-success , --variant-warning , etc variables are specific to the --variant variable they need to be namespaced to avoid clashes. Layered Toggles A big downside of most of these methods (except for the animation-based ones) is that you need to specify all values of the property in one place, and the declaration gets applied whether your custom property has a value or not, which makes it difficult to layer composable styles leading to some undesirable couplings. Roma Komarovs Layered Toggles method addresses this for some cases by allowing us to decouple the different values by taking advantage of Cascade Layers. The core idea is that Cascade Layers include a revert-layer keyword that will cause the current layer to be ignored wrt the declaration its used on. Given that we can use unnamed layers, we can simply user a @layer {} rule for every block of properties we want to apply conditionally. This approach does have some severe limitations which made it rather unpractical for my use cases. The biggest one is that anything in a layer has lower priority than any unlayered styles, which makes it prohibitive for many use cases. Also, this doesnt really simplify cyclic toggles, you still need to set all values in one place. Still, worth a look as there are some use cases it can be helpful for. 3. Paused animations The core idea behind this method is that paused animations ( animation-play-state: paused ) can still be advanced by setting animation-delay to a negative value. For example in an animation like animation: 100s foo , you can access the 50% mark by setting animation-delay: -50s . Its trivial to transform raw numbers to values, so this can be abstracted to plain numbers for the user-facing API. Here is a simple example to illustrate how this works: @keyframes color-mixin { 0% { background: var(--color-neutral-90); border-color: var(--color-neutral-80); } 25% { background: var(--color-success-90); border-color: var(--color-success-80); } 50% { background: var(--color-warning-90); border-color: var(--color-warning-80); } 75% { background: var(--color-danger-90); border-color: var(--color-danger-80); } } button { animation: foo 100s calc(var(--variant) * -100s / 4 ) infinite paused; } Used like: .error button { --variant: 2; } This is merely to illustrate the core idea, having a --variant property that takes numbers is not a good API! Though the numbers could be aliased to variables, so that users would set --variant: var(--success) . This technique seems to have been first documented by me in 2015 , during a talk about pie charts (I would swear I showed it in an earlier talk but I cannot find it). I never bothered writing about it, but someone else did , 4 years later. To ensure you dont get slightly interpolated values due to precision issues, you could also slap a steps() in there: button { animation: foo 100s calc(var(--variant) * -100s / 4 ) infinite paused steps(4); } This is especially useful when 100 divided by your number of values produces repeating decimals, e.g. 3 steps means your keyframes are at increments of 33.33333% . A benefit of this method is that defining each state is done with regular declarations, not involving any weirdness, and that . It does also have some obvious downsides: Values restricted to numbers Takes over the animation property, so you cant use it for actual animations. 4. Type Grinding So far all of these methods impose constraints on the API exposed by these custom properties: numbers by the linear interpolation method and weird values that have to be hidden behind variables for the space toggle and cyclic toggle methods. In October 2022, Jane Ori was the first one to discover a method that actually allows us to support plain keywords, which is what the majority of these use cases needs. She called it CSS-Only Type Grinding . Its core idea is if a custom property is registered (via either @property or CSS.registerProperty() ), assigning values to it that are not valid for its syntax makes it IACVT (Invalid at computed value time) and it falls back to its initial (or inherited) value. She takes advantage of that to progressively transform keywords to other keywords or numbers through a series of intermediate registered custom properties, each substituting one more value for another. I was recently independently experimenting with a similar idea. It started from a use case of one of my components where I wanted to implement a --size property with two values: normal and large . Style queries could almost get me there, but I also needed to set flex-flow: column on the element itself when --size was large . The end result takes N + 1 @property rules, where N is the number of distinct values you need to support. The first one is the rule defining the syntax of your actual property: @property --size { syntax: &quot;normal | large&quot;, initial-value: normal; inherits: true; } Then, you define N more rules, each progressively substituting one value for another: @property --size-step-1 { syntax: &quot;row | large&quot;; initial-value: row; inherits: false; } @property --size-step-end { syntax: &quot;row | column&quot;; initial-value: column; inherits: false; } And at the component host you daisy chain them like this: :host { --size-step-1: var(--size); --size-step-end: var(--size-step-1); flex-flow: var(--size-step-end); } And component consumers get a really nice API: .my-component { --size: large; } You can see it in action in this codepen : See the Pen Transform keywords to other keywords (2 keyword version) by Lea Verou ( @leaverou ) on CodePen . You can use the same general idea to transform more keywords or to transform keywords into different sets of keywords for use in different properties . We can also transform keywords to numbers, by replacing successive keywords with in the syntax, one at a time, with different initial values each time. Here is the --variant example using this method: @property --variant { syntax: &quot;none | success | warning | danger&quot;; initial-value: none; inherits: true; } @property --variant-step-1 { syntax: &quot;none | | warning | danger&quot;; initial-value: 1; inherits: false; } @property --variant-step-2 { syntax: &quot;none | | danger&quot;; initial-value: 2; inherits: false; } @property --variant-step-3 { syntax: &quot;none | &quot;; initial-value: 3; inherits: false; } @property --variant-index { syntax: &quot; &quot;; initial-value: 0; inherits: false; } .callout { --variant-step-1: var(--variant); --variant-step-2: var(--variant-step-1); --variant-step-3: var(--variant-step-2); --variant-index: var(--variant-step-3); /* Now use --variant-index to set other values */ } Then, we can use techniques like linear range mapping to transform it to a length or a percentage ( generator ) or recursive color-mix() to use that number to select an appropriate color. 5. Variable animation name In 2018, Roma Komarov discovered another method that allows plain keywords to be used as the custom property API, forgot about it, then rediscovered it in June 2023 . He still never wrote about it, so these codepens are the only documentation we have. Its a variation of the previous method: instead of using a single @keyframes rule and switching between them via animation-delay , define several separate @keyframes rules, each named after the keyword we want to use: @keyframes success { from, to { background-color: var(--color-success-90); border-color: var(--color-success-80); } } @keyframes warning { from, to { background-color: var(--color-warning-90); border-color: var(--color-warning-80); } } @keyframes danger { from, to { background-color: var(--color-danger-90); border-color: var(--color-danger-80); } } .callout { padding: 1em; margin: 1rem; border: 3px solid var(--color-neutral-80); background: var(--color-neutral-90); animation: var(--variant) 0s paused both; } Used like: .warning { --variant: warning; } The obvious downsides of this method are: Impractical to use outside of Shadow DOM due to the potential for name clashes. Takes over the animation property, so you cant use it for actual animations. Improvements Every one of these methods has limitations, some of which are inerent in its nature, but others can be improved upon. In this section I will discuss some improvements that me or others have thought of. I decided to include these in a separate section, since they affect more than one method. Making animation-based approaches cascade better A big downside with the animation-based approaches (3 and 5) is the place of animations in the cascade: properties applied via animation keyframes can only be overridden via other animations or !important . One way to deal with that is to set custom properties in the animation keyframes, that you apply in regular rules. To use the example from Variable animation name : @keyframes success { from, to { --background-color: var(--color-success-90); --border-color: var(--color-success-80); } } @keyframes warning { from, to { --background-color: var(--color-warning-90); --border-color: var(--color-warning-80); } } @keyframes danger { from, to { --background-color: var(--color-danger-90); --border-color: var(--color-danger-80); } } .callout { padding: 1em; margin: 1rem; border: 3px solid var(--border-color, var(--color-neutral-80)); background-color: var(--background-color, var(--color-neutral-90)); animation: var(--variant) 0s paused both; } Note that you can combine the two approaches (variable animation-name and paused animations ) when you have two custom properties where each state of the first corresponds to N distinct states of the latter. For example, a --variant that sets colors, and a light/dark mode within each variant that sets different colors. Making animation-based approaches compose better with author code Another downside of the animation-based approaches is that they take over the animation property. If authors want to apply an animation to your component, suddenly a bunch of unrelated things stop working, which is not great user experience. There isnt that much to do here to prevent this experience, but you can at least offer a way out: instead of defining your animations directly on animation , define them on a custom property, e.g. --core-animations . Then, if authors want to apply their own animations, they just make sure to also include var(--core-animations) before or after. Discrete color scales Many of the approaches above are based on numerical values, which are then mapped to the value we actually want. For numbers or dimensions , this is easy. But what about colors? I linked to Noah Liebmans post above on recursive color-mix() , where he presents a rather complex method to select among a continuous color scale based on a 0-1 number. However, if you dont care about any intermediate colors and just want to select among a few discrete colors, the method can be a lot simpler. Simple enough to be specified inline. Let me explain: Since color-mix() only takes two colors, we need to nest them to select among more than 2, no way around that. However, the percentages we calculate can be very simple: 100% when we want to select the first color and 0% otherwise. I plugged these numbers into my CSS range mapping tool ( example ) and noticed a pattern: If we want to output 100% when our variable (e.g. --variant-index ) is N-1 and 0% when its N, we can use 100% * (N - var(--variant-index)) . We can use this on every step of the mixing: background: color-mix(in oklab, var(--stone-2) calc(100% * (1 - var(--color-index, 0))), /* default color */ color-mix(in oklab, var(--green-2) calc(100% * (2 - var(--color-index))), color-mix(in oklab, var(--yellow-2) calc(100% * (3 - var(--color-index))), var(--red-2) ) ) ); But what happens when the resulting percentage is 100%? Generally, percentages outside [0%, 100%] make color-mix() invalid , which would indicate that we need to take care to keep our percentages within that range (via clamp() or max() ). However, within math functions there is no parse-time range-checking , so values are simply clamped to the allowed range. Here is a simple example that you can play with ( codepen ): See the Pen Discrete color scales with simpler recursive color-mix() by Lea Verou ( @leaverou ) on CodePen . And here is a more realistic one, using the Type Grinding method to transform keywords to numbers, and then using the above technique to select among 4 colors for backgrounds and borders ( codepen ). Combining approaches There are two components to each method: the input values it supports, i.e. your custom property API that you will expose, e.g. numbers, keywords, etc., and the output values it supports ( , keywords, etc.). Even without doing anything, we can combine methods that support the same type of input values, e.g. Binary Linear Interpolation and Paused animations or Type Grinding and Variable animation names . If we can transform the input values of one method to the input values of another, we can mix and match approaches to maximize flexibility. For example, we can use type grinding to transform keywords to numbers, and then use paused animations or binary linear interpolation to select among a number of quantitative values based on that number. Keywords Numbers Type grinding Numbers Keywords We can use paused animations to select among a number of keywords based on a number (which we transform to a negative animation-delay ). Space toggles Numbers Easy: --number: calc(0 var(--toggle, + 1)) Numbers Space toggles Once again, Roma Komarov has come up with a very cool trick : he conditionally applies an animation which interpolates two custom properties from initial to the empty value and vice versa basically variable animation names but used on an internal value. Unfortunately a Firefox bug prevents it from working interoperably. He also tried a variant for space toggles but that has even worse compatibility, limited to Chrome only. I modified his idea a bit to use paused animations instead, and it looks like my attempt works on Firefox as well. So, which one is better? Ive summarized the pros and cons of each method below: Method Input values Output values Pros Cons Binary Linear Interpolation Numbers Quantitative Lightweight Requires no global rules Limited output range Toggles var(--alias) (actual values are too weird to expose raw) Any Can be used in part of a value Weird values that need to be aliased Paused animations Numbers Any Normal, decoupled declarations Takes over animation property Cascade weirdness Type Grinding Keywords Any value supported by the syntax descriptor High flexibility for exposed API Good encapsulation Must insert CSS into light DOM Tedious code (though can be automated with build tools) No Firefox support (though thats changing ) Variable animation name Keywords Any Normal, decoupled declarations Impractical outside of Shadow DOM due to name clashes Takes over animation property Cascade weirdness The most important consideration is the API we want to expose to component users. After all, exposing a nicer API is the whole point of this, right? If your custom property makes sense as a number without degrading usability (e.g. --size may make sense as a number, but small | medium | large is still better than 0 | 1 | 2 ), then Binary Linear Interpolation is probably the most flexible method to start with, and as we have seen in Combining approaches section, numbers can be converted to inputs for every other method. However, in the vast majority of cases I have seen, the north star API is a set of plain, high-level keywords. This is only possible via Type Grinding and Variable animation names . Between the two, Type Grinding is the one providing the best encapsulation, since it relies entirely on custom properties and does not hijack any native properties. Unfortunately, the fact that @property is not yet supported in Shadow DOM throws a spanner in the works, but since these intermediate properties are only used for internal calculations, we can just give them obscure names and insert them in the light DOM. On the other hand, @keyframes are not only allowed, but also properly scoped when used in Shadow DOM , so Variable animation name might be a good choice when you dont want to use the same keywords for multiple custom properties on the same component and its downsides are not dealbreakers for your particular use case. Conclusion Phew! That was a long one. If youre aware of any other techniques, let me know so I can add them. And I think after all of this, if you had any doubt that we need if() in CSS, the sheer number and horribleness of these hacks must have dispelled it by now. Thanks to Roma Komarov for reviewing earlier drafts of this article. Ive always thought this was our most important deliverable, and pushed for prioritizing it. Recently, I even became editor of it. Im using product here in the general sense, of any software product, technology, or API, not just for-profit or commercial ones.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Inline conditionals in CSS?</title>
  <link>https://lea.verou.me/blog/2024/css-conditionals/</link>
  <pubDate>Sun, 17 May 2026 03:45:08 +0200</pubDate>
  <description>Last week, the CSS WG resolved to add an inline if() to CSS. But what does that mean, and why is it exciting? Last week, we had a CSS WG face-to-face meeting in A Coruña, Spain. There is one resolution from that meeting that I’m particularly excited about: the consensus to add an inline if() to CSS . While I was not the first to propose an inline conditional syntax, I did try and scope down the various nonterminating discussions into an MVP that can actually be implemented quickly, discussed ideas with implemenators, and eventually published a concrete proposal and pushed for group resolution. Quite poetically, the relevant discussion occurred on my birthday, so in a way, I got if() as the most unique birthday present ever. 😀 This also comes to show that proposals being rejected is not the end-all for a given feature. It is in fact quite common for features to be rejected for several times before they are accepted: CSS Nesting, :has() , container queries were all simply the last iteration in a series of rejected proposals. if() itself was apparently rejected in 2018 with very similar syntax to what I proposed. What was the difference? Style queries had already shipped, and we could simply reference the same syntax for conditions (plus media() and supports() from Tab’s @when proposal ) whereas in the 2018 proposal how conditions would work was largely undefined. I posted about this on a variety of social media, and the response by developers has been overwhelmingly positive: Twitter LinkedIn Mastodon I even had friends from big companies writing to tell me their internal Slacks blew up about it. This proves what I’ve always suspected, and was part of the case I made to the CSS WG: that this is a huge pain point . Hopefully the amount and intensity of positive reactions will help browsers prioritize this feature and add it to their roadmaps earlier rather than later. Across all these platforms, besides the “I can’t wait for this to ship!” sentiment being most common, there were a few other recurring questions and a fair bit of confusion that I figured were worth addressing. FAQ What is if() for? Does it replace style queries? Quite the opposite — if() complements style queries . If you can do something with style queries, by all means, use style queries — they are almost certainly a better solution. But there are things you simply cannot do with style queries. Let me explain. The motivating use case was that components (in the broader sense) often need to define higher level custom properties , whose values are not just used verbatim in declarations, but that set unrelated values on a variety of declarations. For example, consider a --variant custom property (inspired from Shoelace’s variant attribute ). It could look like this: --variant: success | danger | warning | primary | none; This needs to set background colors, border colors, text colors, icons, etc. In fact, it’s actual value is not used verbatim anywhere, it is only used to set other values. Style queries get us halfway there: .callout { /* or :host if in Shadow DOM */ @container (style(--variant: success)) { &amp;::before { content: var(--icon-success); color: var(--color-success); } } /* (other variants) */ } However, style queries only work on descendants. We cannot do this: .callout { @container (style(--variant: success)) { border-color: var(--color-success-30); background-color: var(--color-success-95); &amp;::before { content: var(--icon-success); color: var(--color-success-05); } } /* (other variants) */ } Often the declarations we need to set on the element itself are very few, sometimes even just one. However, even one is one too many and makes using custom properties untenable for many (possibly most) higher level custom property use cases. As a result, component libraries end up resorting to presentational attributes like pill , outline , size , etc. While presentational attributes may seem fine at first glance, or even better for DX (fewer characters — at least compared to setting a variable per element), they have several usability issues: Reduced flexibility They cannot be conditionally applied based on selectors, media queries, etc. Changing them requires more JS. If they are used within another component, you’re SOL, whereas with (inheritable) custom properties, you can set the property on the parent component and it will inherit down. Verbosity They have to be applied to individual instances, and cannot be inherited. Even if one uses some form of templating or componentization to reduce duplication, they still have to wade through these attributes when debugging with dev tools. Lack of consistency Since almost every mature component also supports custom properties, users have to remember which styling is done via attributes and which via custom properties. The distinction is often arbitrary, as it’s not driven by use cases, but implementation convenience. With if() , the above example becomes possible, albeit with worse ergonomics than style queries since it cannot cascade (though I do have a proposal to allow it to — plus all other IACVT declarations): .callout { border-color: if( style(--variant: success) ? var(--color-success-30) : style(--variant: danger) ? var(--color-danger-30) : /* (other variants) */ var(--color-neutral-30) ); background-color: if( style(--variant: success) ? var(--color-success-95) : style(--variant: danger) ? var(--color-danger-95) : /* (other variants) */ var(--color-neutral-95) ); @container (style(--variant: success)) { &amp;::before { content: var(--icon-success); color: var(--color-success-05); } } /* (other variants) */ } While this was the primary use case, it turned out that it’s pretty easy to also make media queries and supports conditions part of if() ’s conditional syntax. And since it’s a function, its arguments (including the condition!) can be stored in other custom properties. This means you can do things like this: :root { --xl: media(width &gt; 1600px); --l: media (width &gt; 1200px); --m: media (width &gt; 800px); } and then define values like: padding: if( var(--xl) ? var(--size-3) : var(--l) or var(--m) ? var(--size-2) : var(--size-1) ); Just like ternaries in JS, it may also be more ergonomic for cases where only a small part of the value varies: animation: if(media(prefers-reduced-motion) ? 10s : 1s) rainbow infinite; So is it in browsers yet? Believe it or not, that was a real question I got 😅. No, it’s not in browsers yet, and it won’t be for a while. The most optimistic estimate is 2 years or so, if the process doesn’t stall at any point (as it often does). All we have is consensus to work on the feature. The next steps are: Reach consensus on the syntax of the feature. Syntax debates can often take a very long time, because syntax is an area where everyone has opinions. The current debates revolve around: What separators to use between the condition and the branches? How to represent no value? Do we simply allow empty values like in var() (where you can do var(--foo,) ) or do we introduce a dedicated syntax that means “empty value” ? Should the last value be optional? Spec the feature. Get the first implementation. Often that is the hardest part. Once one browser implements, it is far easier to get the others on board. Get it shipped across all major browsers. I do have a page where I track some of my standards proposals which should help illuminate what the timeline looks like for each of these steps. In fact, you can track the progress of if() specifically there too. Do note that these steps are not necessarily linear. Often we spec an initial version, then resolve on a different syntax and update the spec. Sometimes browsers even implement the early syntax, and then it changes and they have to change their implementations (as happened with Nesting ) Want to make this happen faster? Excited about if() and want to show gratitude? Fund my web standards work on Open Collective . I started this while writing this blog post as an experiment, and don’t plan to promote it much, but it’s there if you are so inclined. Is this the first conditional in CSS? Many responses were along the lines of “Wow, CSS is finally getting conditionals!”. Folks… CSS had conditionals from the very beginning. Every selector is essentially a conditional! In addition: @media and @supports rules are conditionals. And let’s not forget @container . var(--foo, fallback) is a limited type of conditional (essentially if(style(--foo: initial) ? var(--foo) : fallback) ), hence why it’s the basis of most workarounds for emulating inline conditionals. Does this make CSS imperative? A widespread misconception is that non-linear logic (conditionals, loops) makes a language imperative. Declarative vs imperative is not about logic, but level of abstraction. Are we describing the goal or how to achieve it? In culinary terms, a recipe is imperative, a restaurant menu is declarative Conditional logic can actually make a language more declarative if it helps describe intent better. Consider the following two snippets of CSS: Space toggle if() button { border-radius: calc(.2em + var(--pill, 999em)); } .fancy.button { /* Turn pill on */ --pill: initial; } button { border-radius: if(style(--shape: pill) ? 999em : .2em); } .fancy.button { --shape: pill; } I would argue the latter is far more declarative, i.e. much closer to specifying the goal rather than how to achieve it. Does this make CSS a programming language? A very common type of response was around whether CSS is now a programming language (either asking whether it is, or asserting that it now is). To answer that, one first needs to answer what a programming language is . If it’s Turing-completeness that makes a language a programming language, then CSS has been a programming language for over a decade . But then again, so is Excel or Minecraft . So what does that even mean? If it’s imperativeness, then no, CSS is not a programming language. But neither are many actual programming languages! But a deeper question is, why does it matter ? Is it because it legitimizes choosing to specialize in CSS? It is because you can then be considered a programmer even if you only write HTML &amp; CSS? If this only matters for optics, then we should fix the issue at its core and fight to legitimize CSS expertise regardless of whether CSS is a programming language. After all, as anyone who knows several well-respected programming languages and CSS can attest, CSS is far harder to master. Great as all this may be, it won’t be in browsers for a while. What can we do right now ? I wrote Part 2 exactly about that: CSS Conditionals, now?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Inline conditionals in CSS?</title>
  <link>https://lea.verou.me/blog/2024/css-conditionals/</link>
  <pubDate>Sun, 17 May 2026 03:45:08 +0200</pubDate>
  <description>Last week, the CSS WG resolved to add an inline if() to CSS. But what does that mean, and why is it exciting? Last week, we had a CSS WG face-to-face meeting in A Coruña, Spain. There is one resolution from that meeting that Im particularly excited about: the consensus to add an inline if() to CSS . While I was not the first to propose an inline conditional syntax, I did try and scope down the various nonterminating discussions into an MVP that can actually be implemented quickly, discussed ideas with implemenators, and eventually published a concrete proposal and pushed for group resolution. Quite poetically, the relevant discussion occurred on my birthday, so in a way, I got if() as the most unique birthday present ever. This also comes to show that proposals being rejected is not the end-all for a given feature. It is in fact quite common for features to be rejected for several times before they are accepted: CSS Nesting, :has() , container queries were all simply the last iteration in a series of rejected proposals. if() itself was apparently rejected in 2018 with very similar syntax to what I proposed. What was the difference? Style queries had already shipped, and we could simply reference the same syntax for conditions (plus media() and supports() from Tabs @when proposal ) whereas in the 2018 proposal how conditions would work was largely undefined. I posted about this on a variety of social media, and the response by developers has been overwhelmingly positive: Twitter LinkedIn Mastodon I even had friends from big companies writing to tell me their internal Slacks blew up about it. This proves what Ive always suspected, and was part of the case I made to the CSS WG: that this is a huge pain point . Hopefully the amount and intensity of positive reactions will help browsers prioritize this feature and add it to their roadmaps earlier rather than later. Across all these platforms, besides the I cant wait for this to ship! sentiment being most common, there were a few other recurring questions and a fair bit of confusion that I figured were worth addressing. FAQ What is if() for? Does it replace style queries? Quite the opposite if() complements style queries . If you can do something with style queries, by all means, use style queries they are almost certainly a better solution. But there are things you simply cannot do with style queries. Let me explain. The motivating use case was that components (in the broader sense) often need to define higher level custom properties , whose values are not just used verbatim in declarations, but that set unrelated values on a variety of declarations. For example, consider a --variant custom property (inspired from Shoelaces variant attribute ). It could look like this: --variant: success | danger | warning | primary | none; This needs to set background colors, border colors, text colors, icons, etc. In fact, its actual value is not used verbatim anywhere, it is only used to set other values. Style queries get us halfway there: .callout { /* or :host if in Shadow DOM */ @container (style(--variant: success)) { &amp;::before { content: var(--icon-success); color: var(--color-success); } } /* (other variants) */ } However, style queries only work on descendants. We cannot do this: .callout { @container (style(--variant: success)) { border-color: var(--color-success-30); background-color: var(--color-success-95); &amp;::before { content: var(--icon-success); color: var(--color-success-05); } } /* (other variants) */ } Often the declarations we need to set on the element itself are very few, sometimes even just one. However, even one is one too many and makes using custom properties untenable for many (possibly most) higher level custom property use cases. As a result, component libraries end up resorting to presentational attributes like pill , outline , size , etc. While presentational attributes may seem fine at first glance, or even better for DX (fewer characters at least compared to setting a variable per element), they have several usability issues: Reduced flexibility They cannot be conditionally applied based on selectors, media queries, etc. Changing them requires more JS. If they are used within another component, youre SOL, whereas with (inheritable) custom properties, you can set the property on the parent component and it will inherit down. Verbosity They have to be applied to individual instances, and cannot be inherited. Even if one uses some form of templating or componentization to reduce duplication, they still have to wade through these attributes when debugging with dev tools. Lack of consistency Since almost every mature component also supports custom properties, users have to remember which styling is done via attributes and which via custom properties. The distinction is often arbitrary, as its not driven by use cases, but implementation convenience. With if() , the above example becomes possible, albeit with worse ergonomics than style queries since it cannot cascade (though I do have a proposal to allow it to plus all other IACVT declarations): .callout { border-color: if( style(--variant: success) ? var(--color-success-30) : style(--variant: danger) ? var(--color-danger-30) : /* (other variants) */ var(--color-neutral-30) ); background-color: if( style(--variant: success) ? var(--color-success-95) : style(--variant: danger) ? var(--color-danger-95) : /* (other variants) */ var(--color-neutral-95) ); @container (style(--variant: success)) { &amp;::before { content: var(--icon-success); color: var(--color-success-05); } } /* (other variants) */ } While this was the primary use case, it turned out that its pretty easy to also make media queries and supports conditions part of if() s conditional syntax. And since its a function, its arguments (including the condition!) can be stored in other custom properties. This means you can do things like this: :root { --xl: media(width &gt; 1600px); --l: media (width &gt; 1200px); --m: media (width &gt; 800px); } and then define values like: padding: if( var(--xl) ? var(--size-3) : var(--l) or var(--m) ? var(--size-2) : var(--size-1) ); Just like ternaries in JS, it may also be more ergonomic for cases where only a small part of the value varies: animation: if(media(prefers-reduced-motion) ? 10s : 1s) rainbow infinite; So is it in browsers yet? Believe it or not, that was a real question I got . No, its not in browsers yet, and it wont be for a while. The most optimistic estimate is 2 years or so, if the process doesnt stall at any point (as it often does). All we have is consensus to work on the feature. The next steps are: Reach consensus on the syntax of the feature. Syntax debates can often take a very long time, because syntax is an area where everyone has opinions. The current debates revolve around: What separators to use between the condition and the branches? How to represent no value? Do we simply allow empty values like in var() (where you can do var(--foo,) ) or do we introduce a dedicated syntax that means empty value ? Should the last value be optional? Spec the feature. Get the first implementation. Often that is the hardest part. Once one browser implements, it is far easier to get the others on board. Get it shipped across all major browsers. I do have a page where I track some of my standards proposals which should help illuminate what the timeline looks like for each of these steps. In fact, you can track the progress of if() specifically there too. Do note that these steps are not necessarily linear. Often we spec an initial version, then resolve on a different syntax and update the spec. Sometimes browsers even implement the early syntax, and then it changes and they have to change their implementations (as happened with Nesting ) Want to make this happen faster? Excited about if() and want to show gratitude? Fund my web standards work on Open Collective . I started this while writing this blog post as an experiment, and dont plan to promote it much, but its there if you are so inclined. Is this the first conditional in CSS? Many responses were along the lines of Wow, CSS is finally getting conditionals!. Folks CSS had conditionals from the very beginning. Every selector is essentially a conditional! In addition: @media and @supports rules are conditionals. And lets not forget @container . var(--foo, fallback) is a limited type of conditional (essentially if(style(--foo: initial) ? var(--foo) : fallback) ), hence why its the basis of most workarounds for emulating inline conditionals. Does this make CSS imperative? A widespread misconception is that non-linear logic (conditionals, loops) makes a language imperative. Declarative vs imperative is not about logic, but level of abstraction. Are we describing the goal or how to achieve it? In culinary terms, a recipe is imperative, a restaurant menu is declarative Conditional logic can actually make a language more declarative if it helps describe intent better. Consider the following two snippets of CSS: Space toggle if() button { border-radius: calc(.2em + var(--pill, 999em)); } .fancy.button { /* Turn pill on */ --pill: initial; } button { border-radius: if(style(--shape: pill) ? 999em : .2em); } .fancy.button { --shape: pill; } I would argue the latter is far more declarative, i.e. much closer to specifying the goal rather than how to achieve it. Does this make CSS a programming language? A very common type of response was around whether CSS is now a programming language (either asking whether it is, or asserting that it now is). To answer that, one first needs to answer what a programming language is . If its Turing-completeness that makes a language a programming language, then CSS has been a programming language for over a decade . But then again, so is Excel or Minecraft . So what does that even mean? If its imperativeness, then no, CSS is not a programming language. But neither are many actual programming languages! But a deeper question is, why does it matter ? Is it because it legitimizes choosing to specialize in CSS? It is because you can then be considered a programmer even if you only write HTML &amp; CSS? If this only matters for optics, then we should fix the issue at its core and fight to legitimize CSS expertise regardless of whether CSS is a programming language. After all, as anyone who knows several well-respected programming languages and CSS can attest, CSS is far harder to master. Great as all this may be, it wont be in browsers for a while. What can we do right now ? I wrote Part 2 exactly about that: CSS Conditionals, now?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On compliance vs readability: Generating text colors with CSS</title>
  <link>https://lea.verou.me/blog/2024/contrast-color/</link>
  <pubDate>Sun, 17 May 2026 03:45:07 +0200</pubDate>
  <description>Can we emulate the upcoming CSS contrast-color() function via CSS features that have already widely shipped? And if so, what are the tradeoffs involved and how to best balance them? Relative Colors Out of all the CSS features I have designed , Relative Colors aka Relative Color Syntax (RCS) is definitely among the ones I’m most proud of. In a nutshell, they allow CSS authors to derive a new color from an existing color value by doing arbitrary math on color components in any supported color space: --color-lighter: hsl(from var(--color) h s calc(l * 1.2)); --color-lighterer: oklch(from var(--color) calc(l + 0.2) c h); --color-alpha-50: oklab(from var(--color) l a b / 50%); The elevator pitch was that by allowing lower level operations they provide authors flexibility on how to derive color variations, giving us more time to figure out what the appropriate higher level primitives should be. As of May 2024, RCS has shipped in every browser except Firefox . but given that it is an Interop 2024 focus area , that Firefox has expressed a positive standards position , and that the Bugzilla issue has had some recent activity and has been assigned, I am optimistic it would ship in Firefox soon (edit: it shipped 5 days after writing these lines, in Firefox 128 🎉). My guess it that it would become Baseline by the end of 2024. Even if my prediction is off, it already is available to 83% of users worldwide , and if you sort its caniuse page by usage, you will see the vast majority of the remaining 17% doesn’t come from Firefox, but from older Chrome and Safari versions. I think its current market share warrants production use today , as long as we use @supports to make sure things work in non-supporting browsers, even if less pretty. Most Relative Colors tutorials revolve around its primary driving use cases: making tints and shades or other color variations by tweaking a specific color component up or down, and/or overriding a color component with a fixed value, like the example above. While this does address some very common pain points, it is merely scratching the surface of what RCS enables. This article explores a more advanced use case, with the hope that it will spark more creative uses of RCS in the wild. The CSS contrast-color() function One of the big longstanding CSS pain points is that it’s impossible to automatically specify a text color that is guaranteed to be readable on arbitrary backgrounds, e.g. white on darker colors and black on lighter ones. Why would one need that? The primary use case is when colors are outside the CSS author’s control . This includes: User-defined colors. An example you’re likely familiar with: GitHub labels. Think of how you select an arbitrary color when creating a label and GitHub automatically picks the text color — often poorly (we’ll see why in a bit) Colors defined by another developer. E.g. you’re writing a web component that supports certain CSS variables for styling. You could require separate variables for the text and background, but that reduces the usability of your web component by making it more of a hassle to use. Wouldn’t it be great if it could just use a sensible default , that you can, but rarely need to override? Colors defined by an external design system , like Open Props , Material Design , or even ( gasp ) Tailwind . GitHub Labels are an example where colors are user-defined, and the UI needs to pick a text color that works with them. GitHub uses WCAG 2.1 to determine the text color, which is why (as we will see in the next section) the results are often poor. Even in a codebase where every line of CSS code is controlled by a single author, reducing couplings can improve modularity and facilitate code reuse. The good news is that this is not going to be a pain point for much longer. The CSS function contrast-color() was designed to address exactly that. This is not new, you may have heard of it as color-contrast() before, an earlier name. I recently drove consensus to scope it down to an MVP that addresses the most prominent pain points and can actually ship soonish, as it circumvents some very difficult design decisions that had caused the full-blown feature to stall. I then added it to the spec per WG resolution, though some details still need to be ironed out. Usage will look like this: background: var(--color); color: contrast-color(var(--color)); Glorious, isn’t it? Of course, soonish in spec years is still, well, years. As a data point, you can see in my past spec work that with a bit of luck (and browser interest), it can take as little as 2 years to get a feature shipped across all major browsers after it’s been specced. When the standards work is also well-funded, there have even been cases where a feature went from conception to baseline in 2 years , with Cascade Layers being the poster child for this: proposal by Miriam in Oct 2019 , shipped in every major browser by Mar 2022 . But 2 years is still a long time (and there are no guarantees it won’t be longer). What is our recourse until then? As you may have guessed from the title, the answer is yes. It may not be pretty, but there is a way to emulate contrast-color() (or something close to it) using Relative Colors. Using RCS to automatically compute a contrasting text color In the following we will use the OKLCh color space , which is the most perceptually uniform polar color space that CSS supports. Perceptually uniform color space : A color space where the Euclidean distance between two colors is proportional to their perceptual difference. RGB spaces (and their polar forms, HSL, HSV, HSB, HWB, etc.) are typically not perceptually uniform. Some examples of what this means for HSL in my older post on LCH . Examples of perceptually uniform color spaces include Lab, LCH, OkLab, and OkLCh. Polar color space : A color space where colors are represented as an angular hue (which determines the “core” color, e.g. red, yellow, green, blue, etc.) and two components that control the exact shade of that hue (typically some version of colorfulness and lightness). Let’s assume there is a Lightness value above which black text is guaranteed to be readable regardless of the chroma and hue, and below which white text is guaranteed to be readable. We will validate that assumption later, but for now let’s take it for granted. In the rest of this article, we’ll call that value the threshold and represent it as L threshold . We will compute this value more rigously in the next section (and prove that it actually exists!), but for now let’s use 0.7 (70%). We can assign it to a variable to make it easier to tweak: --l-threshold: 0.7; Most RCS examples in the wild use calc() with simple additions and multiplications. However, any math function supported by CSS is actually fair game , including clamp() , trigonometric functions, and many others. For example, if you wanted to create a lighter tint of a core color with RCS, you could do something like this: background: oklch(from var(--color) 90% clamp(0, c, 0.1) h); Let’s work backwards from the desired result. We want to come up with an expression that is composed of widely supported CSS math functions , and will return 1 if L ≤ L threshold and 0 otherwise. If we could write such an expression, we could then use that value as the lightness of a new color: --l: /* ??? */; color: oklch(var(--l) 0 0); The CSS math functions that are widely supported are: calc() min() , max() , clamp() Trigonometric functions ( sin() , cos() , tan() , asin() , acos() , atan() , atan2() ) ( another CSS feature I proposed , back in 2018 😁) Exponential functions ( exp() , log() , log2() , log10() , sqrt() ) How could we simplify the task? One way is to relax what our expression needs to return . We don’t actually need an exact 0 or 1 If we can manage to find an expression that will give us 0 when L &gt; L threshold and &gt; 1 when L ≤ L threshold , we can just use clamp(0, /* expression */, 1) to get the desired result. One idea would be to use ratios, as they have this nice property where they are &gt; 1 if the numerator is larger than the denominator and ≤ 1 otherwise. The ratio of L L threshold is 1 for L ≤ L threshold and &gt; 1 when L &gt; L threshold . This means that L L threshold − 1 will be a negative number for L L threshold and a positive one for L &gt; L threshold . Then all we need to do is multiply that expression by a huge (in magnitude) negative number so that when it’s negative the result is guaranteed to be over 1 . Putting it all together, it looks like this: --l-threshold: 0.7; --l: clamp(0, (l / var(--l-threshold) - 1) * -infinity, 1); color: oklch(from var(--color) var(--l) 0 h); One worry might be that if L gets close enough to the threshold we could get a number between 0 - 1 , but in my experiments this never happened, presumably since precision is finite. Fallback for browsers that don’t support RCS The last piece of the puzzle is to provide a fallback for browsers that don’t support RCS. We can use @supports with any color property and any relative color value as the test, e.g.: .contrast-color { /* Fallback */ background: hsl(0 0 0 / 50%); color: white; @supports (color: oklch(from red l c h)) { --l: clamp(0, (l / var(--l-threshold) - 1) * -infinity, 1); color: oklch(from var(--color) var(--l) 0 h); background: none; } } In the spirit of making sure things work in non-supporting browsers, even if less pretty, some fallback ideas could be: A white or semi-transparent white background with black text or vice versa. -webkit-text-stroke with a color opposite to the text color. This works better with bolder text, since half of the outline is inside the letterforms. Many text-shadow values with a color opposite to the text color. This works better with thinner text, as it’s drawn behind the text. Does this mythical L threshold actually exist? In the previous section we’ve made a pretty big assumption: That there is a Lightness value ( L threshold ) above which black text is guaranteed to be readable regardless of the chroma and hue, and below which white text is guaranteed to be readable regardless of the chroma and hue. But does such a value exist? It is time to put this claim to the test. When people first hear about perceptually uniform color spaces like Lab , LCH or their improved versions, OkLab and OKLCH , they imagine that they can infer the contrast between two colors by simply comparing their L(ightness) values. This is unfortunately not true, as contrast depends on more factors than perceptual lightness. However, there is certainly significant correlation between Lightness values and contrast. At this point, I should point out that while most web designers are aware of the WCAG 2.1 contrast algorithm , which is part of the Web Content Accessibility Guidelines and baked into law in many countries, it has been known for years that it produces extremely poor results . So bad in fact that in some tests it performs almost as bad as random chance for any color that is not very light or very dark. There is a newer contrast algorithm, APCA that produces far better results, but is not yet part of any standard or legislation, and there have previously been some bumps along the way with making it freely available to the public (which seem to be largely resolved). Some text Some text Which of the two seems more readable? You may be surprised to find that the white text version fails WCAG 2.1, while the black text version even passes WCAG AAA! So where does that leave web authors? In quite a predicament as it turns out. It seems that the best way to create accessible color pairings right now is a two step process: Use APCA to ensure actual readability Compliance failsafe : Ensure the result does not actively fail WCAG 2.1. I ran some quick experiments using Color.js where I iterate over the OKLCh reference range (loosely based on the P3 gamut) in increments of increasing granularity and calculate the lightness ranges for colors where white was the “best” text color (= produced higher contrast than black) and vice versa. I also compute the brackets for each level (fail, AA, AAA, AAA+) for both APCA and WCAG. I then turned my exploration into an interactive playground where you can run the same experiments yourself, potentially with narrower ranges that fit your use case, or with higher granularity. Calculating lightness ranges and contrast brackets for black and white on different background colors. This is the table produced with C ∈ [0, 0.4] (step = 0.025) and H ∈ [0, 360) (step = 1) : Text color Level APCA WCAG 2.1 Min Max Min Max white best 0% 75.2% 0% 61.8% fail 71.6% 100% 62.4% 100% AA 62.7% 80.8% 52.3% 72.1% AAA 52.6% 71.7% 42% 62.3% AAA+ 0% 60.8% 0% 52.7% black best 66.1% 100% 52% 100% fail 0% 68.7% 0% 52.7% AA 60% 78.7% 42% 61.5% AAA 69.4% 87.7% 51.4% 72.1% AAA+ 78.2% 100% 62.4% 100% Note that these are the min and max L values for each level. E.g. the fact that white text can fail WCAG when L ∈ [62.4%, 100%] doesn’t mean that every color with L &gt; 62.4% will fail WCAG, just that some do. So, we can only draw meaningful conclusions by inverting the logic : Since all white text failures are have an L ∈ [62.4%, 100%], it logically follows that if L By applying this logic to all ranges, we can draw similar guarantees for many of these brackets: 0% to 52.7% 52.7% to 62.4% 62.4% to 66.1% 66.1% to 68.7% 68.7% to 71.6% 71.6% to 75.2% 75.2% to 100% Compliance WCAG 2.1 white ✅ AA ✅ AA black ✅ AA ✅ AAA ✅ AAA ✅ AAA ✅ AAA ✅ AAA+ Readability APCA white 😍 Best 😍 Best 😍 Best 🙂 OK 🙂 OK black 🙂 OK 🙂 OK 😍 Best Contrast guarantees we can infer for black and white text over arbitrary colors. OK = passes but is not necessarily best. You may have noticed that in general, WCAG has a lot of false negatives around white text, and tends to place the Lightness threshold much lower than APCA. This is a known issue with the WCAG algorithm. Therefore, to best balance readability and compliance, we should use the highest threshold we can get away with . This means: If passing WCAG is a requirement, the highest threshold we can use is 62.3% . If actual readability is our only concern, we can safely ignore WCAG and pick a threshold somewhere between 68.7% and 71.6%, e.g. 70% . Here’s a demo so you can see how they both play out. Edit the color below to see how the two thresholds work in practice, and compare with the actual contrast brackets, shown on the table next to (or below) the color picker. .contrast-color { --text-shadow: 0 0 .05em black; --text-shadow-2: var(--text-shadow), var(--text-shadow); --text-shadow-4: var(--text-shadow-2), var(--text-shadow-2); --text-shadow-8: var(--text-shadow-4), var(--text-shadow-4); text-shadow: var(--text-shadow-8); color: white; } #demo { display: flex; gap: 2em; padding-block: 2rem; color-picker { flex: 1; &amp;::part(swatch) { flex: 1.8; } } input[type=number] { field-sizing: content; background: none; color: inherit; border: .1em solid; border-radius: .2em; } table { width: auto; margin-bottom: 0; } @media (width Your browser does not support Relative Color Syntax, so the demo below will not work. This is what it looks like in a supporting browser: import { getLevel } from &quot;./research/util.js&quot;; globalThis.updateContrasts = function (color) { if (!color) { return; } for (let td of document.querySelectorAll(&quot;[data-algo]&quot;)) { let {algo, color: textColor} = td.dataset; let level = getLevel(algo, color.contrast(textColor, algo)); td.textContent = level; td.className = level === &quot;fail&quot; ? &quot;fail&quot; : &quot;pass&quot;; } } L threshold = 70% L threshold = % L threshold = 62.3% Actual contrast ratios Text color APCA WCAG 2.1 White Black Avoid colors marked “P3+”, “PP” or “PP+”, as these are almost certainly outside your screen gamut, and browsers currently do not gamut map properly , so the visual result will be off. The component above is part of a new project I’m working on called Color Elements ( color-elements on npm). It’s a ( highly experimental) collection of web components that make it easy to make color-related apps and demos. If that seems interesting, feel free to try them out and provide feedback! Note that if your actual color is more constrained (e.g. a subset of hues or chromas or a specific gamut), you might be able to balance these tradeoffs better by using a different threshold. Run the experiment yourself with your actual range of colors and find out! Here are some examples of narrower ranges I have tried and the highest threshold that still passes WCAG 2.1: Description Color range Threshold Modern low-end screens Colors within the sRGB gamut 65% Modern high-end screens Colors within the P3 gamut 64.5% Future high-end screens Colors within the Rec.2020 gamut 63.4% Neutrals C ∈ [0, 0.03] 67% Muted colors C ∈ [0, 0.1] 65.6% Warm colors (reds/oranges/yellows) H ∈ [0, 100] 66.8% Pinks/Purples H ∈ [300, 370] 67% It is particularly interesting that the threshold is improved to 64.5% by just ignoring colors that are not actually displayable on modern screens. So, assuming (though sadly this is not an assumption that currently holds true ) that browsers prioritize preserving lightness when gamut mapping, we could use 64.5% and still guarantee WCAG compliance. You can even turn this into a utility class that you can combine with different thesholds: .contrast-color { --l: clamp(0, (l / var(--l-threshold, 0.623) - 1) * -infinity, 1); color: oklch(from var(--color) var(--l) 0 h); } .pink { --l-threshold: 0.67; } Conclusion &amp; Future work Putting it all together, including a fallback, as well as a “fall forward” that uses contrast-color() , the utility class could look like this: .contrast-color { /* Fallback for browsers that don&#39;t support RCS */ color: white; text-shadow: 0 0 .05em black, 0 0 .05em black, 0 0 .05em black, 0 0 .05em black; @supports (color: oklch(from red l c h)) { --l: clamp(0, (l / var(--l-threshold, 0.623) - 1) * -infinity, 1); color: oklch(from var(--color) var(--l) 0 h); text-shadow: none; } @supports (color: contrast-color(red)) { color: contrast-color(var(--color)); text-shadow: none; } } This is only a start. I can imagine many directions for improvement such as: Since RCS allows us to do math with any of the color components in any color space, I wonder if there is a better formula that still be implemented in CSS and balances readability and compliance even better. E.g. I’ve had some chats with Andrew Somers (creator of APCA) right before publishing this, which suggest that doing math on luminance (the Y component of XYZ) instead could be a promising direction. We currently only calculate thresholds for white and black text. However, in real designs, we rarely want pure black text, which is why contrast-color() only guarantees a “very light or very dark color” unless the max keyword is used. How would this extend to darker tints of the background color? Addendum As often happens, after publishing this blog post, a ton of folks reached out to share all sorts of related work in the space. I thought I’d share some of the most interesting findings here. Using luminance instead of Lightness When colors have sufficiently different lightness values (as happens with white or black text), humans disregard chromatic contrast (the contrast that hue/colorfulness provide) and basically only use lightness contrast to determine readability. This is why L can be such a good predictor of whether white or black text works best. Another measure, luminance, is basically the color’s Y component in the XYZ color space, and a good threshold for flipping to black text is when Y &gt; 0.36. This gives us another method for computing a text color: --y-threshold: 0.36; --y: clamp(0, (y / var(--y-threshold) - 1) * -infinity, 1); color: color(from var(--color) xyz-d65 var(--y) var(--y) var(--y)); As you can see in this demo by Lloyd Kupchanko , using Y threshold &gt; 36% very closely predicts the best text color as determined by APCA. In my tests ( codepen ) it appeared to work as well as the L threshold method, i.e. it was a struggle to find colors where they disagree. However, after this blog post, Lloyd added various L threshold boundaries to his demo, and it appears that indeed, L threshold has a wider range where it disagrees with APCA than Y threshold does. Given this, my recommendation would be to use the Y threshold method if you need to flip between black and white text, and the L threshold method if you need to customize the text color further (e.g. have a very dark color instead of black). Browser bug &amp; workarounds About a week after publishing this post, I discovered a browser bug with color-mix() and RCS, where colors defined via color-mix() used in from render RCS invalid. You can use this testcase to see if a given browser is affected. This has been fixed in Chrome 125 and Safari TP release 194, but it certainly throws a spanner in the works since the whole point of using this technique is that we don’t have to care how the color was defined. There are two ways to work around this: Adjust the @supports condition to use color-mix() , like so: @supports (color: oklch(from color-mix(in oklch, red, tan) l c h)) { /* ... */ } The downside is that right now, this would restrict the set of browsers this works in to a teeny tiny set. 2. Register the custom property that contains the color: @property --color { syntax: &quot; &quot;; inherits: true; initial-value: transparent; } This completely fixes it, since if the property is registered, by the time the color hits RCS, it’s just a resolved color value. @property is currently supported by a much wider set of browsers than RCS, so this workaround doesn’t hurt compatiblity at all. Useful resources Many people have shared useful resources on the topic, such as: Black or White? : Compare different contrast algorithms for picking between black or white Dynamic text color contrast based on background lightness with CSS/SVG filters : A different approach to the same problem (requires extra HTML element for the text) Thanks to Chris Lilley , Andrew Somers , Cory LaViska , Elika Etemad , and Tab Atkins-Bittner for their feedback on earlier drafts of this article.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On compliance vs readability: Generating text colors with CSS</title>
  <link>https://lea.verou.me/blog/2024/contrast-color/</link>
  <pubDate>Sun, 17 May 2026 03:45:07 +0200</pubDate>
  <description>Can we emulate the upcoming CSS contrast-color() function via CSS features that have already widely shipped? And if so, what are the tradeoffs involved and how to best balance them? Relative Colors Out of all the CSS features I have designed , Relative Colors aka Relative Color Syntax (RCS) is definitely among the ones Im most proud of. In a nutshell, they allow CSS authors to derive a new color from an existing color value by doing arbitrary math on color components in any supported color space: --color-lighter: hsl(from var(--color) h s calc(l * 1.2)); --color-lighterer: oklch(from var(--color) calc(l + 0.2) c h); --color-alpha-50: oklab(from var(--color) l a b / 50%); The elevator pitch was that by allowing lower level operations they provide authors flexibility on how to derive color variations, giving us more time to figure out what the appropriate higher level primitives should be. As of May 2024, RCS has shipped in every browser except Firefox . but given that it is an Interop 2024 focus area , that Firefox has expressed a positive standards position , and that the Bugzilla issue has had some recent activity and has been assigned, I am optimistic it would ship in Firefox soon (edit: it shipped 5 days after writing these lines, in Firefox 128 ). My guess it that it would become Baseline by the end of 2024. Even if my prediction is off, it already is available to 83% of users worldwide , and if you sort its caniuse page by usage, you will see the vast majority of the remaining 17% doesnt come from Firefox, but from older Chrome and Safari versions. I think its current market share warrants production use today , as long as we use @supports to make sure things work in non-supporting browsers, even if less pretty. Most Relative Colors tutorials revolve around its primary driving use cases: making tints and shades or other color variations by tweaking a specific color component up or down, and/or overriding a color component with a fixed value, like the example above. While this does address some very common pain points, it is merely scratching the surface of what RCS enables. This article explores a more advanced use case, with the hope that it will spark more creative uses of RCS in the wild. The CSS contrast-color() function One of the big longstanding CSS pain points is that its impossible to automatically specify a text color that is guaranteed to be readable on arbitrary backgrounds, e.g. white on darker colors and black on lighter ones. Why would one need that? The primary use case is when colors are outside the CSS authors control . This includes: User-defined colors. An example youre likely familiar with: GitHub labels. Think of how you select an arbitrary color when creating a label and GitHub automatically picks the text color often poorly (well see why in a bit) Colors defined by another developer. E.g. youre writing a web component that supports certain CSS variables for styling. You could require separate variables for the text and background, but that reduces the usability of your web component by making it more of a hassle to use. Wouldnt it be great if it could just use a sensible default , that you can, but rarely need to override? Colors defined by an external design system , like Open Props , Material Design , or even ( gasp ) Tailwind . GitHub Labels are an example where colors are user-defined, and the UI needs to pick a text color that works with them. GitHub uses WCAG 2.1 to determine the text color, which is why (as we will see in the next section) the results are often poor. Even in a codebase where every line of CSS code is controlled by a single author, reducing couplings can improve modularity and facilitate code reuse. The good news is that this is not going to be a pain point for much longer. The CSS function contrast-color() was designed to address exactly that. This is not new, you may have heard of it as color-contrast() before, an earlier name. I recently drove consensus to scope it down to an MVP that addresses the most prominent pain points and can actually ship soonish, as it circumvents some very difficult design decisions that had caused the full-blown feature to stall. I then added it to the spec per WG resolution, though some details still need to be ironed out. Usage will look like this: background: var(--color); color: contrast-color(var(--color)); Glorious, isnt it? Of course, soonish in spec years is still, well, years. As a data point, you can see in my past spec work that with a bit of luck (and browser interest), it can take as little as 2 years to get a feature shipped across all major browsers after its been specced. When the standards work is also well-funded, there have even been cases where a feature went from conception to baseline in 2 years , with Cascade Layers being the poster child for this: proposal by Miriam in Oct 2019 , shipped in every major browser by Mar 2022 . But 2 years is still a long time (and there are no guarantees it wont be longer). What is our recourse until then? As you may have guessed from the title, the answer is yes. It may not be pretty, but there is a way to emulate contrast-color() (or something close to it) using Relative Colors. Using RCS to automatically compute a contrasting text color In the following we will use the OKLCh color space , which is the most perceptually uniform polar color space that CSS supports. Perceptually uniform color space : A color space where the Euclidean distance between two colors is proportional to their perceptual difference. RGB spaces (and their polar forms, HSL, HSV, HSB, HWB, etc.) are typically not perceptually uniform. Some examples of what this means for HSL in my older post on LCH . Examples of perceptually uniform color spaces include Lab, LCH, OkLab, and OkLCh. Polar color space : A color space where colors are represented as an angular hue (which determines the core color, e.g. red, yellow, green, blue, etc.) and two components that control the exact shade of that hue (typically some version of colorfulness and lightness). Lets assume there is a Lightness value above which black text is guaranteed to be readable regardless of the chroma and hue, and below which white text is guaranteed to be readable. We will validate that assumption later, but for now lets take it for granted. In the rest of this article, well call that value the threshold and represent it as L threshold . We will compute this value more rigously in the next section (and prove that it actually exists!), but for now lets use 0.7 (70%). We can assign it to a variable to make it easier to tweak: --l-threshold: 0.7; Most RCS examples in the wild use calc() with simple additions and multiplications. However, any math function supported by CSS is actually fair game , including clamp() , trigonometric functions, and many others. For example, if you wanted to create a lighter tint of a core color with RCS, you could do something like this: background: oklch(from var(--color) 90% clamp(0, c, 0.1) h); Lets work backwards from the desired result. We want to come up with an expression that is composed of widely supported CSS math functions , and will return 1 if L L threshold and 0 otherwise. If we could write such an expression, we could then use that value as the lightness of a new color: --l: /* ??? */; color: oklch(var(--l) 0 0); The CSS math functions that are widely supported are: calc() min() , max() , clamp() Trigonometric functions ( sin() , cos() , tan() , asin() , acos() , atan() , atan2() ) ( another CSS feature I proposed , back in 2018 ) Exponential functions ( exp() , log() , log2() , log10() , sqrt() ) How could we simplify the task? One way is to relax what our expression needs to return . We dont actually need an exact 0 or 1 If we can manage to find an expression that will give us 0 when L &gt; L threshold and &gt; 1 when L L threshold , we can just use clamp(0, /* expression */, 1) to get the desired result. One idea would be to use ratios, as they have this nice property where they are &gt; 1 if the numerator is larger than the denominator and 1 otherwise. The ratio of L L threshold is 1 for L L threshold and &gt; 1 when L &gt; L threshold . This means that L L threshold 1 will be a negative number for L L threshold and a positive one for L &gt; L threshold . Then all we need to do is multiply that expression by a huge (in magnitude) negative number so that when its negative the result is guaranteed to be over 1 . Putting it all together, it looks like this: --l-threshold: 0.7; --l: clamp(0, (l / var(--l-threshold) - 1) * -infinity, 1); color: oklch(from var(--color) var(--l) 0 h); One worry might be that if L gets close enough to the threshold we could get a number between 0 - 1 , but in my experiments this never happened, presumably since precision is finite. Fallback for browsers that dont support RCS The last piece of the puzzle is to provide a fallback for browsers that dont support RCS. We can use @supports with any color property and any relative color value as the test, e.g.: .contrast-color { /* Fallback */ background: hsl(0 0 0 / 50%); color: white; @supports (color: oklch(from red l c h)) { --l: clamp(0, (l / var(--l-threshold) - 1) * -infinity, 1); color: oklch(from var(--color) var(--l) 0 h); background: none; } } In the spirit of making sure things work in non-supporting browsers, even if less pretty, some fallback ideas could be: A white or semi-transparent white background with black text or vice versa. -webkit-text-stroke with a color opposite to the text color. This works better with bolder text, since half of the outline is inside the letterforms. Many text-shadow values with a color opposite to the text color. This works better with thinner text, as its drawn behind the text. Does this mythical L threshold actually exist? In the previous section weve made a pretty big assumption: That there is a Lightness value ( L threshold ) above which black text is guaranteed to be readable regardless of the chroma and hue, and below which white text is guaranteed to be readable regardless of the chroma and hue. But does such a value exist? It is time to put this claim to the test. When people first hear about perceptually uniform color spaces like Lab , LCH or their improved versions, OkLab and OKLCH , they imagine that they can infer the contrast between two colors by simply comparing their L(ightness) values. This is unfortunately not true, as contrast depends on more factors than perceptual lightness. However, there is certainly significant correlation between Lightness values and contrast. At this point, I should point out that while most web designers are aware of the WCAG 2.1 contrast algorithm , which is part of the Web Content Accessibility Guidelines and baked into law in many countries, it has been known for years that it produces extremely poor results . So bad in fact that in some tests it performs almost as bad as random chance for any color that is not very light or very dark. There is a newer contrast algorithm, APCA that produces far better results, but is not yet part of any standard or legislation, and there have previously been some bumps along the way with making it freely available to the public (which seem to be largely resolved). Some text Some text Which of the two seems more readable? You may be surprised to find that the white text version fails WCAG 2.1, while the black text version even passes WCAG AAA! So where does that leave web authors? In quite a predicament as it turns out. It seems that the best way to create accessible color pairings right now is a two step process: Use APCA to ensure actual readability Compliance failsafe : Ensure the result does not actively fail WCAG 2.1. I ran some quick experiments using Color.js where I iterate over the OKLCh reference range (loosely based on the P3 gamut) in increments of increasing granularity and calculate the lightness ranges for colors where white was the best text color (= produced higher contrast than black) and vice versa. I also compute the brackets for each level (fail, AA, AAA, AAA+) for both APCA and WCAG. I then turned my exploration into an interactive playground where you can run the same experiments yourself, potentially with narrower ranges that fit your use case, or with higher granularity. Calculating lightness ranges and contrast brackets for black and white on different background colors. This is the table produced with C [0, 0.4] (step = 0.025) and H [0, 360) (step = 1) : Text color Level APCA WCAG 2.1 Min Max Min Max white best 0% 75.2% 0% 61.8% fail 71.6% 100% 62.4% 100% AA 62.7% 80.8% 52.3% 72.1% AAA 52.6% 71.7% 42% 62.3% AAA+ 0% 60.8% 0% 52.7% black best 66.1% 100% 52% 100% fail 0% 68.7% 0% 52.7% AA 60% 78.7% 42% 61.5% AAA 69.4% 87.7% 51.4% 72.1% AAA+ 78.2% 100% 62.4% 100% Note that these are the min and max L values for each level. E.g. the fact that white text can fail WCAG when L [62.4%, 100%] doesnt mean that every color with L &gt; 62.4% will fail WCAG, just that some do. So, we can only draw meaningful conclusions by inverting the logic : Since all white text failures are have an L [62.4%, 100%], it logically follows that if L By applying this logic to all ranges, we can draw similar guarantees for many of these brackets: 0% to 52.7% 52.7% to 62.4% 62.4% to 66.1% 66.1% to 68.7% 68.7% to 71.6% 71.6% to 75.2% 75.2% to 100% Compliance WCAG 2.1 white AA AA black AA AAA AAA AAA AAA AAA+ Readability APCA white Best Best Best OK OK black OK OK Best Contrast guarantees we can infer for black and white text over arbitrary colors. OK = passes but is not necessarily best. You may have noticed that in general, WCAG has a lot of false negatives around white text, and tends to place the Lightness threshold much lower than APCA. This is a known issue with the WCAG algorithm. Therefore, to best balance readability and compliance, we should use the highest threshold we can get away with . This means: If passing WCAG is a requirement, the highest threshold we can use is 62.3% . If actual readability is our only concern, we can safely ignore WCAG and pick a threshold somewhere between 68.7% and 71.6%, e.g. 70% . Heres a demo so you can see how they both play out. Edit the color below to see how the two thresholds work in practice, and compare with the actual contrast brackets, shown on the table next to (or below) the color picker. .contrast-color { --text-shadow: 0 0 .05em black; --text-shadow-2: var(--text-shadow), var(--text-shadow); --text-shadow-4: var(--text-shadow-2), var(--text-shadow-2); --text-shadow-8: var(--text-shadow-4), var(--text-shadow-4); text-shadow: var(--text-shadow-8); color: white; } #demo { display: flex; gap: 2em; padding-block: 2rem; color-picker { flex: 1; &amp;::part(swatch) { flex: 1.8; } } input[type=number] { field-sizing: content; background: none; color: inherit; border: .1em solid; border-radius: .2em; } table { width: auto; margin-bottom: 0; } @media (width Your browser does not support Relative Color Syntax, so the demo below will not work. This is what it looks like in a supporting browser: import { getLevel } from &quot;./research/util.js&quot;; globalThis.updateContrasts = function (color) { if (!color) { return; } for (let td of document.querySelectorAll(&quot;[data-algo]&quot;)) { let {algo, color: textColor} = td.dataset; let level = getLevel(algo, color.contrast(textColor, algo)); td.textContent = level; td.className = level === &quot;fail&quot; ? &quot;fail&quot; : &quot;pass&quot;; } } L threshold = 70% L threshold = % L threshold = 62.3% Actual contrast ratios Text color APCA WCAG 2.1 White Black Avoid colors marked P3+, PP or PP+, as these are almost certainly outside your screen gamut, and browsers currently do not gamut map properly , so the visual result will be off. The component above is part of a new project Im working on called Color Elements ( color-elements on npm). Its a ( highly experimental) collection of web components that make it easy to make color-related apps and demos. If that seems interesting, feel free to try them out and provide feedback! Note that if your actual color is more constrained (e.g. a subset of hues or chromas or a specific gamut), you might be able to balance these tradeoffs better by using a different threshold. Run the experiment yourself with your actual range of colors and find out! Here are some examples of narrower ranges I have tried and the highest threshold that still passes WCAG 2.1: Description Color range Threshold Modern low-end screens Colors within the sRGB gamut 65% Modern high-end screens Colors within the P3 gamut 64.5% Future high-end screens Colors within the Rec.2020 gamut 63.4% Neutrals C [0, 0.03] 67% Muted colors C [0, 0.1] 65.6% Warm colors (reds/oranges/yellows) H [0, 100] 66.8% Pinks/Purples H [300, 370] 67% It is particularly interesting that the threshold is improved to 64.5% by just ignoring colors that are not actually displayable on modern screens. So, assuming (though sadly this is not an assumption that currently holds true ) that browsers prioritize preserving lightness when gamut mapping, we could use 64.5% and still guarantee WCAG compliance. You can even turn this into a utility class that you can combine with different thesholds: .contrast-color { --l: clamp(0, (l / var(--l-threshold, 0.623) - 1) * -infinity, 1); color: oklch(from var(--color) var(--l) 0 h); } .pink { --l-threshold: 0.67; } Conclusion &amp; Future work Putting it all together, including a fallback, as well as a fall forward that uses contrast-color() , the utility class could look like this: .contrast-color { /* Fallback for browsers that don&#39;t support RCS */ color: white; text-shadow: 0 0 .05em black, 0 0 .05em black, 0 0 .05em black, 0 0 .05em black; @supports (color: oklch(from red l c h)) { --l: clamp(0, (l / var(--l-threshold, 0.623) - 1) * -infinity, 1); color: oklch(from var(--color) var(--l) 0 h); text-shadow: none; } @supports (color: contrast-color(red)) { color: contrast-color(var(--color)); text-shadow: none; } } This is only a start. I can imagine many directions for improvement such as: Since RCS allows us to do math with any of the color components in any color space, I wonder if there is a better formula that still be implemented in CSS and balances readability and compliance even better. E.g. Ive had some chats with Andrew Somers (creator of APCA) right before publishing this, which suggest that doing math on luminance (the Y component of XYZ) instead could be a promising direction. We currently only calculate thresholds for white and black text. However, in real designs, we rarely want pure black text, which is why contrast-color() only guarantees a very light or very dark color unless the max keyword is used. How would this extend to darker tints of the background color? Addendum As often happens, after publishing this blog post, a ton of folks reached out to share all sorts of related work in the space. I thought Id share some of the most interesting findings here. Using luminance instead of Lightness When colors have sufficiently different lightness values (as happens with white or black text), humans disregard chromatic contrast (the contrast that hue/colorfulness provide) and basically only use lightness contrast to determine readability. This is why L can be such a good predictor of whether white or black text works best. Another measure, luminance, is basically the colors Y component in the XYZ color space, and a good threshold for flipping to black text is when Y &gt; 0.36. This gives us another method for computing a text color: --y-threshold: 0.36; --y: clamp(0, (y / var(--y-threshold) - 1) * -infinity, 1); color: color(from var(--color) xyz-d65 var(--y) var(--y) var(--y)); As you can see in this demo by Lloyd Kupchanko , using Y threshold &gt; 36% very closely predicts the best text color as determined by APCA. In my tests ( codepen ) it appeared to work as well as the L threshold method, i.e. it was a struggle to find colors where they disagree. However, after this blog post, Lloyd added various L threshold boundaries to his demo, and it appears that indeed, L threshold has a wider range where it disagrees with APCA than Y threshold does. Given this, my recommendation would be to use the Y threshold method if you need to flip between black and white text, and the L threshold method if you need to customize the text color further (e.g. have a very dark color instead of black). Browser bug &amp; workarounds About a week after publishing this post, I discovered a browser bug with color-mix() and RCS, where colors defined via color-mix() used in from render RCS invalid. You can use this testcase to see if a given browser is affected. This has been fixed in Chrome 125 and Safari TP release 194, but it certainly throws a spanner in the works since the whole point of using this technique is that we dont have to care how the color was defined. There are two ways to work around this: Adjust the @supports condition to use color-mix() , like so: @supports (color: oklch(from color-mix(in oklch, red, tan) l c h)) { /* ... */ } The downside is that right now, this would restrict the set of browsers this works in to a teeny tiny set. 2. Register the custom property that contains the color: @property --color { syntax: &quot; &quot;; inherits: true; initial-value: transparent; } This completely fixes it, since if the property is registered, by the time the color hits RCS, its just a resolved color value. @property is currently supported by a much wider set of browsers than RCS, so this workaround doesnt hurt compatiblity at all. Useful resources Many people have shared useful resources on the topic, such as: Black or White? : Compare different contrast algorithms for picking between black or white Dynamic text color contrast based on background lightness with CSS/SVG filters : A different approach to the same problem (requires extra HTML element for the text) Thanks to Chris Lilley , Andrew Somers , Cory LaViska , Elika Etemad , and Tab Atkins-Bittner for their feedback on earlier drafts of this article.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Eigensolutions: composability as the antidote to overfit</title>
  <link>https://lea.verou.me/blog/2023/eigensolutions/</link>
  <pubDate>Sun, 17 May 2026 03:45:06 +0200</pubDate>
  <description>tl;dr: Overfitting happens when solutions don’t generalize sufficiently and is a hallmark of poor design. Eigensolutions are the opposite: solutions that generalize so much they expose links between seemingly unrelated use cases. Designing eigensolutions takes a mindset shift from linear design to composability . Creator tools are not Uber or Facebook In product literature, the design process looks a bit like this: Use cases -&gt; Ideas -&gt; Solution&quot; /&gt; This works great with the kinds of transactional processes (marketplaces, social media, search engines, etc) most product literature centers around, but can fall apart when designing creative tools (developer tools, no-code tools, design tools, languages, APIs etc.), as there are fundamental differences [1] between the two: In transactional processes , users have clearly defined goals, and the task is highly specialized (e.g. “Go to work” , “Order takeout” , “Find accommodation for my upcoming trip” ) and can often be modeled as a linear process. In creator tools , use cases vary wildly , goals are neither linear, nor clearly defined, and may even change throughout the session. Creator tools typically ship knowingly addressing only a percentage of their key use cases — otherwise they would never ship at all. It’s all about balancing UX, use case coverage, and design/implementation effort. Evaluating user experience: Floor and ceiling In end-user programming we talk about the floor and the ceiling of a tool: The floor is the minimum level of knowledge users need to create something useful. The ceiling refers to the extent of what can be created. Some people also talk about wide walls : the range of things that can be made (i.e. how domain specific the tool is). I think that vocabulary generalizes more broadly to creator tools, and can be a useful UX metric. Programming languages tend to have high ceiling, but also a high floor: You make anything, but it requires months or years of training, whereas domain specific GUI builders like Google Forms have a low floor, but also a low ceiling: Anyone can start using them with no training, but you can also only make very specific kinds of things with them. A product that combines a low floor with a high ceiling is the unicorn of creator tools. Therefore, most product work in creator tools centers around either reducing the floor (making things easier ), or increasing the ceiling (making things possible ). Which one of the two takes priority depends on various factors (user research, product philosophy, strategy etc.), and could differ by product area or even by feature. Evaluating use case coverage: The Use Case Backlog In creator tools, use cases tend to accumulate at a much faster rate than they can be addressed, especially in the beginning. Therefore we end up with what I call a “use case backlog” : a list of use cases that are within scope, but we cannot yet address due to lack of resources, good solutions, or both. The more general purpose and the more ambitious the tool is, the higher the rate of accumulation, since the pool of use cases is naturally larger. Pain points get processed into use cases, which accumulate in the use case backlog Unlike the linear design process of transactional processes, the design process for creator tools often consists of matching use cases to solutions, which can happen before, during, or after idea conception. A product may include both transactional processes and creator tools, e.g. Instagram is a social media platform (transactional) with a photo editor (creator tool). Although these tend to be more domain-specific creator tools, which are less good examples for the concepts discussed here. From overfitting to eigensolutions Shishir Mehrotra (of Coda ) wrote about the importance of “ Eigenquestions ” when framing problems, a term he coined, inspired from his math background: the eigenquestion is the question where, if answered, it likely answers the subsequent questions as well. This inspired me to name a symmetrical concept I’ve been pondering for a while: Eigensolutions . The eigensolution is a solution that addresses several key use cases, that previously appeared unrelated. An eigensolution is the polar opposite of overfitting . Overfitting happens when the driving use cases behind a solution are insufficiently diverse, so the solution ends up being so specific it cannot even generalize to use cases that are clearly related. Overfitting is one of the worst things that can happen during the design process. It is a hallmark of poor design that leads to feature creep and poor user experiences. It forces product teams to keep adding more features to address the use cases that were not initially addressed. The result is UI clutter and user confusion, as from the user’s perspective, there are now multiple distinct features that solve subtly different problems. A mindset shift to composability This is all nice and dandy, but how do we design and ship eigensolutions? Do we just sit around waiting for inspiration to strike? Well, we could , but it would be a pretty poor use of resources. :) Instead, it takes a mindset shift , from the linear Use case → Idea → Solution process to composability . Rather than designing a solution to address only our driving use cases, step back and ask yourself: can we design a solution as a composition of smaller, more general features, that could be used together to address a broader set of use cases? In many cases the features required for that composition are already implemented and are just missing one piece: our eigensolution. In other cases composability may require more than one new feature, but the result can still be a net win since these features are useful on their own and can ship independently. A composability mindset requires being aware of pain points and use cases across many different product areas . This becomes harder in larger organizations, where product teams are highly specialized. It’s not impossible, but requires conscious effort to cross-polinate all the way down, rather than completely depending on higher levels of the hierarchy to maintain a bird’s eye view of the product. It’s also important to note that it’s a spectrum, not a binary: overfitting and eigensolutions are just its two opposite ends. Eigensolutions do not come along every day, and do not even exist for all problems. While it’s important to actively guard against overfitting by making sure solutions are validated by many diverse use cases, going too far the other side and chasing a general solution for every problem is also a poor use of resources. Instead, I think a happy medium is to try and be on the right side of the spectrum: Shipping eigensolutions Good design is only part of the work; but without shipping, even the most well designed feature is a pointless document . Contrary to what you may expect, eigensolutions can actually be quite hard to push to stakeholders: Due to their generality, they often require significantly higher engineering effort to implement. Quick-wins are easier to sell: they ship faster and add value sooner. In my 11 years designing web technologies, I have seen many beautiful, elegant eigensolutions be vetoed due to implementation difficulties in favor of far more specific solutions — and often this was the right decision, it’s all about the cost-benefit. Eigensolutions tend to be lower level primitives, which are more flexible, but can also involve higher friction to use than a solution that is tailored to a specific use case. In many cases, layering can resolve or mitigate both of these issues. Layering with higher level abstractions My north star product design principle is “Common things should be easy, complex things should be possible” (paraphrasing Alan Kay — because common things are not always simple, but it’s common things you want to optimize for), which in essence is another way of aiming for low floors and high ceilings . Eigensolutions tend to be lower level primitives. They enable a broad set of use cases, but may not be the most learnable or efficient way to implement all of them, compared to a tailored solution. In other words, they make complex things possible, but do not necessarily make common things easy. Some do both, in which case congratulations, you’ve got an even bigger unicorn! You can skip this section. :) However, this is one of the rare times in life where we can have our cake and eat it too. Instead of implementing tailored solutions ad-hoc (risking overfitting), they can be implemented as shortcuts : higher level abstractions using the lower level primitive. Done well, shortcuts provide dual benefit: not only do they reduce friction for common cases, they also serve as teaching aids for the underlying lower level feature. This offers a very smooth ease-of-use to power curve: if users need to go further than what the shortcut provides, they can always fall back on the lower level primitive to do so. We know that tweaking is easier than creating from scratch, so even when users use that escape hatch, they can tweak what they had created with the higher level UI, rather than starting from scratch. This combined approach both reduces the floor and increases the ceiling! Example: Table filtering in Coda Coda is a product I’ve been using a lot in the last few months. It has replaced Google Docs, Google Sheets, and a few more niche or custom apps I was using. Its UI is full of examples of this pattern, but for the sake of brevity, I will focus on one: table filtering. At first, the filtering UI is pretty high level, designed around common use cases: Also note the nice touch of “And” not just being informative, but also a control that allows the user to edit the logic used to combine multiple filters. For the vast majority of use cases (I would guess &gt;95%), the UI is perfectly sufficient. If you don’t need additional flexibility, you may not even notice the little f button on the top right. But for those that need additional power it can be a lifesaver. That little f indicates that behind the scenes, the UI is actually generating a formula for filtering. Clicking it opens a formula editor, where you can edit the formula directly: I suspect that even for the use cases that require that escape hatch, a small tweak to the generated formula is all that is necessary. The user may have not been able to write the formula from scratch, but tweaking is easier. As one data point, the one time I used this, it was just about using parentheses to combine AND and OR differently than the UI allowed. And as a bonus, the app can collect metrics about what users do with the lower level feature and use that to improve the higher level UI. It’s a win-win all around. What to ship first? In an ideal world, lower level primitives and higher level abstractions would be designed and shipped together. However, engineering resources are typically limited, and it often makes sense to ship one before the other, so we can provide value sooner. This can happen in either direction: Lower level primitive first . Shortcuts to make common cases easy can ship at a later stage, and demos and documentation to showcase common “recipes” can be used as a stopgap meanwhile. This prioritizes use case coverage over optimal UX, but it also allows collecting more data, which can inform the design of the shortcuts implemented. Higher level abstraction first , as an independent, ostensibly ad hoc feature. Then later, once the lower level primitive ships, it is used to “explain” the shortcut, and make it more powerful. This prioritizes optimal UX over use case coverage: we’re not covering all use cases, but for the ones we are covering, we’re offering a frictionless user experience. But which one? As with most things in life, the answer is “it depends”. A few considerations are: How many shortcuts do we need? What percentage of use cases do they cover? How much harder is it to use the lower level primitive directly? Are we certain we will need to provide shortcuts, or is it possible it may be sufficient on its own? Which one are we more confident about? How much engineering effort does the lower level primitive require and how does it compare to implementing the shortcuts as ad hoc features? Do we have extensibility mechanisms in place for users to create and share their own higher level abstractions over the lower level feature? Outside of specific cases, it’s also good to have a design principle in place about which way is generally favored, which is part of the product philosophy (the answer to the eigenquestion : “Are we optimizing for flexibility or learnability?” ) and can be used to fall back on if weighing tradeoffs ends up inconclusive. Note that even when we don’t think the eigensolution is implementable , it can still be useful as a north star UI and designing the tailored solutions as special cases of it can still be a good idea. North Star UI? The ideal UI for addressing a set of use cases in a perfect world where we have infinite resources. This is rarely known to us, but there are cases where we know exactly what the perfect solution would be, but it’s not feasible due to practical concerns (e.g. implementation challenges), so we need to keep looking. However, it can still be useful as a guide to steer us in the right direction. And in some cases, it becomes feasible later on, due to changes in internal or external factors. In my 11 years of designing web technologies, I have seen many “unimplementable” solutions become implementable later on. My favorite example, and something I’m proud to have personally helped drive is the current CSS Nesting syntax . We had plenty of signal for what the optimal syntax for users would be, but it was vetoed by engineering across all major browsers, so we had to design around certain constraints. Instead of completely diverging (which could have produced better syntaxes!), we used it as a north star, and designed and shipped a syntax that was a bit more verbose but forwards compatible with it. Once we got consensus on that, I started trying to get people on board to explore ways (even potential algorithms) to bridge the gap, until eventually Chrome engineers closed on a way to implement the north star syntax 🎉, and as they say, the rest is history. In the web platform we’ve gone back and forth on this a lot. In the beginning, the Web skewed towards shipping higher level abstractions. It had a low floor, but also a relatively low ceiling: many capabilities required browser plugins, or desktop applications. The Extensible Web Manifesto was created as a reaction, urging standards groups to design low level primitives first. For a while, this became the gold standard and many new features were very low level. This filled some necessary gaps in the platform, but since resources are limited, the layering was often missed, resulting in only low level primitives which were a pain to use. More recently, we’ve been recommending a more balanced approach, where tradeoffs are evaluated on a case by case basis. A fictional example: TableSoda Suppose we were working on a fictional product that is an improvement over spreadsheets, let’s call it TableSoda . It has several features that make it more powerful and user-friendly than spreadsheets: It allows users to have multiple tables and define formulas or datatypes for a whole column It also supports references from a cell of one table to a row of another table. Its formula language supports operations on entire columns, and can return entire rows from other tables. Each table can be shared with different people, but a given user can either see/edit all the rows and columns of a table, or none. Some of the use cases in TableSoda’s use case backlog are: Pivot tables: tables that display stats about the usage of a value in another table (usually counts but also sum, min, max, average, etc.) [2] Unions of multiple tables. For example, combining a table of debits and a table of credits into a table of transactions. Vertical splitting: Multiple tables augmenting the same data with different metadata. For example, a table of product features, another table that scores these features on various factors, and lastly, a table of 👍🏼 reactions by different team members about each feature. Granular access control, by row(s) or column(s). For example, a table of tasks where each row is assigned to a different team member, and each team member can only see their own tasks and only edit the status column. With the traditional PM mindset, we would prioritize which one(s) of these is most important to solve, design a few possible solutions, evaluate tradeoffs between them. Over time, we may end up with a pivot table feature, a table union feature, a table vertical split feature, a row-level access control feature, and a column-level access control feature. These features would not necessarily be overfitting, they may solve their respective use cases quite well. But they also add a lot of complexity to the product. Instead, we would still prioritize which one to address first, but with the mindset of decomposing it to its essential components and addressing those (note that there may be many different possible decompositions). Suppose we decide that we want to prioritize pivot tables. A pivot table is essentially [2:1] : A table of all unique values in the source column For each unique value, columns with its count, sum, etc. in the source column Users can already count the number of values in a column using formulas, and they can also use a unique() formula to get a list of unique values in a column. So what prevents them from creating their own pivot tables? There is no way to create dynamic tables in TableSoda, rows can only be added by users. What if we could populate a table’s rows via a formula? The formula values could be used either for one column or multiple (if it returns a list of objects). Formula-populated tables not only solve our driving use case, but all of the above: Unions can be implemented by using a formula to concatenate the rows of multiple tables into a single list. Vertical splitting can be implemented by using a formula to keep the rows of multiple tables in sync with a master table Granular access control can be implemented by having a table with different permissions that is populated using a formula that filters the rows and/or columns of the source table. It’s an eigensolution! Note that our eigensolution is not the end for any of our use cases. It makes many things possible , but none of them are easy . Some of them are common enough to warrant a shortcut : UI that generates the formula needed. For others, our solution is more of a workaround than a primary solution, and the search for a primary solution continues, potentially with reduced prioritization. And others don’t come up often enough to warrant anything further. But even if we still need to smoothen the ease-of-use to power curve, making things possible bought us a lot more time to make them easy . Use cases as the testsuite of product design The most discerning of readers may have noticed that despite the name eigensolution , it’s still all about the use cases: eigensolutions just expose links between use cases that may have been hard to detect, but seem obvious in retrospect . In the example above, one could have seen in advance that all of these use cases were fundamentally about dynamically populating tables. But wasn’t it so much easier to see in retrospect? Requiring all use cases to precede any design work can be unnecessarily restrictive, as frequently solving a problem improves our understanding of the problem. Joe McLean (of Miro ) takes a more extreme position : I believe it’s best to think of a use case as a test case to see if your basic tools are working. What’s missing from the toolbox? What are the limits of what’s available? What 4 use cases would open up with the addition of one more tool? Use cases should be applied after design is done — to check if the tools available can accomplish the job. As a starting point, they put you in a mindset to overfit. This is especially dangerous because users will often tell you they love it in concept testing. “Ah yes, here is my process, represented in pictures!” But it’s only when you actually try to use the tool — hold the thing in your hands — that there’s a hundred things you need it to do that it doesn’t. It’s not flexible — it’s a series of menus and disappointed feature requirements. Joe argues for using use cases only at the end, to validate a design, as he believes that starting from use cases leads puts you in a mindset to overfit. This is so much the polar opposite of current conventional wisdom, that many would consider it heresy. I think that also imposes unnecessary constraints on the design process. I personally favor a more iterative process: Collect as many diverse use cases as possible upfront to drive the design Additional use cases are used to refine the design until it stabilizes Even more at the end to validate it further. If you’re on the right path, additional use cases will smoothly take you from refinement to validation as the design stabilizes. If you’re not on the right path, they will expose fundamental flaws in your design and show you that you need to start over. This has some similarities to test-driven development in engineering: engineers start with a few test cases before writing any code, then add more as they go to make sure everything works as expected. But if someone else’s design thinking works best with using use cases only for validation, more power to them! What matters is that the outcome is a solution that addresses a broad set of use cases in a way users can understand and use. We can probably all agree that no proposal should be considered without being rigorously supported by use cases. It is not enough for use cases to exist; they need to be sufficiently diverse and correspond to real user pain points that are common enough to justify the cost of adding a new feature. But whether use cases drove the design, were used to validate it, or a mix of both is irrelevant, and requiring one or the other imposes unnecessary constraints on the design process. Thanks to Marily Nika and Elika Etemad for providing feedback on an earlier draft of this post. Notable reactions I hesitantly published this article right before the 2023 winter break. I say hesitantly, because it was a departure from my usual content, and I wasn’t sure how it would be received. I was elated to see that despite its length, somewhat intimidating title, and publication date, it did get some very validating reactions. My favorite was Daniel Jackson ’s insightful summary of the ideas presented : I just came across an excellent post by Lea Verou which argues for building software on more general and composable abstractions. In short, I see several different ideas at play in her piece: Use cases lead to overfitting and it’s better to design more coherent and general increments of function; More complex and domain-specific functionality can often be obtained as an instantiation or composition of more abstract and general functionality; Even if you don’t implement the more general and abstract functionality, it might be better to design it and think of your implementation as partial; You can use progressive disclosure in the UI as a bridge between more common domain-specific functionality and more general functionality. These ideas seem to have a lot in common with concept design. Maybe her eigensolutions are concepts? What do y’all think? Also, I really liked the critique of use cases, which connects to our discussion last year of Bertrand Meyer’s piece. It was very validating to see that the ideas resonated with someone who has been thinking about good conceptual design so deeply that it’s his primary area of research at MIT for years, and has published an excellent book on the matter (I only started reading it recently, but I’m loving it so far). It was also validating to see that the ideas resonated with Shishir Mehrotra (CEO of Coda ), who commented : Very insightful article, loved it! If you recall, it was him who coined the term eigenquestion that inspired the term eigensolution . Daniel Fosco (Software designer at Miro ) reposted and wrote : This is by far the best design article I’ve read in a very long time. Lea dives right into what it takes to build complex tools that have to meet wide, unmapped user needs. I also love how it does not shy away from the complexity of the topic even for a moment: on the contrary, the title is already telling you what you’re signing up for. @leaverou is no stranger to great writing, but this one is truly a gem. I recently started using Miro myself, for diagrams and wireframes (most illustrations in this article have been made with Miro), and there are some real gems in its design, so it was very validating to see that the ideas resonated with someone who works on designing it. Fredrik Matheson (Creative Director at Bekk) reposted and wrote : Are you new to UX? This post will be a bit like taking an elevator up above the clouds, where you can see further, beyond the constraints of the transactional systems you might be working on already. Recommended. He even subsequently proceeded to quote concepts from it in a number of comments on other posts! 🤩 Nate Baldwin (Principal Product Designer at Intuit) reposted and wrote : This is a wonderful article! What @LeaVerou defines is what I consider platform design, which I think sits one level below UI systems design. Ie: Product design ⬇️ Systems design (UI) ⬇️ Platform design Although her approach to design is relevant to each. I’ve spent so long designing creator tools that I tended to assume my observations and learnings from my experience are universal. I first read about this distinction in Joe Mc Lean’s excellent post on overfitting , and it was a bit of an a-ha moment. ↩︎ Yes, pivot tables are more complex than that, but let’s keep it simple for the sake of the example. ↩︎ ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Eigensolutions: composability as the antidote to overfit</title>
  <link>https://lea.verou.me/blog/2023/eigensolutions/</link>
  <pubDate>Sun, 17 May 2026 03:45:06 +0200</pubDate>
  <description>tl;dr: Overfitting happens when solutions dont generalize sufficiently and is a hallmark of poor design. Eigensolutions are the opposite: solutions that generalize so much they expose links between seemingly unrelated use cases. Designing eigensolutions takes a mindset shift from linear design to composability . Creator tools are not Uber or Facebook In product literature, the design process looks a bit like this: Use cases -&gt; Ideas -&gt; Solution&quot; /&gt; This works great with the kinds of transactional processes (marketplaces, social media, search engines, etc) most product literature centers around, but can fall apart when designing creative tools (developer tools, no-code tools, design tools, languages, APIs etc.), as there are fundamental differences [1] between the two: In transactional processes , users have clearly defined goals, and the task is highly specialized (e.g. Go to work , Order takeout , Find accommodation for my upcoming trip ) and can often be modeled as a linear process. In creator tools , use cases vary wildly , goals are neither linear, nor clearly defined, and may even change throughout the session. Creator tools typically ship knowingly addressing only a percentage of their key use cases otherwise they would never ship at all. Its all about balancing UX, use case coverage, and design/implementation effort. Evaluating user experience: Floor and ceiling In end-user programming we talk about the floor and the ceiling of a tool: The floor is the minimum level of knowledge users need to create something useful. The ceiling refers to the extent of what can be created. Some people also talk about wide walls : the range of things that can be made (i.e. how domain specific the tool is). I think that vocabulary generalizes more broadly to creator tools, and can be a useful UX metric. Programming languages tend to have high ceiling, but also a high floor: You make anything, but it requires months or years of training, whereas domain specific GUI builders like Google Forms have a low floor, but also a low ceiling: Anyone can start using them with no training, but you can also only make very specific kinds of things with them. A product that combines a low floor with a high ceiling is the unicorn of creator tools. Therefore, most product work in creator tools centers around either reducing the floor (making things easier ), or increasing the ceiling (making things possible ). Which one of the two takes priority depends on various factors (user research, product philosophy, strategy etc.), and could differ by product area or even by feature. Evaluating use case coverage: The Use Case Backlog In creator tools, use cases tend to accumulate at a much faster rate than they can be addressed, especially in the beginning. Therefore we end up with what I call a use case backlog : a list of use cases that are within scope, but we cannot yet address due to lack of resources, good solutions, or both. The more general purpose and the more ambitious the tool is, the higher the rate of accumulation, since the pool of use cases is naturally larger. Pain points get processed into use cases, which accumulate in the use case backlog Unlike the linear design process of transactional processes, the design process for creator tools often consists of matching use cases to solutions, which can happen before, during, or after idea conception. A product may include both transactional processes and creator tools, e.g. Instagram is a social media platform (transactional) with a photo editor (creator tool). Although these tend to be more domain-specific creator tools, which are less good examples for the concepts discussed here. From overfitting to eigensolutions Shishir Mehrotra (of Coda ) wrote about the importance of Eigenquestions when framing problems, a term he coined, inspired from his math background: the eigenquestion is the question where, if answered, it likely answers the subsequent questions as well. This inspired me to name a symmetrical concept Ive been pondering for a while: Eigensolutions . The eigensolution is a solution that addresses several key use cases, that previously appeared unrelated. An eigensolution is the polar opposite of overfitting . Overfitting happens when the driving use cases behind a solution are insufficiently diverse, so the solution ends up being so specific it cannot even generalize to use cases that are clearly related. Overfitting is one of the worst things that can happen during the design process. It is a hallmark of poor design that leads to feature creep and poor user experiences. It forces product teams to keep adding more features to address the use cases that were not initially addressed. The result is UI clutter and user confusion, as from the users perspective, there are now multiple distinct features that solve subtly different problems. A mindset shift to composability This is all nice and dandy, but how do we design and ship eigensolutions? Do we just sit around waiting for inspiration to strike? Well, we could , but it would be a pretty poor use of resources. :) Instead, it takes a mindset shift , from the linear Use case Idea Solution process to composability . Rather than designing a solution to address only our driving use cases, step back and ask yourself: can we design a solution as a composition of smaller, more general features, that could be used together to address a broader set of use cases? In many cases the features required for that composition are already implemented and are just missing one piece: our eigensolution. In other cases composability may require more than one new feature, but the result can still be a net win since these features are useful on their own and can ship independently. A composability mindset requires being aware of pain points and use cases across many different product areas . This becomes harder in larger organizations, where product teams are highly specialized. Its not impossible, but requires conscious effort to cross-polinate all the way down, rather than completely depending on higher levels of the hierarchy to maintain a birds eye view of the product. Its also important to note that its a spectrum, not a binary: overfitting and eigensolutions are just its two opposite ends. Eigensolutions do not come along every day, and do not even exist for all problems. While its important to actively guard against overfitting by making sure solutions are validated by many diverse use cases, going too far the other side and chasing a general solution for every problem is also a poor use of resources. Instead, I think a happy medium is to try and be on the right side of the spectrum: Shipping eigensolutions Good design is only part of the work; but without shipping, even the most well designed feature is a pointless document . Contrary to what you may expect, eigensolutions can actually be quite hard to push to stakeholders: Due to their generality, they often require significantly higher engineering effort to implement. Quick-wins are easier to sell: they ship faster and add value sooner. In my 11 years designing web technologies, I have seen many beautiful, elegant eigensolutions be vetoed due to implementation difficulties in favor of far more specific solutions and often this was the right decision, its all about the cost-benefit. Eigensolutions tend to be lower level primitives, which are more flexible, but can also involve higher friction to use than a solution that is tailored to a specific use case. In many cases, layering can resolve or mitigate both of these issues. Layering with higher level abstractions My north star product design principle is Common things should be easy, complex things should be possible (paraphrasing Alan Kay because common things are not always simple, but its common things you want to optimize for), which in essence is another way of aiming for low floors and high ceilings . Eigensolutions tend to be lower level primitives. They enable a broad set of use cases, but may not be the most learnable or efficient way to implement all of them, compared to a tailored solution. In other words, they make complex things possible, but do not necessarily make common things easy. Some do both, in which case congratulations, youve got an even bigger unicorn! You can skip this section. :) However, this is one of the rare times in life where we can have our cake and eat it too. Instead of implementing tailored solutions ad-hoc (risking overfitting), they can be implemented as shortcuts : higher level abstractions using the lower level primitive. Done well, shortcuts provide dual benefit: not only do they reduce friction for common cases, they also serve as teaching aids for the underlying lower level feature. This offers a very smooth ease-of-use to power curve: if users need to go further than what the shortcut provides, they can always fall back on the lower level primitive to do so. We know that tweaking is easier than creating from scratch, so even when users use that escape hatch, they can tweak what they had created with the higher level UI, rather than starting from scratch. This combined approach both reduces the floor and increases the ceiling! Example: Table filtering in Coda Coda is a product Ive been using a lot in the last few months. It has replaced Google Docs, Google Sheets, and a few more niche or custom apps I was using. Its UI is full of examples of this pattern, but for the sake of brevity, I will focus on one: table filtering. At first, the filtering UI is pretty high level, designed around common use cases: Also note the nice touch of And not just being informative, but also a control that allows the user to edit the logic used to combine multiple filters. For the vast majority of use cases (I would guess &gt;95%), the UI is perfectly sufficient. If you dont need additional flexibility, you may not even notice the little f button on the top right. But for those that need additional power it can be a lifesaver. That little f indicates that behind the scenes, the UI is actually generating a formula for filtering. Clicking it opens a formula editor, where you can edit the formula directly: I suspect that even for the use cases that require that escape hatch, a small tweak to the generated formula is all that is necessary. The user may have not been able to write the formula from scratch, but tweaking is easier. As one data point, the one time I used this, it was just about using parentheses to combine AND and OR differently than the UI allowed. And as a bonus, the app can collect metrics about what users do with the lower level feature and use that to improve the higher level UI. Its a win-win all around. What to ship first? In an ideal world, lower level primitives and higher level abstractions would be designed and shipped together. However, engineering resources are typically limited, and it often makes sense to ship one before the other, so we can provide value sooner. This can happen in either direction: Lower level primitive first . Shortcuts to make common cases easy can ship at a later stage, and demos and documentation to showcase common recipes can be used as a stopgap meanwhile. This prioritizes use case coverage over optimal UX, but it also allows collecting more data, which can inform the design of the shortcuts implemented. Higher level abstraction first , as an independent, ostensibly ad hoc feature. Then later, once the lower level primitive ships, it is used to explain the shortcut, and make it more powerful. This prioritizes optimal UX over use case coverage: were not covering all use cases, but for the ones we are covering, were offering a frictionless user experience. But which one? As with most things in life, the answer is it depends. A few considerations are: How many shortcuts do we need? What percentage of use cases do they cover? How much harder is it to use the lower level primitive directly? Are we certain we will need to provide shortcuts, or is it possible it may be sufficient on its own? Which one are we more confident about? How much engineering effort does the lower level primitive require and how does it compare to implementing the shortcuts as ad hoc features? Do we have extensibility mechanisms in place for users to create and share their own higher level abstractions over the lower level feature? Outside of specific cases, its also good to have a design principle in place about which way is generally favored, which is part of the product philosophy (the answer to the eigenquestion : Are we optimizing for flexibility or learnability? ) and can be used to fall back on if weighing tradeoffs ends up inconclusive. Note that even when we dont think the eigensolution is implementable , it can still be useful as a north star UI and designing the tailored solutions as special cases of it can still be a good idea. North Star UI? The ideal UI for addressing a set of use cases in a perfect world where we have infinite resources. This is rarely known to us, but there are cases where we know exactly what the perfect solution would be, but its not feasible due to practical concerns (e.g. implementation challenges), so we need to keep looking. However, it can still be useful as a guide to steer us in the right direction. And in some cases, it becomes feasible later on, due to changes in internal or external factors. In my 11 years of designing web technologies, I have seen many unimplementable solutions become implementable later on. My favorite example, and something Im proud to have personally helped drive is the current CSS Nesting syntax . We had plenty of signal for what the optimal syntax for users would be, but it was vetoed by engineering across all major browsers, so we had to design around certain constraints. Instead of completely diverging (which could have produced better syntaxes!), we used it as a north star, and designed and shipped a syntax that was a bit more verbose but forwards compatible with it. Once we got consensus on that, I started trying to get people on board to explore ways (even potential algorithms) to bridge the gap, until eventually Chrome engineers closed on a way to implement the north star syntax , and as they say, the rest is history. In the web platform weve gone back and forth on this a lot. In the beginning, the Web skewed towards shipping higher level abstractions. It had a low floor, but also a relatively low ceiling: many capabilities required browser plugins, or desktop applications. The Extensible Web Manifesto was created as a reaction, urging standards groups to design low level primitives first. For a while, this became the gold standard and many new features were very low level. This filled some necessary gaps in the platform, but since resources are limited, the layering was often missed, resulting in only low level primitives which were a pain to use. More recently, weve been recommending a more balanced approach, where tradeoffs are evaluated on a case by case basis. A fictional example: TableSoda Suppose we were working on a fictional product that is an improvement over spreadsheets, lets call it TableSoda . It has several features that make it more powerful and user-friendly than spreadsheets: It allows users to have multiple tables and define formulas or datatypes for a whole column It also supports references from a cell of one table to a row of another table. Its formula language supports operations on entire columns, and can return entire rows from other tables. Each table can be shared with different people, but a given user can either see/edit all the rows and columns of a table, or none. Some of the use cases in TableSodas use case backlog are: Pivot tables: tables that display stats about the usage of a value in another table (usually counts but also sum, min, max, average, etc.) [2] Unions of multiple tables. For example, combining a table of debits and a table of credits into a table of transactions. Vertical splitting: Multiple tables augmenting the same data with different metadata. For example, a table of product features, another table that scores these features on various factors, and lastly, a table of reactions by different team members about each feature. Granular access control, by row(s) or column(s). For example, a table of tasks where each row is assigned to a different team member, and each team member can only see their own tasks and only edit the status column. With the traditional PM mindset, we would prioritize which one(s) of these is most important to solve, design a few possible solutions, evaluate tradeoffs between them. Over time, we may end up with a pivot table feature, a table union feature, a table vertical split feature, a row-level access control feature, and a column-level access control feature. These features would not necessarily be overfitting, they may solve their respective use cases quite well. But they also add a lot of complexity to the product. Instead, we would still prioritize which one to address first, but with the mindset of decomposing it to its essential components and addressing those (note that there may be many different possible decompositions). Suppose we decide that we want to prioritize pivot tables. A pivot table is essentially [2:1] : A table of all unique values in the source column For each unique value, columns with its count, sum, etc. in the source column Users can already count the number of values in a column using formulas, and they can also use a unique() formula to get a list of unique values in a column. So what prevents them from creating their own pivot tables? There is no way to create dynamic tables in TableSoda, rows can only be added by users. What if we could populate a tables rows via a formula? The formula values could be used either for one column or multiple (if it returns a list of objects). Formula-populated tables not only solve our driving use case, but all of the above: Unions can be implemented by using a formula to concatenate the rows of multiple tables into a single list. Vertical splitting can be implemented by using a formula to keep the rows of multiple tables in sync with a master table Granular access control can be implemented by having a table with different permissions that is populated using a formula that filters the rows and/or columns of the source table. Its an eigensolution! Note that our eigensolution is not the end for any of our use cases. It makes many things possible , but none of them are easy . Some of them are common enough to warrant a shortcut : UI that generates the formula needed. For others, our solution is more of a workaround than a primary solution, and the search for a primary solution continues, potentially with reduced prioritization. And others dont come up often enough to warrant anything further. But even if we still need to smoothen the ease-of-use to power curve, making things possible bought us a lot more time to make them easy . Use cases as the testsuite of product design The most discerning of readers may have noticed that despite the name eigensolution , its still all about the use cases: eigensolutions just expose links between use cases that may have been hard to detect, but seem obvious in retrospect . In the example above, one could have seen in advance that all of these use cases were fundamentally about dynamically populating tables. But wasnt it so much easier to see in retrospect? Requiring all use cases to precede any design work can be unnecessarily restrictive, as frequently solving a problem improves our understanding of the problem. Joe McLean (of Miro ) takes a more extreme position : I believe its best to think of a use case as a test case to see if your basic tools are working. Whats missing from the toolbox? What are the limits of whats available? What 4 use cases would open up with the addition of one more tool? Use cases should be applied after design is done to check if the tools available can accomplish the job. As a starting point, they put you in a mindset to overfit. This is especially dangerous because users will often tell you they love it in concept testing. Ah yes, here is my process, represented in pictures! But its only when you actually try to use the tool hold the thing in your hands that theres a hundred things you need it to do that it doesnt. Its not flexible its a series of menus and disappointed feature requirements. Joe argues for using use cases only at the end, to validate a design, as he believes that starting from use cases leads puts you in a mindset to overfit. This is so much the polar opposite of current conventional wisdom, that many would consider it heresy. I think that also imposes unnecessary constraints on the design process. I personally favor a more iterative process: Collect as many diverse use cases as possible upfront to drive the design Additional use cases are used to refine the design until it stabilizes Even more at the end to validate it further. If youre on the right path, additional use cases will smoothly take you from refinement to validation as the design stabilizes. If youre not on the right path, they will expose fundamental flaws in your design and show you that you need to start over. This has some similarities to test-driven development in engineering: engineers start with a few test cases before writing any code, then add more as they go to make sure everything works as expected. But if someone elses design thinking works best with using use cases only for validation, more power to them! What matters is that the outcome is a solution that addresses a broad set of use cases in a way users can understand and use. We can probably all agree that no proposal should be considered without being rigorously supported by use cases. It is not enough for use cases to exist; they need to be sufficiently diverse and correspond to real user pain points that are common enough to justify the cost of adding a new feature. But whether use cases drove the design, were used to validate it, or a mix of both is irrelevant, and requiring one or the other imposes unnecessary constraints on the design process. Thanks to Marily Nika and Elika Etemad for providing feedback on an earlier draft of this post. Notable reactions I hesitantly published this article right before the 2023 winter break. I say hesitantly, because it was a departure from my usual content, and I wasnt sure how it would be received. I was elated to see that despite its length, somewhat intimidating title, and publication date, it did get some very validating reactions. My favorite was Daniel Jackson s insightful summary of the ideas presented : I just came across an excellent post by Lea Verou which argues for building software on more general and composable abstractions. In short, I see several different ideas at play in her piece: Use cases lead to overfitting and its better to design more coherent and general increments of function; More complex and domain-specific functionality can often be obtained as an instantiation or composition of more abstract and general functionality; Even if you dont implement the more general and abstract functionality, it might be better to design it and think of your implementation as partial; You can use progressive disclosure in the UI as a bridge between more common domain-specific functionality and more general functionality. These ideas seem to have a lot in common with concept design. Maybe her eigensolutions are concepts? What do yall think? Also, I really liked the critique of use cases, which connects to our discussion last year of Bertrand Meyers piece. It was very validating to see that the ideas resonated with someone who has been thinking about good conceptual design so deeply that its his primary area of research at MIT for years, and has published an excellent book on the matter (I only started reading it recently, but Im loving it so far). It was also validating to see that the ideas resonated with Shishir Mehrotra (CEO of Coda ), who commented : Very insightful article, loved it! If you recall, it was him who coined the term eigenquestion that inspired the term eigensolution . Daniel Fosco (Software designer at Miro ) reposted and wrote : This is by far the best design article Ive read in a very long time. Lea dives right into what it takes to build complex tools that have to meet wide, unmapped user needs. I also love how it does not shy away from the complexity of the topic even for a moment: on the contrary, the title is already telling you what youre signing up for. @leaverou is no stranger to great writing, but this one is truly a gem. I recently started using Miro myself, for diagrams and wireframes (most illustrations in this article have been made with Miro), and there are some real gems in its design, so it was very validating to see that the ideas resonated with someone who works on designing it. Fredrik Matheson (Creative Director at Bekk) reposted and wrote : Are you new to UX? This post will be a bit like taking an elevator up above the clouds, where you can see further, beyond the constraints of the transactional systems you might be working on already. Recommended. He even subsequently proceeded to quote concepts from it in a number of comments on other posts! Nate Baldwin (Principal Product Designer at Intuit) reposted and wrote : This is a wonderful article! What @LeaVerou defines is what I consider platform design, which I think sits one level below UI systems design. Ie: Product design Systems design (UI) Platform design Although her approach to design is relevant to each. Ive spent so long designing creator tools that I tended to assume my observations and learnings from my experience are universal. I first read about this distinction in Joe Mc Leans excellent post on overfitting , and it was a bit of an a-ha moment. Yes, pivot tables are more complex than that, but lets keep it simple for the sake of the example.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Minimalist Affordances: Making the right tradeoffs</title>
  <link>https://lea.verou.me/blog/2023/minimalist-affordances/</link>
  <pubDate>Sun, 17 May 2026 03:45:04 +0200</pubDate>
  <description>Usability and aesthetics usually go hand in hand. In fact, there is even what we call the “Aesthetic Usability Effect” : users perceive beautiful interfaces as easier to use and cut them more slack when it comes to minor usability issues. Unfortunately, sometimes usability and aesthetics can be at odds, also known as “form over function”. Simplicity, and knowing when to stop A common incarnation of form-over-function, is when designers start identifying signifiers and affordances as noise to be eliminated, sacrificing a great deal of learnability for an — often marginal — improvement in aesthetics. Aesthetic and Minimalist Design is one of the Nielsen/Norman core usability heuristics (and all other heuristics taxonomies have something similar). More poetically, Antoine de Saint-Exupéry said “Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away” . However, this is one of those cases where everyone agrees with the theory, but the devil is in the details (though user testing can do wonders for consensus). Case in point: The new Github comment UI is beautiful . Look at how the text area smoothly blends with the tab, creating an irregular and visually interesting shape! The new GitHub commenting UI, unfocused. The new GitHub commenting UI, focused. Am I the only one that expected the focus outline to also follow the irregular shape? However, I cannot for the life of me internalize that this is a text field that I can type in. Even after using it over a dozen times, I still have to do a double take every time ( “Where is the comment field?!” , “Why is this read-only?” ). For comparison, this was the old UI: The old GitHub commenting UI, with and without focus. While definitely more cluttered, its main UI elements were much more recognizable: there is a text field, indicated by the rounded rectangle, and tabs, indicated by the light gray border around the active tab. By merging the two, both affordances are watered down to the point of being unrecognizable. Yes, there was more visual clutter, not all of which serves a purpose. A skilled designer could probably eliminate the rounded rectangle around the entire area without impacting usability. But the current design goes too far, and throws the baby out with the bathwater. Twitter seems to be trying something similar, but since there is no irregular shape, the text field affordance is not entirely lost. The ever-evolving vocabulary of user interaction Communication is all about mutually understood conventions: a sufficiently widespread grammatical mistake eventually becomes part of the language . In the language of user interfaces, affordances and signifiers are the vocabulary, and the same principles apply. Learnability is not an intrinsic property of a UI; it is a function of the context (cultural and otherwise) in which it is used. Many affordances and signifiers use metaphors from the physical world to communicate what a user can do. For example a button that looks raised reminds us of physical buttons. Tabs are a metaphor for the tabs in a binder. Others are entirely arbitrary and acquire meaning through learning, such as link underlines or the “hamburger” menu icon. We see the same pattern in language: some words are onomatopoeic, such as “buzz” or “meow”, while others are entirely learned, such as “dog” or “cat”. Similarly, writing systems began as pictograms, but evolved to be more abstract and symbolic. Insight and picture from https://ux.stackexchange.com/a/56896/11761 At first, the symbols are direct (if cartoony) representations. Then they slowly lose their extrinsic meaning and become defined more by our conventions of using them (our shared language) and the references to outside concepts disappear. It’s worth reading the whole post if you have time. UI evolution is rife with patterns that began as obscure and ended up as obvious. In other words, external consistency improved, not because the UIs changed, but because the environment did. Some examples you are undoubtedly familiar with: Underlines have always been a strong affordance for links (to the point that using them for anything else is an antipattern ). However, users evolved to perceive weaker signals as links, such as different colors, especially if used consistently . Clicking a website logo to go to the homepage was once an obscure hidden interaction, almost an easter egg. It is now so conventional that a logo that does nothing when clicked is considered a usability issue (though having separate Home links is still the recommendation , 28 years after the pattern was introduced !). Buttons used to need a 3D appearance to be perceived as such. We gradually evolved such that any rectangle around text is perceived as a button, even if it is entirely flat (though research shows that they are still less effective ). Could it be that the new GitHub comment UI is the beginning of a new convention? It’s possible, but the odds are slim. For new conventions to become established, they need to be widespread. Links, buttons, website logos are present on any website, so users get plenty of exposure to any evolution in their design. Similarly, multiline text fields and tabs are very commonplace UI elements. However, their combination is far less common. Even if every tabbed text field on the Web begun using the exact same design, the average user would still not get enough exposure to internalize it. UX Stockholm Syndrome It is entirely possible that I’m overestimating the impact of this on GitHub users. After all, I have not done user testing on it, so I’m basing my opinion on my own experience, and on what I’ve learned about usability spending the better part of the last decade at MIT teaching it and doing a PhD on it. I wondered if it could be an A/B test, so I asked Chris to show me what UI he was seeing. He was also seeing the new UI, but interestingly he expressed frustration about being unable to tell where the text field actually is, and where he can type even before I told him about this article. Whether or not it’s not an A/B test, I’m really hoping that GitHub is collecting enough metrics so they can evaluate the impact of this design on user experience at scale. As for me, I take comfort in knowing that when there is no alternative, users can eventually adapt to any UI, no matter how poor, so I will at some point get used to it. Airplane cockpits are the canonical example here, but this is commonly seen in UIs of a lot of enterprise software (though the wind of change is blowing straight into the face of enterprise UX ). Of course even with heavy training, poor UIs can still lead to disastrous consequences, such as plane crashes , hospital deaths or military accidents . Our life is rife with examples of poor usability, to the point where if something is easy to use, people are often surprised. There is even what some of us call “ UX Stockholm Syndrome ”: after very prolonged exposure to a poor interface, users start believing that it is easy to use, and even advocate against improvements. The curse of knowledge makes them forget how difficult it was to learn, and the prolonged exposure can even make them efficient at using it. Take hex colors for example. Quick, what color is #7A6652 ? Learning to mentally translate between hex color notation and actual visible colors takes years of practice. Hex notation was never designed for humans; it was designed for machines, as a compact way to represent the 3 bytes of RGB channels of earlier screens. Humans do not think of colors as combinations of lights. It’s not logical that to make brown you combine some red, a bit less green, and even less blue. That is neither how we think about color, nor does it relate to any of our real-world color mixing experiences. There are several color models with a more human-centered design, such as HSL , LCH , OKLCH . Their coordinates are designed around how humans describe colors, such as hue for the main color (e.g. red, yellow, green, etc.), chroma/saturation to specify how intense the color is (e.g. 0 would be gray), and lightness to specify how light it is (e.g. white would be 100% and black would be 0%). Yet, it’s common to see the kinds of people who have had very prolonged exposure to this notation (e.g. web designers) not only prefer it, but even try to sing its praises! Another example, entirely outside of software, is music notation. You’ve likely learned it as a child, so it’s hard to remember what the learning experience was like, and if you regularly read music sheets, you may even believe it’s easy. But if we try to step back and examine it objectively, it’s highly unintuitive. Expanding on this would take a whole other article, but I will just give one example. Take a look at the symbols for notes and pauses: Image courtesy of Musicnotes There is not only an ordering here, but successive symbols even have a fixed ratio of 2. Yet absolutely nothing in their representation signifies this. Nothing in the depiction of ♩ indicates that it is longer than ♪, let alone that it is double the length. You just have to learn it. Heck, there’s nothing even indicating whether a symbol produces sound or not! Demanding a lot of knowledge in the head is not a problem in itself; it’s a common tradeoff when efficiency is higher priority than learnability. E.g. the alphabet is also a set of arbitrary symbols we need to learn to be able to form words. But even the best tradeoff is worse than none, aka having your cake and eating it too beats both options. Was a tradeoff really necessary here? Was there really no possible depiction of these symbols that could communicate their purpose, order, and ratios? Or at least a notation that was memorable by association rather than straight memorization ? Update: GitHub’s response (Nov 20th, 2023) This post resonated a lot with people on social media. Here are some selected responses: Selected Social Media Replies https://twitter.com/jitl/status/1720272221149581493 https://twitter.com/noeldevelops/status/1724509073964487056 https://twitter.com/zisiszikos/status/1720157900620939519 https://twitter.com/manuelmeister/status/1720147908731818249 @leaverou @github I really thought the page was broken or incompletely loaded until I saw it enough times to try typing in it. It’s emotionally uncomfortable to type in, fighting how it looks vs. what I know it is. — Benjamin @hazula@hachyderm.io , Nov 3rd, 2023 The Primer team at GitHub reached out to me to discuss the issue, and I was happy to see that they were very receptive to feedback. They then iterated, and came up with a new design that communicates purpose much better, even if less minimalistic: The textarea is now clearly outlined, both before and after focus, clearly indicating that this is a multiline text field. https://twitter.com/natalyathree/status/1729161513636884499 @leaverou @github thank you for this post. We have shipped improvements to make it easier again to identify the textarea and distinguish between Write and Preview. — Daniel Adams (@dipree@mastodon.social) , Nov 20th, 2023 Always great to see an org that is receptive to feedback!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Minimalist Affordances: Making the right tradeoffs</title>
  <link>https://lea.verou.me/blog/2023/minimalist-affordances/</link>
  <pubDate>Sun, 17 May 2026 03:45:04 +0200</pubDate>
  <description>Usability and aesthetics usually go hand in hand. In fact, there is even what we call the Aesthetic Usability Effect : users perceive beautiful interfaces as easier to use and cut them more slack when it comes to minor usability issues. Unfortunately, sometimes usability and aesthetics can be at odds, also known as form over function. Simplicity, and knowing when to stop A common incarnation of form-over-function, is when designers start identifying signifiers and affordances as noise to be eliminated, sacrificing a great deal of learnability for an often marginal improvement in aesthetics. Aesthetic and Minimalist Design is one of the Nielsen/Norman core usability heuristics (and all other heuristics taxonomies have something similar). More poetically, Antoine de Saint-Exupéry said Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away . However, this is one of those cases where everyone agrees with the theory, but the devil is in the details (though user testing can do wonders for consensus). Case in point: The new Github comment UI is beautiful . Look at how the text area smoothly blends with the tab, creating an irregular and visually interesting shape! The new GitHub commenting UI, unfocused. The new GitHub commenting UI, focused. Am I the only one that expected the focus outline to also follow the irregular shape? However, I cannot for the life of me internalize that this is a text field that I can type in. Even after using it over a dozen times, I still have to do a double take every time ( Where is the comment field?! , Why is this read-only? ). For comparison, this was the old UI: The old GitHub commenting UI, with and without focus. While definitely more cluttered, its main UI elements were much more recognizable: there is a text field, indicated by the rounded rectangle, and tabs, indicated by the light gray border around the active tab. By merging the two, both affordances are watered down to the point of being unrecognizable. Yes, there was more visual clutter, not all of which serves a purpose. A skilled designer could probably eliminate the rounded rectangle around the entire area without impacting usability. But the current design goes too far, and throws the baby out with the bathwater. Twitter seems to be trying something similar, but since there is no irregular shape, the text field affordance is not entirely lost. The ever-evolving vocabulary of user interaction Communication is all about mutually understood conventions: a sufficiently widespread grammatical mistake eventually becomes part of the language . In the language of user interfaces, affordances and signifiers are the vocabulary, and the same principles apply. Learnability is not an intrinsic property of a UI; it is a function of the context (cultural and otherwise) in which it is used. Many affordances and signifiers use metaphors from the physical world to communicate what a user can do. For example a button that looks raised reminds us of physical buttons. Tabs are a metaphor for the tabs in a binder. Others are entirely arbitrary and acquire meaning through learning, such as link underlines or the hamburger menu icon. We see the same pattern in language: some words are onomatopoeic, such as buzz or meow, while others are entirely learned, such as dog or cat. Similarly, writing systems began as pictograms, but evolved to be more abstract and symbolic. Insight and picture from https://ux.stackexchange.com/a/56896/11761 At first, the symbols are direct (if cartoony) representations. Then they slowly lose their extrinsic meaning and become defined more by our conventions of using them (our shared language) and the references to outside concepts disappear. Its worth reading the whole post if you have time. UI evolution is rife with patterns that began as obscure and ended up as obvious. In other words, external consistency improved, not because the UIs changed, but because the environment did. Some examples you are undoubtedly familiar with: Underlines have always been a strong affordance for links (to the point that using them for anything else is an antipattern ). However, users evolved to perceive weaker signals as links, such as different colors, especially if used consistently . Clicking a website logo to go to the homepage was once an obscure hidden interaction, almost an easter egg. It is now so conventional that a logo that does nothing when clicked is considered a usability issue (though having separate Home links is still the recommendation , 28 years after the pattern was introduced !). Buttons used to need a 3D appearance to be perceived as such. We gradually evolved such that any rectangle around text is perceived as a button, even if it is entirely flat (though research shows that they are still less effective ). Could it be that the new GitHub comment UI is the beginning of a new convention? Its possible, but the odds are slim. For new conventions to become established, they need to be widespread. Links, buttons, website logos are present on any website, so users get plenty of exposure to any evolution in their design. Similarly, multiline text fields and tabs are very commonplace UI elements. However, their combination is far less common. Even if every tabbed text field on the Web begun using the exact same design, the average user would still not get enough exposure to internalize it. UX Stockholm Syndrome It is entirely possible that Im overestimating the impact of this on GitHub users. After all, I have not done user testing on it, so Im basing my opinion on my own experience, and on what Ive learned about usability spending the better part of the last decade at MIT teaching it and doing a PhD on it. I wondered if it could be an A/B test, so I asked Chris to show me what UI he was seeing. He was also seeing the new UI, but interestingly he expressed frustration about being unable to tell where the text field actually is, and where he can type even before I told him about this article. Whether or not its not an A/B test, Im really hoping that GitHub is collecting enough metrics so they can evaluate the impact of this design on user experience at scale. As for me, I take comfort in knowing that when there is no alternative, users can eventually adapt to any UI, no matter how poor, so I will at some point get used to it. Airplane cockpits are the canonical example here, but this is commonly seen in UIs of a lot of enterprise software (though the wind of change is blowing straight into the face of enterprise UX ). Of course even with heavy training, poor UIs can still lead to disastrous consequences, such as plane crashes , hospital deaths or military accidents . Our life is rife with examples of poor usability, to the point where if something is easy to use, people are often surprised. There is even what some of us call UX Stockholm Syndrome : after very prolonged exposure to a poor interface, users start believing that it is easy to use, and even advocate against improvements. The curse of knowledge makes them forget how difficult it was to learn, and the prolonged exposure can even make them efficient at using it. Take hex colors for example. Quick, what color is #7A6652 ? Learning to mentally translate between hex color notation and actual visible colors takes years of practice. Hex notation was never designed for humans; it was designed for machines, as a compact way to represent the 3 bytes of RGB channels of earlier screens. Humans do not think of colors as combinations of lights. Its not logical that to make brown you combine some red, a bit less green, and even less blue. That is neither how we think about color, nor does it relate to any of our real-world color mixing experiences. There are several color models with a more human-centered design, such as HSL , LCH , OKLCH . Their coordinates are designed around how humans describe colors, such as hue for the main color (e.g. red, yellow, green, etc.), chroma/saturation to specify how intense the color is (e.g. 0 would be gray), and lightness to specify how light it is (e.g. white would be 100% and black would be 0%). Yet, its common to see the kinds of people who have had very prolonged exposure to this notation (e.g. web designers) not only prefer it, but even try to sing its praises! Another example, entirely outside of software, is music notation. Youve likely learned it as a child, so its hard to remember what the learning experience was like, and if you regularly read music sheets, you may even believe its easy. But if we try to step back and examine it objectively, its highly unintuitive. Expanding on this would take a whole other article, but I will just give one example. Take a look at the symbols for notes and pauses: Image courtesy of Musicnotes There is not only an ordering here, but successive symbols even have a fixed ratio of 2. Yet absolutely nothing in their representation signifies this. Nothing in the depiction of indicates that it is longer than , let alone that it is double the length. You just have to learn it. Heck, theres nothing even indicating whether a symbol produces sound or not! Demanding a lot of knowledge in the head is not a problem in itself; its a common tradeoff when efficiency is higher priority than learnability. E.g. the alphabet is also a set of arbitrary symbols we need to learn to be able to form words. But even the best tradeoff is worse than none, aka having your cake and eating it too beats both options. Was a tradeoff really necessary here? Was there really no possible depiction of these symbols that could communicate their purpose, order, and ratios? Or at least a notation that was memorable by association rather than straight memorization ? Update: GitHubs response (Nov 20th, 2023) This post resonated a lot with people on social media. Here are some selected responses: Selected Social Media Replies https://twitter.com/jitl/status/1720272221149581493 https://twitter.com/noeldevelops/status/1724509073964487056 https://twitter.com/zisiszikos/status/1720157900620939519 https://twitter.com/manuelmeister/status/1720147908731818249 @leaverou @github I really thought the page was broken or incompletely loaded until I saw it enough times to try typing in it. Its emotionally uncomfortable to type in, fighting how it looks vs. what I know it is. Benjamin @hazula@hachyderm.io , Nov 3rd, 2023 The Primer team at GitHub reached out to me to discuss the issue, and I was happy to see that they were very receptive to feedback. They then iterated, and came up with a new design that communicates purpose much better, even if less minimalistic: The textarea is now clearly outlined, both before and after focus, clearly indicating that this is a multiline text field. https://twitter.com/natalyathree/status/1729161513636884499 @leaverou @github thank you for this post. We have shipped improvements to make it easier again to identify the textarea and distinguish between Write and Preview. Daniel Adams (@dipree@mastodon.social) , Nov 20th, 2023 Always great to see an org that is receptive to feedback!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>State of HTML 2023 now open!</title>
  <link>https://lea.verou.me/blog/2023/state-of-html-2023/</link>
  <pubDate>Sun, 17 May 2026 03:45:02 +0200</pubDate>
  <description>tl;dr the brand new State of HTML survey is finally open! Take State of HTML 2023 Survey Benefits to you: Survey results are used by browsers to prioritize roadmaps — the reason Google is funding this. Time spent thoughtfully filling them out is an investment that can come back to you tenfold in the form of seeing features you care about implemented, browser incompatibilities being prioritized, and gaps in the platform being addressed. In addition to browsers, several standards groups are also using the results for prioritization and decision-making. Learn about new and upcoming features you may have missed; add features to your reading list and get a list of resources at the end! Get a personalized score and see how you compare to other respondents Learn about the latest trends in the ecosystem and what other developers are focusing on While the survey will be open for 3 weeks, responses entered within the first 9 days (until October 1st) will have a much higher impact on the Web, as preliminary data will be used to inform Interop 2024 proposals. The State of HTML logo, designed by Chris Kirk-Nielsen , who I think surpassed himself with this one! Background This is likely the most ambitious Devographics survey to date. For the past couple of months , I’ve been hard at work leading a small product team spread across three continents (2am to 8am became my second work shift 😅). We embarked on this mission with some uncertainty about whether there were enough features for a State of HTML survey, but quickly found ourselves with the opposite problem: there were too many, all with good reasons for inclusion! To help weigh the tradeoffs and decide what makes the cut we consulted both the developer community , as well as stakeholders across browsers, standards groups, community groups, and more. We even designed new UI controls to facilitate collecting the types of complex data that were needed without making the questions too taxing, and did original UX research to validate them. Once the dust settles, I plan to write separate blog posts about some of these. FAQ Can I edit my responses? Absolutely! Do not worry about filling it out perfectly in one go. If you create an account, you can edit your responses for the whole period the survey is open, and even split filling it out across multiple devices (e.g. start on your phone, then fill out some on your desktop, etc.) Even if you’re filling it out anonymously, you can still edit responses on your device for a while. You could even start anonymously and create an account later, and your responses will be preserved (the only issue is filling it out anonymously, then logging in with an existing account). So, perhaps the call to action above should be… Start State of HTML 2023 Survey Why are there JS questions in an HTML survey? For the same reason there are JS APIs in the HTML standard : many JS APIs are intrinsically related to HTML. We mainly included JS APIs in the following areas: APIs used to manipulate HTML dynamically (DOM, form validation, etc.) Web Components APIs, used to create custom HTML elements APIs used to create web apps that feel like native apps (e.g. Service Workers, Web App Manifest, etc.) If you don’t write any JS, we absolutely still want to hear from you! In fact, I would encourage you even more strongly to fill out the survey: we need to hear from folks who don’t write JS, as they are often underrepresented. Please feel free to skip any JS-related questions (all questions are optional anyway) or select that you have never heard these features. There is a question at the end, where you can select that you only write HTML/CSS: Is the survey only available in English? Absolutely not! Localization has been an integral part of these surveys since the beginning. Fun fact: Nobody in the core State of HTML team is a native English speaker. Each survey gets (at least partially) translated to over 30 languages. However, since translations are a community effort, they are not necessarily complete, especially in the beginning. If you are a native speaker of a language that is not yet complete, please consider helping out ! What does my score mean? Previous surveys reported score as a percentage: “You have heard or used X out of Y features mentioned in the survey”. This one did too at first: This was my own score when the survey first launched, and I created the darn survey 😅 Our engineer, Sacha who is also the founder of Devographics got 19%! These were a lot lower for this survey, for two reasons: It asks about a lot of cutting edge features, more than the other surveys. As I mentioned above, we had a lot of difficult tradeoffs to make, and had to cut a ton of features that were otherwise a great fit. We err’ed on the side of more cutting edge features, as those are the areas the survey can help make the most difference in the ecosystem. To save on space, and be able to ask about more features, we used a new compact format for some of the more stable features, which only asks about usage, not awareness. Here is an example from the first section of the survey (Forms): However, this means that if you have never used a feature, it does not count towards your score, even if you have been aware of it for years. It therefore felt unfair to many to report that you’ve “heard or used” X% of features, when there was no way to express that you have heard 89 out of 131 of them! To address this, we changed the score to be a sum of points, a bit like a video game: each used feature is worth 10 points, each known feature is worth 5 points. Since the new score is harder to interpret by itself and only makes sense in comparison to others, we also show your rank among other participants, to make this easier. My score after the change. If you have already taken the survey, you can just revisit it (with the same device &amp; browser if filled it in anonymously) and go straight to the finish page to see your new score and ranking! I found a bug, what should I do? Please file an issue so we can fix it! File content issue File technical issue Acknowledgements This survey would not have been possible without the hard work of many people. Besides myself (Lea Verou), this includes the rest of the team: Engineering team: Sacha Greif, Eric Burel UX research &amp; data science team: Shaine Rosewel Matala, Michael Quiapos, Gio Vernell Quiogue Our logo designer, Chris Kirk-Nielsen And several volunteers: Léonie Watson for accessibility feedback Our usability testing participants …and all folks who provided early feedback throuhgout the process Last but not least, Kadir Topal made the survey possible in the first place, by proposing it and securing funding from Google. Thank you all! 🙏🏼 Press coverage (selected) Turns out I know less about HTML than I thought! 😅 - Kevin Powell (Video) Are you an HTML expert? Find out with the new State of HTML 2023 survey - dev.to Chris’ Corner: Things I Totally Didn’t Know About That I Learned From Taking the State of HTML 2023 Survey Frontend Focus CSS Weekly The HTML Blog You still haven’t started the State of HTML 2023 survey?!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>State of HTML 2023 now open!</title>
  <link>https://lea.verou.me/blog/2023/state-of-html-2023/</link>
  <pubDate>Sun, 17 May 2026 03:45:02 +0200</pubDate>
  <description>tl;dr the brand new State of HTML survey is finally open! Take State of HTML 2023 Survey Benefits to you: Survey results are used by browsers to prioritize roadmaps the reason Google is funding this. Time spent thoughtfully filling them out is an investment that can come back to you tenfold in the form of seeing features you care about implemented, browser incompatibilities being prioritized, and gaps in the platform being addressed. In addition to browsers, several standards groups are also using the results for prioritization and decision-making. Learn about new and upcoming features you may have missed; add features to your reading list and get a list of resources at the end! Get a personalized score and see how you compare to other respondents Learn about the latest trends in the ecosystem and what other developers are focusing on While the survey will be open for 3 weeks, responses entered within the first 9 days (until October 1st) will have a much higher impact on the Web, as preliminary data will be used to inform Interop 2024 proposals. The State of HTML logo, designed by Chris Kirk-Nielsen , who I think surpassed himself with this one! Background This is likely the most ambitious Devographics survey to date. For the past couple of months , Ive been hard at work leading a small product team spread across three continents (2am to 8am became my second work shift ). We embarked on this mission with some uncertainty about whether there were enough features for a State of HTML survey, but quickly found ourselves with the opposite problem: there were too many, all with good reasons for inclusion! To help weigh the tradeoffs and decide what makes the cut we consulted both the developer community , as well as stakeholders across browsers, standards groups, community groups, and more. We even designed new UI controls to facilitate collecting the types of complex data that were needed without making the questions too taxing, and did original UX research to validate them. Once the dust settles, I plan to write separate blog posts about some of these. FAQ Can I edit my responses? Absolutely! Do not worry about filling it out perfectly in one go. If you create an account, you can edit your responses for the whole period the survey is open, and even split filling it out across multiple devices (e.g. start on your phone, then fill out some on your desktop, etc.) Even if youre filling it out anonymously, you can still edit responses on your device for a while. You could even start anonymously and create an account later, and your responses will be preserved (the only issue is filling it out anonymously, then logging in with an existing account). So, perhaps the call to action above should be Start State of HTML 2023 Survey Why are there JS questions in an HTML survey? For the same reason there are JS APIs in the HTML standard : many JS APIs are intrinsically related to HTML. We mainly included JS APIs in the following areas: APIs used to manipulate HTML dynamically (DOM, form validation, etc.) Web Components APIs, used to create custom HTML elements APIs used to create web apps that feel like native apps (e.g. Service Workers, Web App Manifest, etc.) If you dont write any JS, we absolutely still want to hear from you! In fact, I would encourage you even more strongly to fill out the survey: we need to hear from folks who dont write JS, as they are often underrepresented. Please feel free to skip any JS-related questions (all questions are optional anyway) or select that you have never heard these features. There is a question at the end, where you can select that you only write HTML/CSS: Is the survey only available in English? Absolutely not! Localization has been an integral part of these surveys since the beginning. Fun fact: Nobody in the core State of HTML team is a native English speaker. Each survey gets (at least partially) translated to over 30 languages. However, since translations are a community effort, they are not necessarily complete, especially in the beginning. If you are a native speaker of a language that is not yet complete, please consider helping out ! What does my score mean? Previous surveys reported score as a percentage: You have heard or used X out of Y features mentioned in the survey. This one did too at first: This was my own score when the survey first launched, and I created the darn survey Our engineer, Sacha who is also the founder of Devographics got 19%! These were a lot lower for this survey, for two reasons: It asks about a lot of cutting edge features, more than the other surveys. As I mentioned above, we had a lot of difficult tradeoffs to make, and had to cut a ton of features that were otherwise a great fit. We erred on the side of more cutting edge features, as those are the areas the survey can help make the most difference in the ecosystem. To save on space, and be able to ask about more features, we used a new compact format for some of the more stable features, which only asks about usage, not awareness. Here is an example from the first section of the survey (Forms): However, this means that if you have never used a feature, it does not count towards your score, even if you have been aware of it for years. It therefore felt unfair to many to report that youve heard or used X% of features, when there was no way to express that you have heard 89 out of 131 of them! To address this, we changed the score to be a sum of points, a bit like a video game: each used feature is worth 10 points, each known feature is worth 5 points. Since the new score is harder to interpret by itself and only makes sense in comparison to others, we also show your rank among other participants, to make this easier. My score after the change. If you have already taken the survey, you can just revisit it (with the same device &amp; browser if filled it in anonymously) and go straight to the finish page to see your new score and ranking! I found a bug, what should I do? Please file an issue so we can fix it! File content issue File technical issue Acknowledgements This survey would not have been possible without the hard work of many people. Besides myself (Lea Verou), this includes the rest of the team: Engineering team: Sacha Greif, Eric Burel UX research &amp; data science team: Shaine Rosewel Matala, Michael Quiapos, Gio Vernell Quiogue Our logo designer, Chris Kirk-Nielsen And several volunteers: Léonie Watson for accessibility feedback Our usability testing participants and all folks who provided early feedback throuhgout the process Last but not least, Kadir Topal made the survey possible in the first place, by proposing it and securing funding from Google. Thank you all! Press coverage (selected) Turns out I know less about HTML than I thought! - Kevin Powell (Video) Are you an HTML expert? Find out with the new State of HTML 2023 survey - dev.to Chris Corner: Things I Totally Didnt Know About That I Learned From Taking the State of HTML 2023 Survey Frontend Focus CSS Weekly The HTML Blog You still havent started the State of HTML 2023 survey?!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Numbers or Brackets for numeric questions?</title>
  <link>https://lea.verou.me/blog/2023/numbers-vs-brackets/</link>
  <pubDate>Sun, 17 May 2026 03:25:20 +0200</pubDate>
  <description>As you may know, this summer I am leading the design of the inaugural State of HTML survey. Naturally, I am also exploring ways to improve both survey UX, as well as all questions. Shaine Madala , a data scientist working on the survey design team proposed using numerical inputs instead of brackets for the income question. While I was initially against it , I decided to explore this a bit further, which changed my opinion. The current income question, which uses 6 brackets plus a &quot;Not Applicable&quot; option. There are actually four demographics questions in State of X surveys where the answer is essentially a number, yet we ask respondents to select a bracket: age, years of experience, company size, and income. The arguments for brackets are: They are more privacy preserving for sensitive questions (e.g. people may feel more comfortable sharing an income bracket than their actual income) They are more efficient to input (one click vs homing to keyboard and hitting several keystrokes). In some cases respondents may not know the precise number offhand (e.g. company size) The arguments for numerical input are: Depending on the specifics, these can actually be faster to answer overall since they involve lower cognitive overhead (for known numbers). The brackets are applied at the analysis stage, so they can be designed to provide a better overview of the dataset More elaborate statistics can be computed (e.g. averages, medians, stdevs, the sky is the limit) Which one is faster? We can actually calculate this! Average reading speed for non-fiction is around 240 wpm (= 250ms/word) [1] Therefore, we can approximate reading time for each question by multiplying number of brackets × average words per bracket (wpb) × 250ms. However, this assumes the respondent reads all brackets from top to bottom, but this is a rare worst case scenario. Usually they stop reading once they find the bracket that matches their answer, and they may even skip some brackets, performing a sort of manual binary search. We should probably halve these times to get a more realistic estimate. Average typing speed is 200 cpm [2] (≈ 300ms/character). This means we can approximate typing time for each question by multiplying the number of digits on average × 300ms. Let’s see how this works out for each question: Question Brackets WPB Reading time Avg Digits Typing time Age 8 4 4s 2 0.6s Years of Experience 6 4 3s 2 0.6s Company size 9 4 4.5s 3 0.9s Income 7 2 1.75s 5 1.5s As you can see, despite our initial intuition that brackets are faster, the time it takes to read each bracketed question vastly outweighs typing time for all questions! Of course, this is a simplification. There are models in HCI , such as KLM that can more accurately estimate the time it takes for certain UI flows. We even taught some of these to MIT students in 6.813 , as well as its successor . For example, here are some of the variables we left out in our analysis above: When answering with numerical input, most users need to home from mouse to keyboard, which takes time (estimated as 0.4s in KLM) and then focus the input so they can write in it, which takes an additional click (estimated as 0.2s in KLM) When answering with brackets, users need to move the mouse to the correct bracket, which takes time (KLM estimates all pointing tasks as a flat 1.1s, but this can be more accurately estimated using Fitts’ Law ) We are assuming that the decision is instantaneous, but doing the mental math of comparing the number in your head to the bracket numbers also takes time. However, given the vast difference in times, I don’t think a more accurate model would change the conclusion much. Note that this analysis is based on a desktop interface, primarily because it’s easier (most of these models were developed before mobile was widespread, e.g. KLM was invented in 1978!) Mobile would require a separate calculation taking into account the specifics of mobile interaction (e.g. the time it takes for the keyboard to pop up), though the same logic applies. (thanks Tim for this exellent question !) What about sliders? Sliders are uncommon in surveys, and for good reason. They offer the most benefit in UIs where changes to the value provide feedback , and allow users to iteratively approach the desired value by reacting to this feedback . For example: In a color picker, the user can zero in to the desired coordinates iteratively, by seeing the color change in real time In a video player, the user can drag the slider to the right time by getting feedback about video frames. In searches (e.g. for flights), dragging the slider updates the results in real time, allowing the user to gradually refine their search with suitable tradeoffs In surveys, there is usually no feedback, which eliminates this core benefit. When the number is known in advance, sliders are usually a poor choice, except when we have very few numbers to choose among (e.g. a 1-5 rating) and the slider UI makes it very clear where to click to select each of them, or we don’t much care about the number we select (e.g. search flights by departure time). [3] None of our demographics questions falls in this category (unless bracketed, in which case why not use regular brackets?). There are several reasons for this: It is hard to predict where exactly to click to select the desired number. The denser the range, the harder it is. Even if you know where to click, it’s hard to do so on mobile Dragging a slider on desktop is generally slower than typing the number outright. [4] all the things? Efficiency is not the only consideration here. Privacy is a big one. These surveys are anonoymous, but respondents are still often concerned about entering data they consider sensitive. Also, for the efficiency argument to hold true, the numerical answer needs to be top of mind, which is not always the case. I summarize my recommendations below. Age This is a two digit number, that is always top of mind. Number input. Years of experience This is a 1-2 digit number, and it is either top of mind, or very close to it. Number input. Company size While most people know their rough company size, they very rarely would be able to provide an exact number without searching. This is a good candidate for brackets . However, the number of brackets should be reduced from the current 9 (does the difference between 2-5 and 6-10 employees really matter?), and their labels should be copyedited for scannability. We should also take existing data into account. Looking at the State of CSS 2022 results for this question , it appears that about one third of respondents work at companies with 2-100 people, so we should probably not combine these 5 brackets into one, like I was planning to propose. 101 to 1000 employees is also the existing bracket with the most responses (15.1%), so we could narrow it a little, shifting some of its respondents to the previous bracket. Taking all these factors into consideration, I proposed the following brackets: Just me! Small (2 - 50) Medium (51 - 200) Large (201 - 1000) Very Large (1000+) Income The question that started it all is unfortunately the hardest. Income is a number that people know (or can approximate). It is faster to type, but only marginally (1.75s vs 1.5s). We can however reduce the keystrokes further (from 1.5s to 0.6s on average) by asking people to enter thousands. The biggest concern here is privacy . Would people be comfortable sharing a more precise number? We could mitigate this somewhat by explicitly instructing respondents to round it further, e.g. to the nearest multiple of 10: What is your approximate yearly income (before taxes)? Feel free to round to the nearest multiple of 10 if you are not comfortable sharing an exact number. If it varies by year, please enter an average. $ ,000 However, this assumes that the privacy issues are about granularity, or about the number being too low (rounding to 10s could help with both). However, David Karger made an excellent point in the comments , that people at the higher income brackets may also be reluctant to share their income: I don’t think that rounding off accomplishes anything. It’s not the least significant digit that people care about, but the most significant digit. This all depends on who they imagine will read the data of course. But consider some techy earnings, say 350k. That’s quite a generous salary and some people might be embarrassed to reveal their good fortune. Rounding it to 300k would still be embarrassing. On the other hand, a bracket like 150 to 500 would give them wiggle room to say that they’re earning a decent salary without revealing that they’re earning a fantastic one. I don’t have immediate insight into what brackets should be chosen to give people the cover they need to be truthful, but I think they will work better for this question. Another idea was to offer UI that lets users indicate that the number they have entered is actually an upper or lower bound. What is your approximate yearly income (before taxes)? About Over Under Exactly $ ,000 Of course, a dropdown PLUS a number input is much slower than using brackets, but if only a tiny fraction of respondents uses it, it does not affect the analysis of the average case. However, after careful consideration and input, both qualitative and quantitative , it appears that privacy is a much bigger factor than I had previously realized. Even though I was aware that people see income level as sensitive data (more so in certain cultures than others), I had not fully realized the extent of this. In the end, I think the additional privacy afforded by brackets far outweighs any argument for efficiency or data analysis convenience . Conclusion I’m sure there is a lot of prior art on the general dilemma on numerical inputs vs brackets, but I wanted to do some analysis with the specifics of this case and outline an analytical framework for answering these kinds of dilemmas. That said, if you know of any relevant prior art, please share it in the comments! Same if you can spot any flaws in my analysis or recommendations. You could also check out the relevant discussion as there may be good points there. https://www.sciencedirect.com/science/article/abs/pii/S0749596X19300786 ↩︎ https://www.typingpal.com/en/blog/good-typing-speed ↩︎ Slider Design: Rules of Thumb, NNGroup, 2015 ↩︎ KLM is a poor model for dragging tasks for two reasons: First, it regards dragging as simply a combination of three actions: button press, mouse move, button release . But we all know from experience that dragging is much harder than simply pointing, as managing two tasks simultaneously (holding down the mouse button and moving the pointer) is almost always harder than doing them sequentially. Second, it assumes that all pointing tasks have a fixed cost (1.1s), which may be acceptable for actual pointing tasks, but the inaccuracy is magnified for dragging tasks. A lot of HCI literature (and even NNGroup ) refers to the Steering Law to estimate the time it takes to use a slider, however modern sliders (and scrollbars) do not require steering, as they are not constrained to a single axis: once dragging is initiated, moving the pointer in any direction adjusts the slider, until the mouse button is released. Fitts Law actually appears to be a better model here, and indeed there are many papers extending it to dragging. However, evaluating this research is out of scope for this post. ↩︎</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Numbers or Brackets for numeric questions?</title>
  <link>https://lea.verou.me/blog/2023/numbers-vs-brackets/</link>
  <pubDate>Sun, 17 May 2026 03:25:20 +0200</pubDate>
  <description>As you may know, this summer I am leading the design of the inaugural State of HTML survey. Naturally, I am also exploring ways to improve both survey UX, as well as all questions. Shaine Madala , a data scientist working on the survey design team proposed using numerical inputs instead of brackets for the income question. While I was initially against it , I decided to explore this a bit further, which changed my opinion. The current income question, which uses 6 brackets plus a &quot;Not Applicable&quot; option. There are actually four demographics questions in State of X surveys where the answer is essentially a number, yet we ask respondents to select a bracket: age, years of experience, company size, and income. The arguments for brackets are: They are more privacy preserving for sensitive questions (e.g. people may feel more comfortable sharing an income bracket than their actual income) They are more efficient to input (one click vs homing to keyboard and hitting several keystrokes). In some cases respondents may not know the precise number offhand (e.g. company size) The arguments for numerical input are: Depending on the specifics, these can actually be faster to answer overall since they involve lower cognitive overhead (for known numbers). The brackets are applied at the analysis stage, so they can be designed to provide a better overview of the dataset More elaborate statistics can be computed (e.g. averages, medians, stdevs, the sky is the limit) Which one is faster? We can actually calculate this! Average reading speed for non-fiction is around 240 wpm (= 250ms/word) [1] Therefore, we can approximate reading time for each question by multiplying number of brackets average words per bracket (wpb) 250ms. However, this assumes the respondent reads all brackets from top to bottom, but this is a rare worst case scenario. Usually they stop reading once they find the bracket that matches their answer, and they may even skip some brackets, performing a sort of manual binary search. We should probably halve these times to get a more realistic estimate. Average typing speed is 200 cpm [2] ( 300ms/character). This means we can approximate typing time for each question by multiplying the number of digits on average 300ms. Lets see how this works out for each question: Question Brackets WPB Reading time Avg Digits Typing time Age 8 4 4s 2 0.6s Years of Experience 6 4 3s 2 0.6s Company size 9 4 4.5s 3 0.9s Income 7 2 1.75s 5 1.5s As you can see, despite our initial intuition that brackets are faster, the time it takes to read each bracketed question vastly outweighs typing time for all questions! Of course, this is a simplification. There are models in HCI , such as KLM that can more accurately estimate the time it takes for certain UI flows. We even taught some of these to MIT students in 6.813 , as well as its successor . For example, here are some of the variables we left out in our analysis above: When answering with numerical input, most users need to home from mouse to keyboard, which takes time (estimated as 0.4s in KLM) and then focus the input so they can write in it, which takes an additional click (estimated as 0.2s in KLM) When answering with brackets, users need to move the mouse to the correct bracket, which takes time (KLM estimates all pointing tasks as a flat 1.1s, but this can be more accurately estimated using Fitts Law ) We are assuming that the decision is instantaneous, but doing the mental math of comparing the number in your head to the bracket numbers also takes time. However, given the vast difference in times, I dont think a more accurate model would change the conclusion much. Note that this analysis is based on a desktop interface, primarily because its easier (most of these models were developed before mobile was widespread, e.g. KLM was invented in 1978!) Mobile would require a separate calculation taking into account the specifics of mobile interaction (e.g. the time it takes for the keyboard to pop up), though the same logic applies. (thanks Tim for this exellent question !) What about sliders? Sliders are uncommon in surveys, and for good reason. They offer the most benefit in UIs where changes to the value provide feedback , and allow users to iteratively approach the desired value by reacting to this feedback . For example: In a color picker, the user can zero in to the desired coordinates iteratively, by seeing the color change in real time In a video player, the user can drag the slider to the right time by getting feedback about video frames. In searches (e.g. for flights), dragging the slider updates the results in real time, allowing the user to gradually refine their search with suitable tradeoffs In surveys, there is usually no feedback, which eliminates this core benefit. When the number is known in advance, sliders are usually a poor choice, except when we have very few numbers to choose among (e.g. a 1-5 rating) and the slider UI makes it very clear where to click to select each of them, or we dont much care about the number we select (e.g. search flights by departure time). [3] None of our demographics questions falls in this category (unless bracketed, in which case why not use regular brackets?). There are several reasons for this: It is hard to predict where exactly to click to select the desired number. The denser the range, the harder it is. Even if you know where to click, its hard to do so on mobile Dragging a slider on desktop is generally slower than typing the number outright. [4] all the things? Efficiency is not the only consideration here. Privacy is a big one. These surveys are anonoymous, but respondents are still often concerned about entering data they consider sensitive. Also, for the efficiency argument to hold true, the numerical answer needs to be top of mind, which is not always the case. I summarize my recommendations below. Age This is a two digit number, that is always top of mind. Number input. Years of experience This is a 1-2 digit number, and it is either top of mind, or very close to it. Number input. Company size While most people know their rough company size, they very rarely would be able to provide an exact number without searching. This is a good candidate for brackets . However, the number of brackets should be reduced from the current 9 (does the difference between 2-5 and 6-10 employees really matter?), and their labels should be copyedited for scannability. We should also take existing data into account. Looking at the State of CSS 2022 results for this question , it appears that about one third of respondents work at companies with 2-100 people, so we should probably not combine these 5 brackets into one, like I was planning to propose. 101 to 1000 employees is also the existing bracket with the most responses (15.1%), so we could narrow it a little, shifting some of its respondents to the previous bracket. Taking all these factors into consideration, I proposed the following brackets: Just me! Small (2 - 50) Medium (51 - 200) Large (201 - 1000) Very Large (1000+) Income The question that started it all is unfortunately the hardest. Income is a number that people know (or can approximate). It is faster to type, but only marginally (1.75s vs 1.5s). We can however reduce the keystrokes further (from 1.5s to 0.6s on average) by asking people to enter thousands. The biggest concern here is privacy . Would people be comfortable sharing a more precise number? We could mitigate this somewhat by explicitly instructing respondents to round it further, e.g. to the nearest multiple of 10: What is your approximate yearly income (before taxes)? Feel free to round to the nearest multiple of 10 if you are not comfortable sharing an exact number. If it varies by year, please enter an average. $ ,000 However, this assumes that the privacy issues are about granularity, or about the number being too low (rounding to 10s could help with both). However, David Karger made an excellent point in the comments , that people at the higher income brackets may also be reluctant to share their income: I dont think that rounding off accomplishes anything. Its not the least significant digit that people care about, but the most significant digit. This all depends on who they imagine will read the data of course. But consider some techy earnings, say 350k. Thats quite a generous salary and some people might be embarrassed to reveal their good fortune. Rounding it to 300k would still be embarrassing. On the other hand, a bracket like 150 to 500 would give them wiggle room to say that theyre earning a decent salary without revealing that theyre earning a fantastic one. I dont have immediate insight into what brackets should be chosen to give people the cover they need to be truthful, but I think they will work better for this question. Another idea was to offer UI that lets users indicate that the number they have entered is actually an upper or lower bound. What is your approximate yearly income (before taxes)? About Over Under Exactly $ ,000 Of course, a dropdown PLUS a number input is much slower than using brackets, but if only a tiny fraction of respondents uses it, it does not affect the analysis of the average case. However, after careful consideration and input, both qualitative and quantitative , it appears that privacy is a much bigger factor than I had previously realized. Even though I was aware that people see income level as sensitive data (more so in certain cultures than others), I had not fully realized the extent of this. In the end, I think the additional privacy afforded by brackets far outweighs any argument for efficiency or data analysis convenience . Conclusion Im sure there is a lot of prior art on the general dilemma on numerical inputs vs brackets, but I wanted to do some analysis with the specifics of this case and outline an analytical framework for answering these kinds of dilemmas. That said, if you know of any relevant prior art, please share it in the comments! Same if you can spot any flaws in my analysis or recommendations. You could also check out the relevant discussion as there may be good points there. https://www.sciencedirect.com/science/article/abs/pii/S0749596X19300786 https://www.typingpal.com/en/blog/good-typing-speed Slider Design: Rules of Thumb, NNGroup, 2015 KLM is a poor model for dragging tasks for two reasons: First, it regards dragging as simply a combination of three actions: button press, mouse move, button release . But we all know from experience that dragging is much harder than simply pointing, as managing two tasks simultaneously (holding down the mouse button and moving the pointer) is almost always harder than doing them sequentially. Second, it assumes that all pointing tasks have a fixed cost (1.1s), which may be acceptable for actual pointing tasks, but the inaccuracy is magnified for dragging tasks. A lot of HCI literature (and even NNGroup ) refers to the Steering Law to estimate the time it takes to use a slider, however modern sliders (and scrollbars) do not require steering, as they are not constrained to a single axis: once dragging is initiated, moving the pointer in any direction adjusts the slider, until the mouse button is released. Fitts Law actually appears to be a better model here, and indeed there are many papers extending it to dragging. However, evaluating this research is out of scope for this post.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Help Design the Inaugural State of HTML Survey!</title>
  <link>https://lea.verou.me/blog/2023/design-state-of-html/</link>
  <pubDate>Sun, 17 May 2026 03:25:19 +0200</pubDate>
  <description>You have likely participated in several Devographics surveys before, such as State of CSS , or State of JS . These surveys have become the primary source of unbiased data for the practices of front-end developers today (there is also the Web Almanac research, but because this studies what is actually used on the web, it takes a lot longer for changes in developer practices to propagate). You may remember that last summer, Google sponsored me to be Survey Design Lead for State of CSS 2022 . It went really well: we got 60% higher response rate than the year before, which gave browsers a lot of actionable data to prioritize their work. The feedback from these surveys is a prime input into the Interop project, where browsers collaborate to implement the most important features for developers interoperably. So this summer, Google trusted me with a much bigger project, a brand new survey: State of HTML ! For some of you, a State of HTML survey may be the obvious next step, the remaining missing piece. For others, the gap this is filling may not be as clear. No, this is not about whether you prefer or ! It turns out, just like JavaScript and CSS, HTML is actually going through an evolution of its own! New elements like and are on the horizon, or cool new features like popovers and declarative Shadow DOM . There are even JS APIs that are intrinsically tied to HTML, such as e.g. Imperative slot assignment or DOM APIs like input.showPicker() Historically, these did not fit in any of these surveys. Some were previously asked in State of JS , some in State of CSS , but it was always a bit awkward. This new survey aims to fill these gaps, and finish surveying the core technologies of the Web, which are HTML, CSS and JavaScript. Designing a brand new survey is a more daunting task than creating the new edition of an existing survey, but also an exciting one, as comparability with the data from prior years is not a concern, so there is a lot more freedom. Each State of X survey consists of two parts: Part 1 is a quiz: a long list of lesser-known and/or cutting-edge (or even upcoming) features where respondents select one of three options: Starting with State of CSS 2022, respondents could also add freeform comments to provide more context about their answer through the little speech bubble icon. One of my goals this year is to make this feature quicker to use for common types of feedback, and to facilitate quantitative analysis of the responses (to some degree). At the end of the survey, respondents even get a knowledge score based on their answers, which provides immediate value and motivation which reduces survey fatigue. Part 2 is more freeform, and usually includes multiple-choice questions about tools and resources, freeform questions about pain points, and of course, demographics. One of the novel things I tried in the 2022 State of CSS survey was to involve the community in the design process , with one-click voting for the features to ask about. These were actually GitHub Issues with certain labels. Two years prior I had released MaVoice: an app to facilitate one click voting on Issues in any repo , and it fit the bill perfectly here . This process worked exceptionally well for uncovering blind spots: it turned out there were a bunch of CSS features that would be good to ask about, but were simply not on our radar. This is one of the reasons I strongly believe in transparency and co-design: no one human or small team can ever match the collective intelligence of the community . Predictably, I plan to try the same approach for State of HTML. Instead of using MaVoice, this year I’m trying GitHub Discussions . These allow one click voting from the GitHub interface itself, without users having to authorize a separate app. They also allow for more discussion, and do not clutter Issues, which are better suited for – well – actual issues. I have created a Discussions category for this and seeded it with 55 features spanning 12 focus areas (Forms &amp; Editing, Making Web Components, Consuming Web Components, ARIA &amp; Accessibility APIs, Embedding, Multimedia, Interactivity, Semantic HTML, Templating, Bridging the gap with native, Performance, Security &amp; Privacy). These initial ideas and focus areas came from a combination of personal research, as well as several brainstorming sessions with the WebDX CG . Vote on Features for State of HTML 2023! You can also see a (read-only) summary of the proposed features with their metadata here though keep in mind that it’s manually updated so it may not not include new proposals. If you can think of features we missed, please post a new Discussion in this category . There is also a more general 💬 State of HTML 2023 Design category, for meta-discussions on Part 1 of the survey, and design brainstorming on Part 2. Note that the feedback period will be open for two weeks, until August 10th . After that point, feedback may still be taken into account, but it may be too late in the process to make a difference. Some things to keep in mind when voting and generally participating in these discussions: The votes and proposals collected through this process are only one of the many variables that feed into deciding what to ask about, and are non-binding . There are two goals to balance here: The survey needs to provide value to developers – and be fun to fill in! The survey needs to provide value to browsers, i.e. get them actionable feedback they can use to help prioritize what to work on. This is the main way that these surveys have impact on the web platform, and is at least as important as (1). While the title is “State of HTML”, certain JS APIs or even CSS syntax is also relevant, especially those very close to HTML, such as DOM, ARIA, Web Components, PWAs etc. Stable features that have existed for a long time and are widely known are generally less likely to make it to the survey. Now go vote! 🗳</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Help Design the Inaugural State of HTML Survey!</title>
  <link>https://lea.verou.me/blog/2023/design-state-of-html/</link>
  <pubDate>Sun, 17 May 2026 03:25:19 +0200</pubDate>
  <description>You have likely participated in several Devographics surveys before, such as State of CSS , or State of JS . These surveys have become the primary source of unbiased data for the practices of front-end developers today (there is also the Web Almanac research, but because this studies what is actually used on the web, it takes a lot longer for changes in developer practices to propagate). You may remember that last summer, Google sponsored me to be Survey Design Lead for State of CSS 2022 . It went really well: we got 60% higher response rate than the year before, which gave browsers a lot of actionable data to prioritize their work. The feedback from these surveys is a prime input into the Interop project, where browsers collaborate to implement the most important features for developers interoperably. So this summer, Google trusted me with a much bigger project, a brand new survey: State of HTML ! For some of you, a State of HTML survey may be the obvious next step, the remaining missing piece. For others, the gap this is filling may not be as clear. No, this is not about whether you prefer or ! It turns out, just like JavaScript and CSS, HTML is actually going through an evolution of its own! New elements like and are on the horizon, or cool new features like popovers and declarative Shadow DOM . There are even JS APIs that are intrinsically tied to HTML, such as e.g. Imperative slot assignment or DOM APIs like input.showPicker() Historically, these did not fit in any of these surveys. Some were previously asked in State of JS , some in State of CSS , but it was always a bit awkward. This new survey aims to fill these gaps, and finish surveying the core technologies of the Web, which are HTML, CSS and JavaScript. Designing a brand new survey is a more daunting task than creating the new edition of an existing survey, but also an exciting one, as comparability with the data from prior years is not a concern, so there is a lot more freedom. Each State of X survey consists of two parts: Part 1 is a quiz: a long list of lesser-known and/or cutting-edge (or even upcoming) features where respondents select one of three options: Starting with State of CSS 2022, respondents could also add freeform comments to provide more context about their answer through the little speech bubble icon. One of my goals this year is to make this feature quicker to use for common types of feedback, and to facilitate quantitative analysis of the responses (to some degree). At the end of the survey, respondents even get a knowledge score based on their answers, which provides immediate value and motivation which reduces survey fatigue. Part 2 is more freeform, and usually includes multiple-choice questions about tools and resources, freeform questions about pain points, and of course, demographics. One of the novel things I tried in the 2022 State of CSS survey was to involve the community in the design process , with one-click voting for the features to ask about. These were actually GitHub Issues with certain labels. Two years prior I had released MaVoice: an app to facilitate one click voting on Issues in any repo , and it fit the bill perfectly here . This process worked exceptionally well for uncovering blind spots: it turned out there were a bunch of CSS features that would be good to ask about, but were simply not on our radar. This is one of the reasons I strongly believe in transparency and co-design: no one human or small team can ever match the collective intelligence of the community . Predictably, I plan to try the same approach for State of HTML. Instead of using MaVoice, this year Im trying GitHub Discussions . These allow one click voting from the GitHub interface itself, without users having to authorize a separate app. They also allow for more discussion, and do not clutter Issues, which are better suited for well actual issues. I have created a Discussions category for this and seeded it with 55 features spanning 12 focus areas (Forms &amp; Editing, Making Web Components, Consuming Web Components, ARIA &amp; Accessibility APIs, Embedding, Multimedia, Interactivity, Semantic HTML, Templating, Bridging the gap with native, Performance, Security &amp; Privacy). These initial ideas and focus areas came from a combination of personal research, as well as several brainstorming sessions with the WebDX CG . Vote on Features for State of HTML 2023! You can also see a (read-only) summary of the proposed features with their metadata here though keep in mind that its manually updated so it may not not include new proposals. If you can think of features we missed, please post a new Discussion in this category . There is also a more general State of HTML 2023 Design category, for meta-discussions on Part 1 of the survey, and design brainstorming on Part 2. Note that the feedback period will be open for two weeks, until August 10th . After that point, feedback may still be taken into account, but it may be too late in the process to make a difference. Some things to keep in mind when voting and generally participating in these discussions: The votes and proposals collected through this process are only one of the many variables that feed into deciding what to ask about, and are non-binding . There are two goals to balance here: The survey needs to provide value to developers and be fun to fill in! The survey needs to provide value to browsers, i.e. get them actionable feedback they can use to help prioritize what to work on. This is the main way that these surveys have impact on the web platform, and is at least as important as (1). While the title is State of HTML, certain JS APIs or even CSS syntax is also relevant, especially those very close to HTML, such as DOM, ARIA, Web Components, PWAs etc. Stable features that have existed for a long time and are widely known are generally less likely to make it to the survey. Now go vote!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Going Lean</title>
  <link>https://lea.verou.me/blog/2023/going-lean/</link>
  <pubDate>Sun, 17 May 2026 03:25:18 +0200</pubDate>
  <description>WordPress has been with me since my very first post in 2009 . There is a lot to love about it: It’s open source, it has a thriving ecosystem, a beautiful default theme, and a revolutionary block editor that makes my inner UX geek giddy. Plus, WP made building a website and publishing content accessible to everyone. No wonder it’s the most popular CMS in the world , by a huge margin. However, for me, the bad had started to outweigh the good: Things I could do in minutes in a static site, in WP required finding a plugin or tweaking PHP code. It was slow and bloated. Getting a draft out of it and into another medium was a pain. Despite having never been hacked, I was terrified about it, given all the horror stories. I was periodically getting “Error establishing a database connection” errors, whose frequency kept increasing. It was time to move on. It’s not you WP, it’s me. It seemed obvious that the next step would be a statically generated blog. I had been using Eleventy for a while on a variety of sites at that point and loved it, so using that was a no-brainer. In fact, my blog was one of my last remaining non-JAMstack sites, and by far the biggest. I had built a simple 11ty blog for my husband a year ago, and was almost jealous of the convenience and simplicity. There are so many conveniences that just come for free with this workflow: git, Markdown, custom components, even GitHub Copilot as you write your prose! And if you can make the repo public, oooooh, the possibilities! People could even file PRs and issues for your blog posts! Using Netlify as a platform was also a no-brainer: I had been using it for years, for over 30 sites at this point! I love their simplicity, their focus on developer experience, and their commitment to open source. I also happen to know a bunch of folks there, and they have a great culture too. However, I was dreading the amount of work it would take to migrate 14 years of content, plugins, and styling. The stroke that broke the camel’s back was a particularly bad db outage. I tweeted about my frustration, but I had already made up my mind. I reviewed the list of plugins I had installed on WP to estimate the amount of work. Nearly all fell in one of two categories: Solving problems I wouldn’t have if I wasn’t using WP (e.g. SVG support, Don’t Muck My Markup) Giving me benefits I could get in 11ty with very little code (e.g. Prism syntax highlighting, Custom Body Class, Disqus, Unlist Posts &amp; Pages, Widget CSS classes) Giving me benefits I could get with existing Eleventy plugins (e.g. Add Anchor Links, Easy Table of Contents) This could actually work! Public or private repo? One of the hardest dilemmas was whether to make the repo for this website public or private. Overall, I was happy to have most files be public, but there were a few things I wanted to keep private: Drafts (some drafts I’m ok to share publicly, but not all) Unlisted pages and posts (posts with publicly accessible URLs, but not linked from anywhere) Private pages (e.g. in the previous site I had a password-protected page with my details for conference organizers) Unfortunately, right now it’s all-or-nothing, even if only one file needs to be private, the whole repo needs to be private. FWIW I don’t think it has to be this way, and I tweeted about this, including some ideas about fixing it, either from the GitHub side, or the serverless platform side. I’m hoping to write a blog post to expand on this soon. Making the repo public does have many advantages: Transparency is one of my core values, and this is in line with it. People can learn from my code and avoid going down the same rabbit holes I did. People can file issues for problems. People can send PRs to fix both content and functionality. I wouldn’t need to use a separate public repo for the data that populates my Speaking , Publications , and Projects pages. I went back and forth quite a lot, but in the end I decided to make it public. In fact, I fully embraced it, by making it as easy as possible to file issues and submit PRs. Each page has a link to report a problem with it, which prefills as much info as possible. This was also a good excuse to try out GitHub Issue Forms , as well as URLs for prefilling the form ! Each page has a link to edit it on GitHub, which automatically takes you through a PR flow if you don’t have write access to the repo. Licensing Note that a public repo is not automatically open source . As you know, I have a long track record of open sourcing my code. I love seeing people learning from it, using it in their own projects, and blogging about what they’ve learned. So, MIT-licensing the code part of this website is a no-brainer. CC-BY also seems like a no-brainer for content , because, why not? Where it gets tricky is the design . I’m well aware that neither my logo nor the visual style of this website would win any design awards; I haven’t worked as a graphic designer for many years, and it shows. However, it’s something I feel is very personal to me, my own personal brand, which by definition needs to be unique. Seeing another website with the same logo and/or visual style would feel just as unsettling as walking into a house that looks exactly like mine. I’m speaking from experience: I’ve had my logo and design copied many times, and it always felt like a violation. I’m not sure how to express this distinction in a GitHub LICENSE file, so I haven’t yet added one, but I did try to outline it in the Credits &amp; Making Of page. It’s still difficult to draw the line precisely, especially when it comes to CSS code. I’m basically happy for people to copy as much of my CSS code as they want (following MIT license rules), as long as the end result doesn’t scream “Lea Verou” to anyone who has seen this site. But how on Earth do you express that? 🤔 Migrating content to Markdown The title of this section says “to Markdown” because that’s one of the benefits of this approach: static site generators are largely compatible with each other, so if I ever needed to migrate again, it would be much easier. Thankfully, travelers on this road before me had already paved it. Many open source scripts out there to migrate WP to Markdown! The one that worked well for me was lonekorean/wordpress-export-to-markdown (though I later discovered there’s a more updated fork now) It was still a bumpy road. First, it kept getting stuck on parsing the WP XML export, specifically in comments. I use Disqus for comments, but it mirrors comments in the internal WP system. Also, WP seems to continue recording trackbacks even if they are not displayed anywhere. Turns out I had hundreds of thousands of spam trackbacks, which I spent hours cleaning up (it was quite a meditative experience). In the end I got the total comments + trackbacks from 290K down to 26K which reduced the size of the XML export from 210 MB to a mere 31 MB. This did not fix the parsing issue, but allowed me to simply open the file in VS Code and delete the problematic comments manually. It also fixed the uptime issues I was having: I never got another “Error establishing a database connection” error after that, despite taking my own sweet time to migrate (started in April 2023, and finished in July!). Ideally, I wish WP had an option to export without comments, but I guess that’s not a common use case. While this importer is great, and allowed me to configure the file structure in a way that preserved all my URLs, I did lose a few things: “Read more” separators (filed it as issue #93 ) Figures (they are imported as just images with text underneath) (filed it as issue #94 ) Drafts ( #16 ) Pages (I had to manually copy them over, but it was only a handful) Any custom classes were gone (e.g. a &quot;view-demo&quot; class I used to create “call to action” links) A few other issues: It downloaded all images, but did not update the URLs in the Markdown files. This was easy to fix with a regex find and replace from https?://lea.verou.me/wp-content/uploads/(\d{4}/\d{2})/([\w\.-]+\.(?:png|gif|jpe?g)) to images/$2 . Some images from some posts were not downloaded – I still have no idea why. It did not download any non-media uploads, e.g. zip files. Thankfully, these were only a couple, so I could detect and port over manually. Older posts included code directly in the content, without code blocks, which meant it was being parsed as HTML, often with disastrous results (e.g. the post just cutting off in the middle of a sentence because it mentioned , which opened an actual element and ate up the rest of the content). I fixed a few manually, but I’m sure there’s more left. Because code was just included as content, the importer also escaped all Markdown special symbols, so adding code blocks around it was not enough, I also had to remove a bunch of backslashes manually. Rethinking Categorization While the importer preserved both tags and categories, this was a good opportunity to rethink whether I need them both, and to re-evaluate how I use them. This spun off into a separate post: Rethinking Categorization . Migrating comments Probably one of the hardest parts of this migration was preserving Disqus comments. In fact, it was so hard that I procrastinated on it for three months, being stuck in a limbo where I couldn’t blog because I’d have to port the new post manually. I’ve documented the process in a separate blog post , as it was quite involved, including some thoughts about what system to use in the future, as I eventually hope to migrate away from Disqus. Keeping URLs cool I wanted to preserve the URL structure of my old site as much as possible, both for SEO, but also because cool URLs don’t change . The WP importer I used allowed me to preserve the /year/month/slug structure of my URLs. I did want to have the blog in its own directory though. This site started as a blog, but I now see it as more of a personal site with a blog. Thankfully, redirecting these URLs to corresponding /blog/ URLs was a one liner using Netlify redirects : /20* /blog/20:splat 301 Going forwards, I also decided to do away with the month being part of the URL, as it complicates the file structure for no discernible benefit and I don’t blog nearly as much now as I did in 2009, e.g. compare 2009 vs 2022 : 38 vs 7! I do think I will start blogging more again now, not only due to the new site, but also due to new interests and a long backlog of ideas (just look at July 2023 so far!). However, I doubt I will ever get back to the pre-2014 levels, I simply don’t have that kind of time anymore (coincidentally, it appears my blogging frequency dropped significantly after I started my PhD ). I also wanted to continue having nice, RESTful, usable URLs, which also requires: URLs that are “hackable” to allow users to move to higher levels of the information architecture by hacking off the end of the URL In practice, this means it’s not enough if tags/foo/ shows all posts tagged “foo”, tags/ should also show all tags. Similarly, it’s not enough if /blog/2023/04/private-fields-considered-harmful/ links to the corresponding blog post , but also: /blog/2023/04/ should show all posts from April 2023 /blog/2023/ should show all posts from 2023 and of course /blog/ should show all posts This proved quite tricky to do with Eleventy, and spanned an entirely different blog post . Overall impressions Overall, I’m happy with the result, and the flexibility. I’ve had a lot of fun with this project, and it was a great distraction during a very difficult time in my life, due to dealing with some serious health issues in my immediate family. However, there are a few things that are now more of a hassle than they were in WP, mainly around the editing flow: In WP, editing a blog post I was looking at in my browser was a single click (provided I was logged in). I guess I could still do that by editing through GitHub, but now I’m spoiled, I want an easy way to edit in my own editor (VS Code, which has a lot of nice features for Markdown editing ), however the only way to do that is to either painfully traverse the directory structure, or …search to find the right *.md file, neither of which is ideal. Previewing a post I was editing was also a single click, whereas now I need to run a local server and manually type the URL in (or browse the website to find it). Making edits now requires me to think of a suitable commit message. Sure, this is useful sometimes, but most of the time, I want the convenience of just saving my changes and being done with it. Open file in VS Code from the browser? There is a way to solve the first problem: VS Code supports a vscode:// protocol that allows you to open a file in VS Code from the browser . This means, this link would open the file for this blog post in VS Code: Edit in VS Code See the issue? I cannot add a button to the UI that only works for me! However, I don’t need to add a button to the UI: as long as I expose the input path of the current page (Eleventy’s page.inputPath ) in the HTML somehow, I can just add a bookmarklet to my own browser that just does: location.href = `vscode://file/Users/leaverou/Documents/lea.verou.me/${document.documentElement.dataset.inputpath}`; In fact, here it is, ready to be dragged to the bookmarks bar: Edit in VS Code Now, if only I could find a way to do the opposite: open the localhost URL that corresponds to the Markdown file I’m editing — and my workflow would be complete! What’s next? Obviously, there’s a lot of work left to do, and I bet people will find a lot more breakage than I had noticed. I also have a backlog of blog post ideas that I can’t wait to write about. But I’ve also been toying around with the idea of porting over my personal (non-tech) blog posts, and keep them in an entirely separate section of the website. I don’t like that my content is currently hostage to Tumblr (2012-2013) and Medium (2017-2021), and would love to own it too, though I’m a bit concerned that properly separating the two would take a lot of work. Anyhow, &#39;nuff said. Ship it, squirrel! 🚢🐿️</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Going Lean</title>
  <link>https://lea.verou.me/blog/2023/going-lean/</link>
  <pubDate>Sun, 17 May 2026 03:25:18 +0200</pubDate>
  <description>WordPress has been with me since my very first post in 2009 . There is a lot to love about it: Its open source, it has a thriving ecosystem, a beautiful default theme, and a revolutionary block editor that makes my inner UX geek giddy. Plus, WP made building a website and publishing content accessible to everyone. No wonder its the most popular CMS in the world , by a huge margin. However, for me, the bad had started to outweigh the good: Things I could do in minutes in a static site, in WP required finding a plugin or tweaking PHP code. It was slow and bloated. Getting a draft out of it and into another medium was a pain. Despite having never been hacked, I was terrified about it, given all the horror stories. I was periodically getting Error establishing a database connection errors, whose frequency kept increasing. It was time to move on. Its not you WP, its me. It seemed obvious that the next step would be a statically generated blog. I had been using Eleventy for a while on a variety of sites at that point and loved it, so using that was a no-brainer. In fact, my blog was one of my last remaining non-JAMstack sites, and by far the biggest. I had built a simple 11ty blog for my husband a year ago, and was almost jealous of the convenience and simplicity. There are so many conveniences that just come for free with this workflow: git, Markdown, custom components, even GitHub Copilot as you write your prose! And if you can make the repo public, oooooh, the possibilities! People could even file PRs and issues for your blog posts! Using Netlify as a platform was also a no-brainer: I had been using it for years, for over 30 sites at this point! I love their simplicity, their focus on developer experience, and their commitment to open source. I also happen to know a bunch of folks there, and they have a great culture too. However, I was dreading the amount of work it would take to migrate 14 years of content, plugins, and styling. The stroke that broke the camels back was a particularly bad db outage. I tweeted about my frustration, but I had already made up my mind. I reviewed the list of plugins I had installed on WP to estimate the amount of work. Nearly all fell in one of two categories: Solving problems I wouldnt have if I wasnt using WP (e.g. SVG support, Dont Muck My Markup) Giving me benefits I could get in 11ty with very little code (e.g. Prism syntax highlighting, Custom Body Class, Disqus, Unlist Posts &amp; Pages, Widget CSS classes) Giving me benefits I could get with existing Eleventy plugins (e.g. Add Anchor Links, Easy Table of Contents) This could actually work! Public or private repo? One of the hardest dilemmas was whether to make the repo for this website public or private. Overall, I was happy to have most files be public, but there were a few things I wanted to keep private: Drafts (some drafts Im ok to share publicly, but not all) Unlisted pages and posts (posts with publicly accessible URLs, but not linked from anywhere) Private pages (e.g. in the previous site I had a password-protected page with my details for conference organizers) Unfortunately, right now its all-or-nothing, even if only one file needs to be private, the whole repo needs to be private. FWIW I dont think it has to be this way, and I tweeted about this, including some ideas about fixing it, either from the GitHub side, or the serverless platform side. Im hoping to write a blog post to expand on this soon. Making the repo public does have many advantages: Transparency is one of my core values, and this is in line with it. People can learn from my code and avoid going down the same rabbit holes I did. People can file issues for problems. People can send PRs to fix both content and functionality. I wouldnt need to use a separate public repo for the data that populates my Speaking , Publications , and Projects pages. I went back and forth quite a lot, but in the end I decided to make it public. In fact, I fully embraced it, by making it as easy as possible to file issues and submit PRs. Each page has a link to report a problem with it, which prefills as much info as possible. This was also a good excuse to try out GitHub Issue Forms , as well as URLs for prefilling the form ! Each page has a link to edit it on GitHub, which automatically takes you through a PR flow if you dont have write access to the repo. Licensing Note that a public repo is not automatically open source . As you know, I have a long track record of open sourcing my code. I love seeing people learning from it, using it in their own projects, and blogging about what theyve learned. So, MIT-licensing the code part of this website is a no-brainer. CC-BY also seems like a no-brainer for content , because, why not? Where it gets tricky is the design . Im well aware that neither my logo nor the visual style of this website would win any design awards; I havent worked as a graphic designer for many years, and it shows. However, its something I feel is very personal to me, my own personal brand, which by definition needs to be unique. Seeing another website with the same logo and/or visual style would feel just as unsettling as walking into a house that looks exactly like mine. Im speaking from experience: Ive had my logo and design copied many times, and it always felt like a violation. Im not sure how to express this distinction in a GitHub LICENSE file, so I havent yet added one, but I did try to outline it in the Credits &amp; Making Of page. Its still difficult to draw the line precisely, especially when it comes to CSS code. Im basically happy for people to copy as much of my CSS code as they want (following MIT license rules), as long as the end result doesnt scream Lea Verou to anyone who has seen this site. But how on Earth do you express that? Migrating content to Markdown The title of this section says to Markdown because thats one of the benefits of this approach: static site generators are largely compatible with each other, so if I ever needed to migrate again, it would be much easier. Thankfully, travelers on this road before me had already paved it. Many open source scripts out there to migrate WP to Markdown! The one that worked well for me was lonekorean/wordpress-export-to-markdown (though I later discovered theres a more updated fork now) It was still a bumpy road. First, it kept getting stuck on parsing the WP XML export, specifically in comments. I use Disqus for comments, but it mirrors comments in the internal WP system. Also, WP seems to continue recording trackbacks even if they are not displayed anywhere. Turns out I had hundreds of thousands of spam trackbacks, which I spent hours cleaning up (it was quite a meditative experience). In the end I got the total comments + trackbacks from 290K down to 26K which reduced the size of the XML export from 210 MB to a mere 31 MB. This did not fix the parsing issue, but allowed me to simply open the file in VS Code and delete the problematic comments manually. It also fixed the uptime issues I was having: I never got another Error establishing a database connection error after that, despite taking my own sweet time to migrate (started in April 2023, and finished in July!). Ideally, I wish WP had an option to export without comments, but I guess thats not a common use case. While this importer is great, and allowed me to configure the file structure in a way that preserved all my URLs, I did lose a few things: Read more separators (filed it as issue #93 ) Figures (they are imported as just images with text underneath) (filed it as issue #94 ) Drafts ( #16 ) Pages (I had to manually copy them over, but it was only a handful) Any custom classes were gone (e.g. a &quot;view-demo&quot; class I used to create call to action links) A few other issues: It downloaded all images, but did not update the URLs in the Markdown files. This was easy to fix with a regex find and replace from https?://lea.verou.me/wp-content/uploads/(\d{4}/\d{2})/([\w\.-]+\.(?:png|gif|jpe?g)) to images/$2 . Some images from some posts were not downloaded I still have no idea why. It did not download any non-media uploads, e.g. zip files. Thankfully, these were only a couple, so I could detect and port over manually. Older posts included code directly in the content, without code blocks, which meant it was being parsed as HTML, often with disastrous results (e.g. the post just cutting off in the middle of a sentence because it mentioned , which opened an actual element and ate up the rest of the content). I fixed a few manually, but Im sure theres more left. Because code was just included as content, the importer also escaped all Markdown special symbols, so adding code blocks around it was not enough, I also had to remove a bunch of backslashes manually. Rethinking Categorization While the importer preserved both tags and categories, this was a good opportunity to rethink whether I need them both, and to re-evaluate how I use them. This spun off into a separate post: Rethinking Categorization . Migrating comments Probably one of the hardest parts of this migration was preserving Disqus comments. In fact, it was so hard that I procrastinated on it for three months, being stuck in a limbo where I couldnt blog because Id have to port the new post manually. Ive documented the process in a separate blog post , as it was quite involved, including some thoughts about what system to use in the future, as I eventually hope to migrate away from Disqus. Keeping URLs cool I wanted to preserve the URL structure of my old site as much as possible, both for SEO, but also because cool URLs dont change . The WP importer I used allowed me to preserve the /year/month/slug structure of my URLs. I did want to have the blog in its own directory though. This site started as a blog, but I now see it as more of a personal site with a blog. Thankfully, redirecting these URLs to corresponding /blog/ URLs was a one liner using Netlify redirects : /20* /blog/20:splat 301 Going forwards, I also decided to do away with the month being part of the URL, as it complicates the file structure for no discernible benefit and I dont blog nearly as much now as I did in 2009, e.g. compare 2009 vs 2022 : 38 vs 7! I do think I will start blogging more again now, not only due to the new site, but also due to new interests and a long backlog of ideas (just look at July 2023 so far!). However, I doubt I will ever get back to the pre-2014 levels, I simply dont have that kind of time anymore (coincidentally, it appears my blogging frequency dropped significantly after I started my PhD ). I also wanted to continue having nice, RESTful, usable URLs, which also requires: URLs that are hackable to allow users to move to higher levels of the information architecture by hacking off the end of the URL In practice, this means its not enough if tags/foo/ shows all posts tagged foo, tags/ should also show all tags. Similarly, its not enough if /blog/2023/04/private-fields-considered-harmful/ links to the corresponding blog post , but also: /blog/2023/04/ should show all posts from April 2023 /blog/2023/ should show all posts from 2023 and of course /blog/ should show all posts This proved quite tricky to do with Eleventy, and spanned an entirely different blog post . Overall impressions Overall, Im happy with the result, and the flexibility. Ive had a lot of fun with this project, and it was a great distraction during a very difficult time in my life, due to dealing with some serious health issues in my immediate family. However, there are a few things that are now more of a hassle than they were in WP, mainly around the editing flow: In WP, editing a blog post I was looking at in my browser was a single click (provided I was logged in). I guess I could still do that by editing through GitHub, but now Im spoiled, I want an easy way to edit in my own editor (VS Code, which has a lot of nice features for Markdown editing ), however the only way to do that is to either painfully traverse the directory structure, or search to find the right *.md file, neither of which is ideal. Previewing a post I was editing was also a single click, whereas now I need to run a local server and manually type the URL in (or browse the website to find it). Making edits now requires me to think of a suitable commit message. Sure, this is useful sometimes, but most of the time, I want the convenience of just saving my changes and being done with it. Open file in VS Code from the browser? There is a way to solve the first problem: VS Code supports a vscode:// protocol that allows you to open a file in VS Code from the browser . This means, this link would open the file for this blog post in VS Code: Edit in VS Code See the issue? I cannot add a button to the UI that only works for me! However, I dont need to add a button to the UI: as long as I expose the input path of the current page (Eleventys page.inputPath ) in the HTML somehow, I can just add a bookmarklet to my own browser that just does: location.href = `vscode://file/Users/leaverou/Documents/lea.verou.me/${document.documentElement.dataset.inputpath}`; In fact, here it is, ready to be dragged to the bookmarks bar: Edit in VS Code Now, if only I could find a way to do the opposite: open the localhost URL that corresponds to the Markdown file Im editing and my workflow would be complete! Whats next? Obviously, theres a lot of work left to do, and I bet people will find a lot more breakage than I had noticed. I also have a backlog of blog post ideas that I cant wait to write about. But Ive also been toying around with the idea of porting over my personal (non-tech) blog posts, and keep them in an entirely separate section of the website. I dont like that my content is currently hostage to Tumblr (2012-2013) and Medium (2017-2021), and would love to own it too, though Im a bit concerned that properly separating the two would take a lot of work. Anyhow, &#39;nuff said. Ship it, squirrel!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Rethinking Categorization</title>
  <link>https://lea.verou.me/blog/2023/rethinking-categorization/</link>
  <pubDate>Sun, 17 May 2026 03:25:17 +0200</pubDate>
  <description>This is the third spinoff post in the migration saga of this blog from WordPress to 11ty . Migrating was a good opportunity to rethink the information architecture of my site , especially around categorization. Categories vs Tags Just like most WP users, I was using both categories and tags, simply because they came for free. However the difference between them was a bit fuzzy, as evidenced by how inconsistently they are used, both here and around the Web. I was mainly using Categories for the type of article (Articles, Rants, Releases, Tips, Tutorials, News, Thoughts), however there were also categories that were more like content tags (e.g. CSS WG, Original, Speaking, Benchmarks). This was easily solved by moving the latter to actual tags. However, tags are no panacea, there are several issues with them as well. Problems with tags Tag aliases First, there were many tags that were synonyms of each other , and posts were fragmented across them, or had to include both (e.g. JS and Javascript ). I addressed this by defining aliases in a global data file, and using Eleventy to dynamically build Netlify redirects for them. # Tag aliases {% for alias, tag in tag_aliases %}/tags/{{ alias }}/ /tags/{{ tag }}/ 301 {% endfor %} Turns out I’m not the first to think of building the Netlify _redirects file dynamically, some googling revealed this blog post from 2021 that does the same thing. I’ve also decided to expose these aliases in the tags index : “Orphan” tags Lastly, another issue is what I call “orphan tags”: Tags that are only used in a single post. The primary use case for both tags and categories is to help you discover related content. Tags that are only used once clutter the list of tags, but serve no actual purpose. It is important to note that orphan tags are not (always) an authoring mistake. While some tags are definitely too specific and thus unlikely to be used again, the vast majority of orphan tags are tags that could plausibly be used again, but it simply hasn’t happened. I definitely removed a bunch of overly specific tags from the content, but was still left with more orphan tags than tags with more than one post (103 vs 78 as I write these lines). For (1), the best course of action is probably to remove the tags from the content altogether. However for (2), there are two things to consider. How to best display orphan tags in the tag index ? For the tag index , I’ve separated orphan tags from the rest, and I’m displaying them in a element at the end, that is collapsed by default. Each tag is a link to the post that uses it instead of a tags page, since there is only one post that uses it. How to best display orphan tags in the post itself? This is a little trickier. For now, I’ve refrained from making them links, and I’m displaying them faded out to communicate this. Another alternative I’m contemplating is to hide them entirely. Not as a punitive measure because they have failed at their one purpose in life 😅, but because this would allow me to use tags liberally, and only what sticks would be displayed to the end user. A third, intermediate solution, would be to have a “and 4 orphan tags” message at the end of the list of tags, which can be clicked to show them. These are not just UX/IA improvements, they are also performance improvements. Not linking orphan tags to tag pages means I don’t need to generate these tag pages at all. Since the majority of tags are orphan tags, this allowed me to substantially reduce the number of pages that need to be generated, and cut down build time by a whopping 40% , from 2.7s to 1.7s (on average). Tag hierarchies? The theory is that categories are a taxonomy and tags a folksonomy . Taxonomies can be hierarchical, but folksonomies are, by definition , flat. However, in practice, tags almost always have an implicit hierarchy , which is also what research on folksonomies in the wild tends to find . Examples from this very blog: There is a separate tag for ES (ECMAScript), and a separate one for JS . However, any post tagged ES should also be tagged JS – though the opposite is not true. There is a tag for CSS , tags for specific CSS specifications (e.g. CSS Backgrounds &amp; Borders ), and even tags for specific CSS functions or properties (e.g. background-attachment , background-size ). However, these are not orthogonal: posts tagged with specific CSS features should also be tagged with the CSS spec that contains them, as well as a general “CSS” tag. I have yet to see a use case for tagging that does not result in implicit hierarchies. Yet, all UIs for entering tags assume that they are flat. Instead, it’s up to each individual post to maintain these relationships, which is tedious and error prone. In practice, the more general tags are often left out, but not intentionally or predictably. It would be much better to be able to define this hierarchy in a central place, and have it automatically applied to all posts. In 11ty, it could be as simple as a data file for each tag’s “parent” tag. Every time the tag is used, its parent is also added to the post automatically, recursively all the way up to the root (at build time). I have not tried this yet, but I’m excited to experiment with it once I have a bit more time. Categories vs Tags: Reprise Back to our original dilemma: Do I still need categories, especially if I eventually implement tag hierarchies? It does seem that the categories I used in WP for the article type (Articles, Rants, Releases, Tips, Tutorials, News, Thoughts etc) are somewhat distinct from my usage of tags, which are more about the content of the article. However, it is unclear whether this is the best use of categories, or whether I should just use tags for this as well. Another common practice is to use tags for more specific content tags, and categories for broader areas (e.g. “Software engineering”, “Product”, “HCI”, “Personal” etc). Skipping past the point that tag hierarchies make it easy to use tags for this too, this makes me think: maybe what is needed is actually metadata, not categories. Instead of deciding that categories hold the article type, or the broader domain, what if we had certain attributes for both of these things. Then, we could have a “type” attribute, and a “domain” attribute, and use them both for categorization, and for filtering. Since Eleventy already supports arbitrary metadata, this is just a matter of implementation. Lots to think about, but one thing seems clear: Categories do not have a clear purpose, and thus I’m doing away with them. For now, I have converted all past categories to tags, so that the additional metadata is not lost, and I will revisit how to best expose this metadata in the future.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>11ty: Index ALL the things!</title>
  <link>https://lea.verou.me/blog/2023/11ty-indices/</link>
  <pubDate>Sun, 17 May 2026 03:25:17 +0200</pubDate>
  <description>This is a second spinoff post in the migration saga of this blog from WordPress to 11ty . On good URLs It was important to me to have good, RESTful, usable, hackable URLs. While a lot of that is easy and comes for free, following this principle with Eleventy proved quite hard: URLs that are “hackable” to allow users to move to higher levels of the information architecture by hacking off the end of the URL What does this mean in practice? It means it’s not enough if tags/foo/ shows all posts tagged “foo”, tags/ should also show all tags. Similarly, it’s not enough if /blog/2023/04/private-fields-considered-harmful/ links to the corresponding blog post , but also: /blog/2023/04/ should show all posts from April 2023 /blog/2023/ should show all posts from 2023 /blog/ should show all posts Eleventy “Pagination” Primer Eleventy has a pagination feature, which actually does a lot more than pagination: it’s used every time you want to generate several pages from a single template by chunking object keys and using them in permalinks. One of the most common non-pagination use cases for it is tag pages. The typical /tags/tagname/ page is generated by a deceptively simple template: --- pagination: data: collections size: 1 alias: tag filter: [&quot;blog&quot;, &quot;all&quot;] permalink: /blog/tags/{{ tag }}/ override:tags: [] eleventyComputed: title: &quot;{{ collections[ tag ].length | pluralize(&#39;post&#39;) }} on {{ tag | format_tag }}&quot; --- {% set taglist = collections[ tag ] | reverse %} {# ... Loop over taglist here ... #} That was it, then you just loop over taglist (or collections[ tag ] | reverse directly) to template the posts under each tag in reverse chronological order. Simple, right? But what about the indices? As it currently stands, visiting /blog/tags/ will just produce a 404. Index of all tags Creating an index of all tags only involves a single page, so it does not involve contorting the pagination feature to mind-bending levels, like the rest of this post. However, we need to do some processing to sort the tags by post count, and remove those that are not “real” tags. There are many ways to go about with this. The quick and dirty way The quick and dirty way is to just iterate over collections and count the posts for each tag: {% for tag, posts in collections %} {{ tags.one(tag) }} ({{ posts.length }} posts) {% endfor %} Unfamiliar with the tags.one() syntax above? It’s using Nunjucks macros (there’s a {% import &quot;_tags.njk&quot; as tags %} earlier in the template too). Macros allow you to create parameterized templates snippets, and I’ve come to love them during this migration project. The problem is that this does not produce the tags in any particular order, and you usually want frequently used tags to come first. You could actually fix that with CSS: {% for tag, posts in collections %} {{ tags.one(tag) }} ({{ posts.length }} posts) {% endfor %} The only advantage of this approach is that this is entirely doable via templates and doesn’t require any JS, but there are several drawbacks. First, it limits what styling you can use: for the order property to actually have an effect, you need to be using either Flexbox or Grid layout. But worse, the order property does not affect the order screen readers read your content one iota. Dynamic postsByTag collection To do it all in Eleventy, the most common way is a dynamic collection, added via eleventyConfig.addCollection() : config.addCollection(&quot;postsByTag&quot;, (collectionApi) =&gt; { const posts = collectionApi.getFilteredByTag(&quot;blog&quot;); let ret = {}; for (let post of posts) { for (let tag of post.data.tags) { ret[tag] ??= []; ret[tag].push(post); } } // Now sort, and reconstruct the object ret = Object.fromEntries(Object.entries(ret).sort((a, b) =&gt; b[1].length - a[1].length)); return ret; }); That we then use in the template: {% for tag, posts in collections.postsByTag %} {{ tags.one(tag) }} ({{ posts }} posts) {% endfor %} Custom taglist filter Another way is a custom filter: config.addFilter(&quot;taglist&quot; (collections) =&gt; { let tags = Object.keys(collections).filter(filters.is_real_tag); tags.sort((a, b) =&gt; collections[b].length - collections[a].length); return Object.fromEntries(tags.map(tag =&gt; [tag, collections[tag].length])); }); used like this: {% for tag, posts in collections | taglist %} {{ tags.one(tag) }} ({{ posts }} posts) {% endfor %} Usually, filters are meant for more broadly usable utility functions, and are not a good fit here. However, the filter approach can be more elegant if your use case is more complicated and involves many different outputs. For the vast majority of use cases, a dynamic collection is more appropriate. Index of posts by year Generating yearly indices can be quite similar as generating tag pages. The main difference is that for tags the collection already exists ( collections[tag] ) whereas for years you have to build it yourself, using addCollection() in your config file. This seems to come up pretty frequently, both for years and months (the next section): https://github.com/11ty/eleventy/issues/502 https://github.com/tomayac/blogccasion/issues/19 https://github.com/11ty/eleventy/issues/316#issuecomment-441053919 https://github.com/11ty/eleventy/issues/1284 Group posts by year in Eleventy (Blog post) This is what I did, after spending a pretty long time reading discussions and blog posts: eleventyConfig.addCollection(&quot;postsByYear&quot;, (collectionApi) =&gt; { const posts = collectionApi.getFilteredByTag(&quot;blog&quot;).reverse(); const ret = {}; for (let post of posts) { let key = post.date.getFullYear(); ret[key] ??= []; ret[key].push(post); } return ret; }); and then, in blog/year-index.njk : --- pagination: data: collections.postsByYear size: 1 alias: year permalink: /blog/{{ year }}/ override:tags: [] eleventyComputed: title: &quot;Posts from {{ year }}&quot; --- {% import &quot;_posts.njk&quot; as posts %} {{ posts.list(collections.postsByYear[year], {style: &quot;compact&quot;}) }} You can see an example of such a page here: Posts from 2010 . Bonus, because this collection is more broadly useful, I was able to utilize it to make a little yearly archives bar chart! Index of posts by month Pagination only works on one level: You cannot paginate a paginated collection (though Zach has a workaround for that that I’m still trying to wrap my head around). This also means that you cannot easily paginate tag or year index pages. I worked around that by simply showing a more compact post list if there are more than 10 posts. However, it also means you cannot process the postsByYear collection and somehow paginate by month. You need to create another collection, this time with the year + month as the key: config.addCollection(&quot;postsByMonth&quot;, (collectionApi) =&gt; { const posts = collectionApi.getFilteredByTag(&quot;blog&quot;).reverse(); const ret = {}; for (let post of posts) { let key = filters.format_date(post.date, &quot;iso&quot;).substring(0, 7); // YYYY-MM ret[key] ??= []; ret[key].push(post); } return ret; }); And a separate blog/month-index.njk file: --- pagination: data: collections.postsByMonth size: 1 alias: month permalink: /blog/{{ month | replace(&quot;-&quot;, &quot;/&quot;) }}/ override:tags: [] eleventyComputed: title: &quot;Posts from {{ month | format_date({month: &#39;long&#39;, year: &#39;numeric&#39;}) }}&quot; --- {% import &quot;_posts.njk&quot; as posts %} {{ posts.list(collections.postsByMonth[month], {style: &quot;compact&quot;}) }} You can see an example of such a page here: Posts from December 2010 .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Rethinking Categorization</title>
  <link>https://lea.verou.me/blog/2023/rethinking-categorization/</link>
  <pubDate>Sun, 17 May 2026 03:25:17 +0200</pubDate>
  <description>This is the third spinoff post in the migration saga of this blog from WordPress to 11ty . Migrating was a good opportunity to rethink the information architecture of my site , especially around categorization. Categories vs Tags Just like most WP users, I was using both categories and tags, simply because they came for free. However the difference between them was a bit fuzzy, as evidenced by how inconsistently they are used, both here and around the Web. I was mainly using Categories for the type of article (Articles, Rants, Releases, Tips, Tutorials, News, Thoughts), however there were also categories that were more like content tags (e.g. CSS WG, Original, Speaking, Benchmarks). This was easily solved by moving the latter to actual tags. However, tags are no panacea, there are several issues with them as well. Problems with tags Tag aliases First, there were many tags that were synonyms of each other , and posts were fragmented across them, or had to include both (e.g. JS and Javascript ). I addressed this by defining aliases in a global data file, and using Eleventy to dynamically build Netlify redirects for them. # Tag aliases {% for alias, tag in tag_aliases %}/tags/{{ alias }}/ /tags/{{ tag }}/ 301 {% endfor %} Turns out Im not the first to think of building the Netlify _redirects file dynamically, some googling revealed this blog post from 2021 that does the same thing. Ive also decided to expose these aliases in the tags index : Orphan tags Lastly, another issue is what I call orphan tags: Tags that are only used in a single post. The primary use case for both tags and categories is to help you discover related content. Tags that are only used once clutter the list of tags, but serve no actual purpose. It is important to note that orphan tags are not (always) an authoring mistake. While some tags are definitely too specific and thus unlikely to be used again, the vast majority of orphan tags are tags that could plausibly be used again, but it simply hasnt happened. I definitely removed a bunch of overly specific tags from the content, but was still left with more orphan tags than tags with more than one post (103 vs 78 as I write these lines). For (1), the best course of action is probably to remove the tags from the content altogether. However for (2), there are two things to consider. How to best display orphan tags in the tag index ? For the tag index , Ive separated orphan tags from the rest, and Im displaying them in a element at the end, that is collapsed by default. Each tag is a link to the post that uses it instead of a tags page, since there is only one post that uses it. How to best display orphan tags in the post itself? This is a little trickier. For now, Ive refrained from making them links, and Im displaying them faded out to communicate this. Another alternative Im contemplating is to hide them entirely. Not as a punitive measure because they have failed at their one purpose in life , but because this would allow me to use tags liberally, and only what sticks would be displayed to the end user. A third, intermediate solution, would be to have a and 4 orphan tags message at the end of the list of tags, which can be clicked to show them. These are not just UX/IA improvements, they are also performance improvements. Not linking orphan tags to tag pages means I dont need to generate these tag pages at all. Since the majority of tags are orphan tags, this allowed me to substantially reduce the number of pages that need to be generated, and cut down build time by a whopping 40% , from 2.7s to 1.7s (on average). Tag hierarchies? The theory is that categories are a taxonomy and tags a folksonomy . Taxonomies can be hierarchical, but folksonomies are, by definition , flat. However, in practice, tags almost always have an implicit hierarchy , which is also what research on folksonomies in the wild tends to find . Examples from this very blog: There is a separate tag for ES (ECMAScript), and a separate one for JS . However, any post tagged ES should also be tagged JS though the opposite is not true. There is a tag for CSS , tags for specific CSS specifications (e.g. CSS Backgrounds &amp; Borders ), and even tags for specific CSS functions or properties (e.g. background-attachment , background-size ). However, these are not orthogonal: posts tagged with specific CSS features should also be tagged with the CSS spec that contains them, as well as a general CSS tag. I have yet to see a use case for tagging that does not result in implicit hierarchies. Yet, all UIs for entering tags assume that they are flat. Instead, its up to each individual post to maintain these relationships, which is tedious and error prone. In practice, the more general tags are often left out, but not intentionally or predictably. It would be much better to be able to define this hierarchy in a central place, and have it automatically applied to all posts. In 11ty, it could be as simple as a data file for each tags parent tag. Every time the tag is used, its parent is also added to the post automatically, recursively all the way up to the root (at build time). I have not tried this yet, but Im excited to experiment with it once I have a bit more time. Categories vs Tags: Reprise Back to our original dilemma: Do I still need categories, especially if I eventually implement tag hierarchies? It does seem that the categories I used in WP for the article type (Articles, Rants, Releases, Tips, Tutorials, News, Thoughts etc) are somewhat distinct from my usage of tags, which are more about the content of the article. However, it is unclear whether this is the best use of categories, or whether I should just use tags for this as well. Another common practice is to use tags for more specific content tags, and categories for broader areas (e.g. Software engineering, Product, HCI, Personal etc). Skipping past the point that tag hierarchies make it easy to use tags for this too, this makes me think: maybe what is needed is actually metadata, not categories. Instead of deciding that categories hold the article type, or the broader domain, what if we had certain attributes for both of these things. Then, we could have a type attribute, and a domain attribute, and use them both for categorization, and for filtering. Since Eleventy already supports arbitrary metadata, this is just a matter of implementation. Lots to think about, but one thing seems clear: Categories do not have a clear purpose, and thus Im doing away with them. For now, I have converted all past categories to tags, so that the additional metadata is not lost, and I will revisit how to best expose this metadata in the future.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>11ty: Index ALL the things!</title>
  <link>https://lea.verou.me/blog/2023/11ty-indices/</link>
  <pubDate>Sun, 17 May 2026 03:25:17 +0200</pubDate>
  <description>This is a second spinoff post in the migration saga of this blog from WordPress to 11ty . On good URLs It was important to me to have good, RESTful, usable, hackable URLs. While a lot of that is easy and comes for free, following this principle with Eleventy proved quite hard: URLs that are hackable to allow users to move to higher levels of the information architecture by hacking off the end of the URL What does this mean in practice? It means its not enough if tags/foo/ shows all posts tagged foo, tags/ should also show all tags. Similarly, its not enough if /blog/2023/04/private-fields-considered-harmful/ links to the corresponding blog post , but also: /blog/2023/04/ should show all posts from April 2023 /blog/2023/ should show all posts from 2023 /blog/ should show all posts Eleventy Pagination Primer Eleventy has a pagination feature, which actually does a lot more than pagination: its used every time you want to generate several pages from a single template by chunking object keys and using them in permalinks. One of the most common non-pagination use cases for it is tag pages. The typical /tags/tagname/ page is generated by a deceptively simple template: --- pagination: data: collections size: 1 alias: tag filter: [&quot;blog&quot;, &quot;all&quot;] permalink: /blog/tags/{{ tag }}/ override:tags: [] eleventyComputed: title: &quot;{{ collections[ tag ].length | pluralize(&#39;post&#39;) }} on {{ tag | format_tag }}&quot; --- {% set taglist = collections[ tag ] | reverse %} {# ... Loop over taglist here ... #} That was it, then you just loop over taglist (or collections[ tag ] | reverse directly) to template the posts under each tag in reverse chronological order. Simple, right? But what about the indices? As it currently stands, visiting /blog/tags/ will just produce a 404. Index of all tags Creating an index of all tags only involves a single page, so it does not involve contorting the pagination feature to mind-bending levels, like the rest of this post. However, we need to do some processing to sort the tags by post count, and remove those that are not real tags. There are many ways to go about with this. The quick and dirty way The quick and dirty way is to just iterate over collections and count the posts for each tag: {% for tag, posts in collections %} {{ tags.one(tag) }} ({{ posts.length }} posts) {% endfor %} Unfamiliar with the tags.one() syntax above? Its using Nunjucks macros (theres a {% import &quot;_tags.njk&quot; as tags %} earlier in the template too). Macros allow you to create parameterized templates snippets, and Ive come to love them during this migration project. The problem is that this does not produce the tags in any particular order, and you usually want frequently used tags to come first. You could actually fix that with CSS: {% for tag, posts in collections %} {{ tags.one(tag) }} ({{ posts.length }} posts) {% endfor %} The only advantage of this approach is that this is entirely doable via templates and doesnt require any JS, but there are several drawbacks. First, it limits what styling you can use: for the order property to actually have an effect, you need to be using either Flexbox or Grid layout. But worse, the order property does not affect the order screen readers read your content one iota. Dynamic postsByTag collection To do it all in Eleventy, the most common way is a dynamic collection, added via eleventyConfig.addCollection() : config.addCollection(&quot;postsByTag&quot;, (collectionApi) =&gt; { const posts = collectionApi.getFilteredByTag(&quot;blog&quot;); let ret = {}; for (let post of posts) { for (let tag of post.data.tags) { ret[tag] ??= []; ret[tag].push(post); } } // Now sort, and reconstruct the object ret = Object.fromEntries(Object.entries(ret).sort((a, b) =&gt; b[1].length - a[1].length)); return ret; }); That we then use in the template: {% for tag, posts in collections.postsByTag %} {{ tags.one(tag) }} ({{ posts }} posts) {% endfor %} Custom taglist filter Another way is a custom filter: config.addFilter(&quot;taglist&quot; (collections) =&gt; { let tags = Object.keys(collections).filter(filters.is_real_tag); tags.sort((a, b) =&gt; collections[b].length - collections[a].length); return Object.fromEntries(tags.map(tag =&gt; [tag, collections[tag].length])); }); used like this: {% for tag, posts in collections | taglist %} {{ tags.one(tag) }} ({{ posts }} posts) {% endfor %} Usually, filters are meant for more broadly usable utility functions, and are not a good fit here. However, the filter approach can be more elegant if your use case is more complicated and involves many different outputs. For the vast majority of use cases, a dynamic collection is more appropriate. Index of posts by year Generating yearly indices can be quite similar as generating tag pages. The main difference is that for tags the collection already exists ( collections[tag] ) whereas for years you have to build it yourself, using addCollection() in your config file. This seems to come up pretty frequently, both for years and months (the next section): https://github.com/11ty/eleventy/issues/502 https://github.com/tomayac/blogccasion/issues/19 https://github.com/11ty/eleventy/issues/316#issuecomment-441053919 https://github.com/11ty/eleventy/issues/1284 Group posts by year in Eleventy (Blog post) This is what I did, after spending a pretty long time reading discussions and blog posts: eleventyConfig.addCollection(&quot;postsByYear&quot;, (collectionApi) =&gt; { const posts = collectionApi.getFilteredByTag(&quot;blog&quot;).reverse(); const ret = {}; for (let post of posts) { let key = post.date.getFullYear(); ret[key] ??= []; ret[key].push(post); } return ret; }); and then, in blog/year-index.njk : --- pagination: data: collections.postsByYear size: 1 alias: year permalink: /blog/{{ year }}/ override:tags: [] eleventyComputed: title: &quot;Posts from {{ year }}&quot; --- {% import &quot;_posts.njk&quot; as posts %} {{ posts.list(collections.postsByYear[year], {style: &quot;compact&quot;}) }} You can see an example of such a page here: Posts from 2010 . Bonus, because this collection is more broadly useful, I was able to utilize it to make a little yearly archives bar chart! Index of posts by month Pagination only works on one level: You cannot paginate a paginated collection (though Zach has a workaround for that that Im still trying to wrap my head around). This also means that you cannot easily paginate tag or year index pages. I worked around that by simply showing a more compact post list if there are more than 10 posts. However, it also means you cannot process the postsByYear collection and somehow paginate by month. You need to create another collection, this time with the year + month as the key: config.addCollection(&quot;postsByMonth&quot;, (collectionApi) =&gt; { const posts = collectionApi.getFilteredByTag(&quot;blog&quot;).reverse(); const ret = {}; for (let post of posts) { let key = filters.format_date(post.date, &quot;iso&quot;).substring(0, 7); // YYYY-MM ret[key] ??= []; ret[key].push(post); } return ret; }); And a separate blog/month-index.njk file: --- pagination: data: collections.postsByMonth size: 1 alias: month permalink: /blog/{{ month | replace(&quot;-&quot;, &quot;/&quot;) }}/ override:tags: [] eleventyComputed: title: &quot;Posts from {{ month | format_date({month: &#39;long&#39;, year: &#39;numeric&#39;}) }}&quot; --- {% import &quot;_posts.njk&quot; as posts %} {{ posts.list(collections.postsByMonth[month], {style: &quot;compact&quot;}) }} You can see an example of such a page here: Posts from December 2010 .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Migrating Disqus from WP to 11ty</title>
  <link>https://lea.verou.me/blog/2023/preserve-disqus/</link>
  <pubDate>Sun, 17 May 2026 03:25:16 +0200</pubDate>
  <description>So I recently ported my 14 year old blog from WordPress to Eleventy . I had been using Disqus for comments for years, so I didn’t want to lose them, even if I ended up using a different solution for the future (or no comments at all). Looking around for an existing solution did not yield many results. There’s Zach’s eleventy-import-disqus but it’s aimed at importing Disqus comments as static copies, but I wanted to have the option to continue using Disqus. Looking at the WP generated HTML source, I noticed that Disqus was using the WP post id (a number that is not displayed in the UI) to link its threads to the posts. However, the importer I used did not preserve the post ids as metadata (filed issue #95 ). What to do? Getting the WP post id My first thought was to add the post id to each post manually, but use a HEAD request to my existing blog to read it from the Link header, possibly en masse. My second thought was, if I can use JS to get it, maybe I can include Disqus dynamically, through JS, after it procures this number. Then I remembered that 11ty can handle any number of different data sources, and combines them all into a single data object. If I could build an index of slug → post id as another data file, I could add a post id via JS in the 11ty config. My last epiphany was realizing I didn’t need any HTTP requests to get the post id: it was all in the exported sitemap XML, just unused by the importer! Indeed, each included a element with the post id and a element with the URL. I tried to open it in Chrome so I could run some JS on it and build the index, but it complained of parse errors. When I fixed them, the tab crashed under its sheer size. I needed to remove non-relevant data, and I needed to do it fast. All I really needed was the post id, the slug, and the containing element. Since this did not just contain posts, but also other types of content, such as attachments or custom blocks, we also needed to retain so we can filter these out. I copied the XML over to a separate file, and run a series of find &amp; replaces in VS Code: ^(?!.*(wp:post_id|wp:post_type| | )).+\n (regex) with empty string \n{3,} (regex) with \n (to remove empty lines) wp:post with post to remove namespaces and make the XML easier to handle https://lea.verou.me/ with empty string and with to keep just the yyyy/mm/slug part of the URL Then added at the top and wrapped everything in a element to make it valid XML. This resulted in a series of elements that looked like this: 2023/04/private-fields-considered-harmful 3599 2023/04/private-fields-considered-harmful/image-27 3600 At this point, we have exuahsted the capabilities of find &amp; replace; it’s time for some JS! I opened the file in Chrome and ran: copy(Object.assign({}, ...[...document.querySelectorAll(&quot;item&quot;)] .filter(item =&gt; item.querySelector(&quot;post_type&quot;).textContent === &quot;post&quot;) .map(item =&gt; ({ [item.querySelector(&quot;link&quot;).textContent]: item.querySelector(&quot;post_id&quot;).textContent } )))); which copies JSON like this to the clipboard, ready to be pasted in a JSON file (I used wpids.json ): { ... &quot;2022/11/tag-2&quot;: &quot;3531&quot;, &quot;2023/03/contrast-ratio-new-home&quot;: &quot;3592&quot;, &quot;2023/04/private-fields-considered-harmful&quot;: &quot;3599&quot; } Some cleanup was still needed, but this was basically good to go. Adding the post id to the posts To inject a wpid property to each post, I added a blog.11tydata.js file with the following: module.exports = { eleventyComputed: { postUrlStem: data =&gt; { return data.page.filePathStem.replace(/^\/blog\/|\/index$/g, &quot;&quot;); }, wpid: data =&gt; { return data.wpids[data.postUrlStem]; } } }; Linking to Disqus We now have the post id, and we can use it in our template. Adapting the code from the Universal Embed Code , we get: {% if wpid %} /** * RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT * THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR * PLATFORM OR CMS. * * LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: * https://disqus.com/admin/universalcode/#configuration-variables */ var disqus_config = function () { // Replace PAGE_URL with your page&#39;s canonical URL variable this.page.url = &#39;https://lea.verou.me/{{ postUrlStem }}/&#39;; // Replace PAGE_IDENTIFIER with your page&#39;s unique identifier variable this.page.identifier = &quot;{{ wpid }} https:\/\/lea.verou.me\/?p={{ wpid }}&quot;; }; (function() { // REQUIRED CONFIGURATION VARIABLE: EDIT THE SHORTNAME BELOW var d = document, s = d.createElement(&#39;script&#39;); // IMPORTANT: Replace EXAMPLE with your forum shortname! s.src = &#39;https://leaverou.disqus.com/embed.js&#39;; s.setAttribute(&#39;data-timestamp&#39;, +new Date()); (d.head || d.body).appendChild(s); })(); {% endif %} That’s it! This now works and displays the Disqus threads correctly! Using Disqus on new posts as well Note that as it currently stands, this will not display the Disqus UI on new posts, since they won’t have a wpid. Even if I switch to something else in the future, Disqus is better than nothing meanwhile (for me – many people would disagree: switching to no comments at all seems very common when people switch to a SSG blog). So, new posts don’t have a wpid, but they don’t need one either. As long as we pass some kind of unique identifier to Disqus, we have a comment thread. The easiest way to do this is to use the post’s path, e.g. 2023/preserve-disqus for this one, as this is guaranteed to be unique. We also want to be able to disable comments on a per-post basis, so we need a way to do that. So instead of dealing with wpid directly in templates, I added another computed property in blog.11tydata.js : disqus_id: data =&gt; { let wpid = data.wpid; if (wpid) { return `${ wpid } https:\/\/lea.verou.me\/?p=${ wpid }`; } else if (data.disqus !== false) { return typeof data.disqus !== &quot;string&quot;? data.postUrlStem : data.disqus; } } Note that this allows us to pass a custom identifier to Disqus by using a string, disable it by using false , or just get the automatic behavior by using true or not specifying it at all. The custom identifier can be useful if we want to change the URL of a post without losing the comments. Then, I updated the template to use disqus_id instead of wpid . What’s next? I don’t know if I will continue using Disqus. It’s convenient, but also heavyweight, and there are privacy concerns around it. However, I’m not sure what I would use instead. Any third party SaaS service would have the same privacy issues. Not necessarily now, but quite likely in the future. I’ve looked into Webmentions , but the end-user experience does not compare to a regular comment system, and it seems like quite a hassle to implement. Utterances is a really cool idea: it uses GitHub issues as a backend for a comment system. Having myself (ab)used the GitHub API as a storage backend many a times (even as early as 2012 ), I can see the appeal. This may be a viable path forwards, though I need to verify that GitHub Issues can be easily exported, so that I’m not locked in. On a similar vein, I really loved Gisqus seems great too: It’s like Utterances, but uses GitHub Discussions instead of Issues. What holds me back from switching to it is that Discussions cannot yet be exported, and I think portability is important here. People don’t even really use comments much anymore, they post on social media instead. I would have loved some way to simply collect the discussions about the post from various social media and display them underneath, but with API prices getting out of control ( 1 2 ), that doesn’t seem feasible. If there are any options I missed, please let me know in the (Disqus, for now 😕) comments!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Migrating Disqus from WP to 11ty</title>
  <link>https://lea.verou.me/blog/2023/preserve-disqus/</link>
  <pubDate>Sun, 17 May 2026 03:25:16 +0200</pubDate>
  <description>So I recently ported my 14 year old blog from WordPress to Eleventy . I had been using Disqus for comments for years, so I didnt want to lose them, even if I ended up using a different solution for the future (or no comments at all). Looking around for an existing solution did not yield many results. Theres Zachs eleventy-import-disqus but its aimed at importing Disqus comments as static copies, but I wanted to have the option to continue using Disqus. Looking at the WP generated HTML source, I noticed that Disqus was using the WP post id (a number that is not displayed in the UI) to link its threads to the posts. However, the importer I used did not preserve the post ids as metadata (filed issue #95 ). What to do? Getting the WP post id My first thought was to add the post id to each post manually, but use a HEAD request to my existing blog to read it from the Link header, possibly en masse. My second thought was, if I can use JS to get it, maybe I can include Disqus dynamically, through JS, after it procures this number. Then I remembered that 11ty can handle any number of different data sources, and combines them all into a single data object. If I could build an index of slug post id as another data file, I could add a post id via JS in the 11ty config. My last epiphany was realizing I didnt need any HTTP requests to get the post id: it was all in the exported sitemap XML, just unused by the importer! Indeed, each included a element with the post id and a element with the URL. I tried to open it in Chrome so I could run some JS on it and build the index, but it complained of parse errors. When I fixed them, the tab crashed under its sheer size. I needed to remove non-relevant data, and I needed to do it fast. All I really needed was the post id, the slug, and the containing element. Since this did not just contain posts, but also other types of content, such as attachments or custom blocks, we also needed to retain so we can filter these out. I copied the XML over to a separate file, and run a series of find &amp; replaces in VS Code: ^(?!.*(wp:post_id|wp:post_type| | )).+\n (regex) with empty string \n{3,} (regex) with \n (to remove empty lines) wp:post with post to remove namespaces and make the XML easier to handle https://lea.verou.me/ with empty string and with to keep just the yyyy/mm/slug part of the URL Then added at the top and wrapped everything in a element to make it valid XML. This resulted in a series of elements that looked like this: 2023/04/private-fields-considered-harmful 3599 2023/04/private-fields-considered-harmful/image-27 3600 At this point, we have exuahsted the capabilities of find &amp; replace; its time for some JS! I opened the file in Chrome and ran: copy(Object.assign({}, ...[...document.querySelectorAll(&quot;item&quot;)] .filter(item =&gt; item.querySelector(&quot;post_type&quot;).textContent === &quot;post&quot;) .map(item =&gt; ({ [item.querySelector(&quot;link&quot;).textContent]: item.querySelector(&quot;post_id&quot;).textContent } )))); which copies JSON like this to the clipboard, ready to be pasted in a JSON file (I used wpids.json ): { ... &quot;2022/11/tag-2&quot;: &quot;3531&quot;, &quot;2023/03/contrast-ratio-new-home&quot;: &quot;3592&quot;, &quot;2023/04/private-fields-considered-harmful&quot;: &quot;3599&quot; } Some cleanup was still needed, but this was basically good to go. Adding the post id to the posts To inject a wpid property to each post, I added a blog.11tydata.js file with the following: module.exports = { eleventyComputed: { postUrlStem: data =&gt; { return data.page.filePathStem.replace(/^\/blog\/|\/index$/g, &quot;&quot;); }, wpid: data =&gt; { return data.wpids[data.postUrlStem]; } } }; Linking to Disqus We now have the post id, and we can use it in our template. Adapting the code from the Universal Embed Code , we get: {% if wpid %} /** * RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT * THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR * PLATFORM OR CMS. * * LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: * https://disqus.com/admin/universalcode/#configuration-variables */ var disqus_config = function () { // Replace PAGE_URL with your page&#39;s canonical URL variable this.page.url = &#39;https://lea.verou.me/{{ postUrlStem }}/&#39;; // Replace PAGE_IDENTIFIER with your page&#39;s unique identifier variable this.page.identifier = &quot;{{ wpid }} https:\/\/lea.verou.me\/?p={{ wpid }}&quot;; }; (function() { // REQUIRED CONFIGURATION VARIABLE: EDIT THE SHORTNAME BELOW var d = document, s = d.createElement(&#39;script&#39;); // IMPORTANT: Replace EXAMPLE with your forum shortname! s.src = &#39;https://leaverou.disqus.com/embed.js&#39;; s.setAttribute(&#39;data-timestamp&#39;, +new Date()); (d.head || d.body).appendChild(s); })(); {% endif %} Thats it! This now works and displays the Disqus threads correctly! Using Disqus on new posts as well Note that as it currently stands, this will not display the Disqus UI on new posts, since they wont have a wpid. Even if I switch to something else in the future, Disqus is better than nothing meanwhile (for me many people would disagree: switching to no comments at all seems very common when people switch to a SSG blog). So, new posts dont have a wpid, but they dont need one either. As long as we pass some kind of unique identifier to Disqus, we have a comment thread. The easiest way to do this is to use the posts path, e.g. 2023/preserve-disqus for this one, as this is guaranteed to be unique. We also want to be able to disable comments on a per-post basis, so we need a way to do that. So instead of dealing with wpid directly in templates, I added another computed property in blog.11tydata.js : disqus_id: data =&gt; { let wpid = data.wpid; if (wpid) { return `${ wpid } https:\/\/lea.verou.me\/?p=${ wpid }`; } else if (data.disqus !== false) { return typeof data.disqus !== &quot;string&quot;? data.postUrlStem : data.disqus; } } Note that this allows us to pass a custom identifier to Disqus by using a string, disable it by using false , or just get the automatic behavior by using true or not specifying it at all. The custom identifier can be useful if we want to change the URL of a post without losing the comments. Then, I updated the template to use disqus_id instead of wpid . Whats next? I dont know if I will continue using Disqus. Its convenient, but also heavyweight, and there are privacy concerns around it. However, Im not sure what I would use instead. Any third party SaaS service would have the same privacy issues. Not necessarily now, but quite likely in the future. Ive looked into Webmentions , but the end-user experience does not compare to a regular comment system, and it seems like quite a hassle to implement. Utterances is a really cool idea: it uses GitHub issues as a backend for a comment system. Having myself (ab)used the GitHub API as a storage backend many a times (even as early as 2012 ), I can see the appeal. This may be a viable path forwards, though I need to verify that GitHub Issues can be easily exported, so that Im not locked in. On a similar vein, I really loved Gisqus seems great too: Its like Utterances, but uses GitHub Discussions instead of Issues. What holds me back from switching to it is that Discussions cannot yet be exported, and I think portability is important here. People dont even really use comments much anymore, they post on social media instead. I would have loved some way to simply collect the discussions about the post from various social media and display them underneath, but with API prices getting out of control ( 1 2 ), that doesnt seem feasible. If there are any options I missed, please let me know in the (Disqus, for now ) comments!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>JS private class fields considered harmful</title>
  <link>https://lea.verou.me/2023/04/private-fields-considered-harmful/</link>
  <pubDate>Sun, 17 May 2026 03:25:15 +0200</pubDate>
  <description>Today I mourn. What am I mourning? Encapsulation. At least in my projects. As a library author, I’ve decided to avoid private class fields from now on and gradually refactor them out of my existing libraries . Why did I make such a drastic decision? It all started a few days ago, when I was building a Vue 3 app that used Color.js Color objects. For context, Vue 3 uses proxies to implement its reactivity system, just like Mavo did back in 2016 (the first one to do so as far as I’m aware). I was getting several errors and upon tracking them down I had a very sad realization: instances of classes that use private fields cannot be proxied . I will let that sink in for a bit. Private fields, proxies, pick one, you can’t have both. Here is a reduced testcase illustrating the problem. Basically, because a Proxy creates a different object, it breaks both strict equality ( obj1 === obj2 ), as well as private properties. MDN even has a whole section on this . Unfortunately, the workaround proposed is no help when proxies are used to implement reactivity, so when I tried to report this as a Vue bug , it was (rightly) closed as wontfix. It would not be possible to fix this in Mavo either, for the same reason. I joined TC39 fairly recently, so I was not aware of the background when proxies or private class fields were designed. Several fellow TC39 members filled me in on the discussions from back then. A lot of the background is in this super long thread , some interesting tl;drs as replies to my tweet: https://twitter.com/LeaVerou/status/1650562320702099457 After a lot of back and forth, I decided I cannot justify using private properties going forwards . The tradeoff is simply not worth it. There is no real workaround for proxy-ability, whereas for private fields there is always private-by-convention. Does it suck? Absolutely. However, a sucky workaround is better than a nonexistent workaround . Also, I control the internal implementation of my classes, whereas proxying happens by other parties. As a library user, it must be incredibly confusing to have to deal with errors about access to private fields in a class you did not write. This was one of the saddest PRs I have ever written https://github.com/LeaVerou/color.js/pull/306 . It feels like such a huge step backwards. I’ve waited years for private fields to be supported everywhere and relished when they got there. I was among the first library authors to adopt them in library code, before a lot of tooling even parsed them properly (and some still don’t ). Sure, they were kind of annoying to use (you usually want protected, i.e. visible to subclasses, not actually private), but they were better than nothing. I was not joking in the first paragraph; I am literally grieving. I may still use private fields on a case by case basis, where I cannot imagine objects being proxied being very useful, for example in web components. But from now on I will not reach to them without thought, like I have been for the past couple of years.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>JS private class fields considered harmful</title>
  <link>https://lea.verou.me/2023/04/private-fields-considered-harmful/</link>
  <pubDate>Sun, 17 May 2026 03:25:15 +0200</pubDate>
  <description>Today I mourn. What am I mourning? Encapsulation. At least in my projects. As a library author, Ive decided to avoid private class fields from now on and gradually refactor them out of my existing libraries . Why did I make such a drastic decision? It all started a few days ago, when I was building a Vue 3 app that used Color.js Color objects. For context, Vue 3 uses proxies to implement its reactivity system, just like Mavo did back in 2016 (the first one to do so as far as Im aware). I was getting several errors and upon tracking them down I had a very sad realization: instances of classes that use private fields cannot be proxied . I will let that sink in for a bit. Private fields, proxies, pick one, you cant have both. Here is a reduced testcase illustrating the problem. Basically, because a Proxy creates a different object, it breaks both strict equality ( obj1 === obj2 ), as well as private properties. MDN even has a whole section on this . Unfortunately, the workaround proposed is no help when proxies are used to implement reactivity, so when I tried to report this as a Vue bug , it was (rightly) closed as wontfix. It would not be possible to fix this in Mavo either, for the same reason. I joined TC39 fairly recently, so I was not aware of the background when proxies or private class fields were designed. Several fellow TC39 members filled me in on the discussions from back then. A lot of the background is in this super long thread , some interesting tl;drs as replies to my tweet: https://twitter.com/LeaVerou/status/1650562320702099457 After a lot of back and forth, I decided I cannot justify using private properties going forwards . The tradeoff is simply not worth it. There is no real workaround for proxy-ability, whereas for private fields there is always private-by-convention. Does it suck? Absolutely. However, a sucky workaround is better than a nonexistent workaround . Also, I control the internal implementation of my classes, whereas proxying happens by other parties. As a library user, it must be incredibly confusing to have to deal with errors about access to private fields in a class you did not write. This was one of the saddest PRs I have ever written https://github.com/LeaVerou/color.js/pull/306 . It feels like such a huge step backwards. Ive waited years for private fields to be supported everywhere and relished when they got there. I was among the first library authors to adopt them in library code, before a lot of tooling even parsed them properly (and some still dont ). Sure, they were kind of annoying to use (you usually want protected, i.e. visible to subclasses, not actually private), but they were better than nothing. I was not joking in the first paragraph; I am literally grieving. I may still use private fields on a case by case basis, where I cannot imagine objects being proxied being very useful, for example in web components. But from now on I will not reach to them without thought, like I have been for the past couple of years.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Contrast Ratio has a new home — and this is great news!</title>
  <link>https://lea.verou.me/2023/03/contrast-ratio-new-home/</link>
  <pubDate>Sun, 17 May 2026 03:25:14 +0200</pubDate>
  <description>It has been over a decade when I launched contrast-ratio.com , an app to calculate the WCAG 2.1 contrast ratio between any two CSS colors. At the time, all similar tools suffered from several flaws when being used for CSS editing: No support for semi-transparent colors (Since WCAG included no guidance for alpha transparency — I had to do original research to calculate the contrast ratio range for that case) No support for color formats other than hex or (at best) RGB with sliders. I wanted something where I could just paste a CSS color just like I had it specified in my code (e.g. hsl(220 10% 90% ), possibly tweak it a bit to pass, then paste it back. I didn’t want to use unintuitive hex colors, and I didn’t want to fiddle with sliders. Poor UX, often calculating the actual ratio required further user actions, making iteration tedious Over the years, contrast-ratio.com grew in popularity: it was recommended in several books , talks, and workshops. It basically became the standard URL developers would visit for this purpose. However, I’ve been too busy to work on it further beyond just merging pull requests. My time is currently split between the dozens of open source projects I have started and maintain, my TAG work , my CSS WG work , and my teaching &amp; research at MIT. Therefore, when Ross and Drew from Siege Media approached me with a generous offer to buy the domain, and a commitment to take over maintainship of the open source project , I was cautiously optimistic. But now, after having seen some of their plans for it, I could not be more certain that the future of this tool is much brighter with them. Please join me in welcoming them to the project and help them get settled in as new stewards! ETA: Siege Media Press Release</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Position Statement for the 2022 W3C TAG Election</title>
  <link>https://lea.verou.me/2022/11/tag-2/</link>
  <pubDate>Sun, 17 May 2026 03:25:14 +0200</pubDate>
  <description>Update: I got re-elected !! Thank you for trusting me once more with this huge responsibility towards the Open Web. I will continue to do my best to justify the confidence the W3C membership has placed in me. 🥹 Context: In 2020 , I ran for the TAG election for the first time and had the great honor of being elected by the W3C membership. This year, I’m running for re-election. The W3C Technical Architecture Group (TAG) is the Working Group that ensures that Web Platform technologies are usable and follow consistent design principles, whether they are created inside or outside W3C. It advocates for the needs of everyone who uses the Web and everyone who works on the Web. If you work for a company that is a W3C Member , please consider encouraging your AC rep to vote for me! My candidate statement follows. I’m Lea , and I’m running for re-election to the TAG to continue applying my usability research, CSS WG, and TAG experience to help W3C stay connected to the developer community, and to better serve their needs by ensuring web platform features are not only powerful, but also learnable and approachable, with a smooth ease-of-use to complexity curve. I wear many hats. My background spans almost two decades of web design &amp; development experience, one decade of standards work in the CSS WG, nearly a decade of PhD level human-computer interaction research &amp; teaching at MIT , and over a decade of educating web developers through talks , books, articles , and helping them through my dozens of open source projects , some of which are used on millions of websites . For those unfamiliar with my background, I encourage taking a look at my 2020 candidate statement . In 2020, I had the great honor of being elected to serve on the TAG by the W3C membership. In the two years I have served on the TAG, I participated in over 70 design reviews and helped prioritize API design in our reviewing. I have been publicly praised for the quality of design reviews I led. It is important that the TAG does not operate in a vacuum: The primary purpose of our work is to serve developers and end-users by ensuring web platform features are usable, secure and privacy preserving. I have used my experience during design reviews to make sure we remain connected to this mission. Together with Sangwhan Moon, I took the lead on our Web Platform Design Principles effort, which documents the principles that underlie Web Platform features — previously only existing in WG lore. The Web Platform is going through an explosion of new features; only in the last year the TAG received almost a hundred design review requests. With this volume, it is important that reviews are consistent, transparent, and fast. Evolving our published design principles helps with all three goals. The Web ecosystem is not just the Web Platform itself, but also the various tools and libraries out there. I started a project to publish a subset of the design principles that apply to web developers, to help them in creating Web Platform compatible APIs. After all, with web components, web developers are now HTML designers, with Houdini APIs, they are now CSS designers, and with JS, they’ve been JS API designers since forever. The project is currently in its infancy, and If elected, it will be one of my tasks to get it published within my next term. As a Greek woman, I bring both a Mediterranean and European perspective that diversifies the TAG and as a fully bilingual Greek and English speaker, I can fully participate in rapid technical discussions while also having an appreciation of the Internationalization needs of those who use the Web in languages other than English. To ensure my participation has been beneficial for the TAG, I reached out to the chairs for feedback before deciding to run again. Both were very positive and strongly encouraged me to run again. As someone not employed at a big tech company, I am not influenced by any particular company line. My only agenda is to lead the Web Platform to its full potential, and if re-elected, I’m willing to commit to spending the requisite hundreds of hours working towards that goal over the next two years. This was just the beginning, there is so much more important work to be done! I would like to thank Open JS Foundation for graciously funding my TAG-related travel, in the event that I am re-elected, and both OpenJS Foundation and Bocoup for funding it during my first term.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Contrast Ratio has a new home and this is great news!</title>
  <link>https://lea.verou.me/2023/03/contrast-ratio-new-home/</link>
  <pubDate>Sun, 17 May 2026 03:25:14 +0200</pubDate>
  <description>It has been over a decade when I launched contrast-ratio.com , an app to calculate the WCAG 2.1 contrast ratio between any two CSS colors. At the time, all similar tools suffered from several flaws when being used for CSS editing: No support for semi-transparent colors (Since WCAG included no guidance for alpha transparency I had to do original research to calculate the contrast ratio range for that case) No support for color formats other than hex or (at best) RGB with sliders. I wanted something where I could just paste a CSS color just like I had it specified in my code (e.g. hsl(220 10% 90% ), possibly tweak it a bit to pass, then paste it back. I didnt want to use unintuitive hex colors, and I didnt want to fiddle with sliders. Poor UX, often calculating the actual ratio required further user actions, making iteration tedious Over the years, contrast-ratio.com grew in popularity: it was recommended in several books , talks, and workshops. It basically became the standard URL developers would visit for this purpose. However, Ive been too busy to work on it further beyond just merging pull requests. My time is currently split between the dozens of open source projects I have started and maintain, my TAG work , my CSS WG work , and my teaching &amp; research at MIT. Therefore, when Ross and Drew from Siege Media approached me with a generous offer to buy the domain, and a commitment to take over maintainship of the open source project , I was cautiously optimistic. But now, after having seen some of their plans for it, I could not be more certain that the future of this tool is much brighter with them. Please join me in welcoming them to the project and help them get settled in as new stewards! ETA: Siege Media Press Release</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Position Statement for the 2022 W3C TAG Election</title>
  <link>https://lea.verou.me/2022/11/tag-2/</link>
  <pubDate>Sun, 17 May 2026 03:25:14 +0200</pubDate>
  <description>Update: I got re-elected !! Thank you for trusting me once more with this huge responsibility towards the Open Web. I will continue to do my best to justify the confidence the W3C membership has placed in me. Context: In 2020 , I ran for the TAG election for the first time and had the great honor of being elected by the W3C membership. This year, Im running for re-election. The W3C Technical Architecture Group (TAG) is the Working Group that ensures that Web Platform technologies are usable and follow consistent design principles, whether they are created inside or outside W3C. It advocates for the needs of everyone who uses the Web and everyone who works on the Web. If you work for a company that is a W3C Member , please consider encouraging your AC rep to vote for me! My candidate statement follows. Im Lea , and Im running for re-election to the TAG to continue applying my usability research, CSS WG, and TAG experience to help W3C stay connected to the developer community, and to better serve their needs by ensuring web platform features are not only powerful, but also learnable and approachable, with a smooth ease-of-use to complexity curve. I wear many hats. My background spans almost two decades of web design &amp; development experience, one decade of standards work in the CSS WG, nearly a decade of PhD level human-computer interaction research &amp; teaching at MIT , and over a decade of educating web developers through talks , books, articles , and helping them through my dozens of open source projects , some of which are used on millions of websites . For those unfamiliar with my background, I encourage taking a look at my 2020 candidate statement . In 2020, I had the great honor of being elected to serve on the TAG by the W3C membership. In the two years I have served on the TAG, I participated in over 70 design reviews and helped prioritize API design in our reviewing. I have been publicly praised for the quality of design reviews I led. It is important that the TAG does not operate in a vacuum: The primary purpose of our work is to serve developers and end-users by ensuring web platform features are usable, secure and privacy preserving. I have used my experience during design reviews to make sure we remain connected to this mission. Together with Sangwhan Moon, I took the lead on our Web Platform Design Principles effort, which documents the principles that underlie Web Platform features previously only existing in WG lore. The Web Platform is going through an explosion of new features; only in the last year the TAG received almost a hundred design review requests. With this volume, it is important that reviews are consistent, transparent, and fast. Evolving our published design principles helps with all three goals. The Web ecosystem is not just the Web Platform itself, but also the various tools and libraries out there. I started a project to publish a subset of the design principles that apply to web developers, to help them in creating Web Platform compatible APIs. After all, with web components, web developers are now HTML designers, with Houdini APIs, they are now CSS designers, and with JS, theyve been JS API designers since forever. The project is currently in its infancy, and If elected, it will be one of my tasks to get it published within my next term. As a Greek woman, I bring both a Mediterranean and European perspective that diversifies the TAG and as a fully bilingual Greek and English speaker, I can fully participate in rapid technical discussions while also having an appreciation of the Internationalization needs of those who use the Web in languages other than English. To ensure my participation has been beneficial for the TAG, I reached out to the chairs for feedback before deciding to run again. Both were very positive and strongly encouraged me to run again. As someone not employed at a big tech company, I am not influenced by any particular company line. My only agenda is to lead the Web Platform to its full potential, and if re-elected, Im willing to commit to spending the requisite hundreds of hours working towards that goal over the next two years. This was just the beginning, there is so much more important work to be done! I would like to thank Open JS Foundation for graciously funding my TAG-related travel, in the event that I am re-elected, and both OpenJS Foundation and Bocoup for funding it during my first term.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>State of CSS 2022 now open!</title>
  <link>https://lea.verou.me/2022/10/state-of-css-2022-now-open/</link>
  <pubDate>Sun, 17 May 2026 03:25:13 +0200</pubDate>
  <description>Take State of CSS 2022 survey A while ago I posted a call for feedback to inform the design of the State of CSS 2022 survey . The response has been overwhelming and it was glorious. We got quite a lot of proposals , feedback , votes . But that also meant we had to make some tough decisions about what gets in the survey and what doesn’t, otherwise we’d end up with a survey so long nobody would want to finish it! In the end we added questions about 15 new CSS features based on proposals in that repo, and decided against adding 9 . Overall, there are 30 new CSS features the 2022 survey asks about. To make space for all of that, we also removed a few that were not really shining much light into what developers do anymore, and also a couple others that were not actually about CSS . However, CSS features are not the only — or even the most important questions being asked. Last year, some of the freeform questions about pain points were particularly useful to browser vendors for prioritizing implementation and standards work, and we expect this to be true this year as well. We put considerable effort into redesigning these freeform questions to make them more intuitive , while maintaining their helpfulness for browser vendors: We hope the new wording makes it more clear that these are mutually exclusive, so that respondents do not feel they need to duplicate their answers. One of the new questions I’m excited about is this question to gauge whether the respondent spends more time writing JS or CSS: A focus of this year’s State of CSS survey is to reach a broader range of developers; a majority of respondents of past surveys has been JS developers who also write CSS, rather than developers that focus on CSS equally or even primarily. This is a natural consequence of this having been spun off the State of JS survey . To truly see what the State of CSS is in 2022, we need input from all types of developers, as developers with different focus have different needs and priorities. This question will allow us to evaluate how well we have reached this goal, and going forward, whether we are improving every year. Another thing I’m excited about in this year’s survey is the ability to add freeform comments to any question. Adding freeform comments to a question It’s often hard to tell what the background is behind each of the three answers: are people not using a given feature due to poor browser support, poor ergonomics, or some other reason? When people do use a feature, was their experience good or bad? Would they use it again? We went back and forth many times about having a more structured followup question there, but in the end settled on a simple freeform field for this first iteration. Maybe next year it will be more structured, depending on how people use it this year. So, without further ado, the survey is finally open for responses: Take State of CSS 2022 survey This survey is not just for fun: the results actually inform what browsers prioritize for implementation . So by spending a few minutes on a thoughtful and comprehensive response, you can actually make both your and other developers’ lives better! What are you waiting for?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>State of CSS 2022 now open!</title>
  <link>https://lea.verou.me/2022/10/state-of-css-2022-now-open/</link>
  <pubDate>Sun, 17 May 2026 03:25:13 +0200</pubDate>
  <description>Take State of CSS 2022 survey A while ago I posted a call for feedback to inform the design of the State of CSS 2022 survey . The response has been overwhelming and it was glorious. We got quite a lot of proposals , feedback , votes . But that also meant we had to make some tough decisions about what gets in the survey and what doesnt, otherwise wed end up with a survey so long nobody would want to finish it! In the end we added questions about 15 new CSS features based on proposals in that repo, and decided against adding 9 . Overall, there are 30 new CSS features the 2022 survey asks about. To make space for all of that, we also removed a few that were not really shining much light into what developers do anymore, and also a couple others that were not actually about CSS . However, CSS features are not the only or even the most important questions being asked. Last year, some of the freeform questions about pain points were particularly useful to browser vendors for prioritizing implementation and standards work, and we expect this to be true this year as well. We put considerable effort into redesigning these freeform questions to make them more intuitive , while maintaining their helpfulness for browser vendors: We hope the new wording makes it more clear that these are mutually exclusive, so that respondents do not feel they need to duplicate their answers. One of the new questions Im excited about is this question to gauge whether the respondent spends more time writing JS or CSS: A focus of this years State of CSS survey is to reach a broader range of developers; a majority of respondents of past surveys has been JS developers who also write CSS, rather than developers that focus on CSS equally or even primarily. This is a natural consequence of this having been spun off the State of JS survey . To truly see what the State of CSS is in 2022, we need input from all types of developers, as developers with different focus have different needs and priorities. This question will allow us to evaluate how well we have reached this goal, and going forward, whether we are improving every year. Another thing Im excited about in this years survey is the ability to add freeform comments to any question. Adding freeform comments to a question Its often hard to tell what the background is behind each of the three answers: are people not using a given feature due to poor browser support, poor ergonomics, or some other reason? When people do use a feature, was their experience good or bad? Would they use it again? We went back and forth many times about having a more structured followup question there, but in the end settled on a simple freeform field for this first iteration. Maybe next year it will be more structured, depending on how people use it this year. So, without further ado, the survey is finally open for responses: Take State of CSS 2022 survey This survey is not just for fun: the results actually inform what browsers prioritize for implementation . So by spending a few minutes on a thoughtful and comprehensive response, you can actually make both your and other developers lives better! What are you waiting for?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On ratings and meters</title>
  <link>https://lea.verou.me/2022/08/on-ratings-and-meters/</link>
  <pubDate>Sun, 17 May 2026 03:25:12 +0200</pubDate>
  <description>I always thought that the semantically appropriate way to represent a rating (e.g. a star rating) is a element. They essentially convey the same type of information, the star rating is just a different presentation. An example of a star rating widget, from Amazon However, trying to style a element to look like a star rating is …tricky at best . Not to mention that this approach won’t even work in Shadow trees (unless you include the CSS in every single shadow tree). So, I set out to create a proper web component for star ratings. The first conundrum was, how does this relate to a element? Option 1: Should it extend using builtin extends ? Option 2: Should it use a web component with a in Shadow DOM? Option 3: Should it be an entirely separate web component that just uses a meter ARIA Role and related ARIA attributes? This is what the code would look like: Safari has all but killed built-in extends , but there is a very small polyfill , so I didn’t mind too much. I first decided to go with that, but it turns out you can’t even mess with the Shadow DOM of the element you’re extending. You have no access to the existing Shadow DOM of the element, because it’s closed, and you cannot attach a new one. So there’s no way to add encapsulated styles, which was a strong requirement of my use case. I did some work on Option 2, but I quickly discovered that having an internal that everything goes through was not worth it, and it was far easier to implement it myself, with appropriate implicit ARIA through ElementInternals . The next dilemma was even more of a conundrum: A is not editable by default, but for a rating widget, you need it to be editable at least sometimes (e.g. see Shoelace Rating for an example). There is no established convention in HTML for elements that are readonly by default, and editable only some of the time. All editable elements we have are basically form controls that can lose editability through the readonly attribute. For anything else, I suppose there is contentEditable but there is no way for web components to hook into it and expose custom editing UI that overrides the one generated by the browser. In the end what I ended up doing was creating two components: A component that is a discrete version of An component that inherits from but is editable (unless readonly is specified) I’m still unsure if this is the right way. There were a couple issues with it. The first problem was related to encapsulation . I like to use a private #internals property for an element’s ElementInternals instance. However, needed to modify the internals of its parent, to add form association stuff , so I could not use a private property anymore, and you cannot attach a separate ElementInternals object. I ended up going for a Symbol property that the parent exports , but it still doesn’t feel like a great solution as it breaks encapsulation. Ideally JS needs protected class fields , but it doesn’t look like that’s happening anytime soon . The other problem was related to semantics . Is it still semantically a when it’s editable, or does it then become closer to a slider that you set by hovering instead of dragging? I decided to ignore that thought for now, but it does make me a little uneasy. Anyhow, you can find my experiments at nudeui.com : All NudeUI components are very much works in progress and mainly my personal experiments, but if you feel like it, please report issues in the repo . I can’t promise I’ll get to them though!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On ratings and meters</title>
  <link>https://lea.verou.me/2022/08/on-ratings-and-meters/</link>
  <pubDate>Sun, 17 May 2026 03:25:12 +0200</pubDate>
  <description>I always thought that the semantically appropriate way to represent a rating (e.g. a star rating) is a element. They essentially convey the same type of information, the star rating is just a different presentation. An example of a star rating widget, from Amazon However, trying to style a element to look like a star rating is tricky at best . Not to mention that this approach wont even work in Shadow trees (unless you include the CSS in every single shadow tree). So, I set out to create a proper web component for star ratings. The first conundrum was, how does this relate to a element? Option 1: Should it extend using builtin extends ? Option 2: Should it use a web component with a in Shadow DOM? Option 3: Should it be an entirely separate web component that just uses a meter ARIA Role and related ARIA attributes? This is what the code would look like: Safari has all but killed built-in extends , but there is a very small polyfill , so I didnt mind too much. I first decided to go with that, but it turns out you cant even mess with the Shadow DOM of the element youre extending. You have no access to the existing Shadow DOM of the element, because its closed, and you cannot attach a new one. So theres no way to add encapsulated styles, which was a strong requirement of my use case. I did some work on Option 2, but I quickly discovered that having an internal that everything goes through was not worth it, and it was far easier to implement it myself, with appropriate implicit ARIA through ElementInternals . The next dilemma was even more of a conundrum: A is not editable by default, but for a rating widget, you need it to be editable at least sometimes (e.g. see Shoelace Rating for an example). There is no established convention in HTML for elements that are readonly by default, and editable only some of the time. All editable elements we have are basically form controls that can lose editability through the readonly attribute. For anything else, I suppose there is contentEditable but there is no way for web components to hook into it and expose custom editing UI that overrides the one generated by the browser. In the end what I ended up doing was creating two components: A component that is a discrete version of An component that inherits from but is editable (unless readonly is specified) Im still unsure if this is the right way. There were a couple issues with it. The first problem was related to encapsulation . I like to use a private #internals property for an elements ElementInternals instance. However, needed to modify the internals of its parent, to add form association stuff , so I could not use a private property anymore, and you cannot attach a separate ElementInternals object. I ended up going for a Symbol property that the parent exports , but it still doesnt feel like a great solution as it breaks encapsulation. Ideally JS needs protected class fields , but it doesnt look like thats happening anytime soon . The other problem was related to semantics . Is it still semantically a when its editable, or does it then become closer to a slider that you set by hovering instead of dragging? I decided to ignore that thought for now, but it does make me a little uneasy. Anyhow, you can find my experiments at nudeui.com : All NudeUI components are very much works in progress and mainly my personal experiments, but if you feel like it, please report issues in the repo . I cant promise Ill get to them though!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Help design the State of CSS Survey 2022!</title>
  <link>https://lea.verou.me/2022/07/help-design-the-state-of-css-survey-2022/</link>
  <pubDate>Sun, 17 May 2026 03:25:11 +0200</pubDate>
  <description>Since 2019, the annual State of CSS survey has collected feedback from web developers from across the world to try and take the pulse of the CSS ecosystem, and it’s become a valuable resource not only for CSS developers, but also for browser vendors. This summer, one of my side projects is helping out with survey design and outreach for the State of CSS survey , thanks to a generous Google UI fund grant. The target is for the survey to launch in mid September, and we are currently working on the outline. So far we have created a preliminary outline based on last year’s survey and early research. All our work happens is in the open, in this repo . Here are some of the changes from last year’s survey : Removed the Pre-processors category as it feels like there isn’t too much debate around that area. Got rid of “which browser do you primarily develop in?” question as we already ask which browsers people test in. Merged “Opinions” and “Environments” sections into new “Usage” section. Moved browsers question to “Other Tools”. New features: currentcolor color-mix() Wide gamut colors scroll-behavior scroll-padding font-palette :focus-visible :has() pseudo-class :where() pseudo-class Cascade Layers Houdini Paint API and there are several others we are considering We are currently looking for feedback from the community, including suggesting CSS features to ask about, libraries and tools, or even new questions altogether. There are also some design issues to flesh out, you’re welcome to weigh in there too. If you want to quickly vote on which features are most important for you to make it into the survey, you can do that either via GitHub 👍🏼reactions, or here (which uses GitHub reactions behind the scenes). Do note that reactions are only one metric among many we will use to consider items. The feedback period will be open until August 20 , then we will start working on launching the survey. Do note that browser makers are looking at this and similar surveys to prioritize what to implement. This is why Google is sponsoring this project. So any effort you put into survey outline feedback, and on responding to the survey when it’s ready, could come back to you tenfold when your favorite CSS features get implemented faster!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Help design the State of CSS Survey 2022!</title>
  <link>https://lea.verou.me/2022/07/help-design-the-state-of-css-survey-2022/</link>
  <pubDate>Sun, 17 May 2026 03:25:11 +0200</pubDate>
  <description>Since 2019, the annual State of CSS survey has collected feedback from web developers from across the world to try and take the pulse of the CSS ecosystem, and its become a valuable resource not only for CSS developers, but also for browser vendors. This summer, one of my side projects is helping out with survey design and outreach for the State of CSS survey , thanks to a generous Google UI fund grant. The target is for the survey to launch in mid September, and we are currently working on the outline. So far we have created a preliminary outline based on last years survey and early research. All our work happens is in the open, in this repo . Here are some of the changes from last years survey : Removed the Pre-processors category as it feels like there isnt too much debate around that area. Got rid of which browser do you primarily develop in? question as we already ask which browsers people test in. Merged Opinions and Environments sections into new Usage section. Moved browsers question to Other Tools. New features: currentcolor color-mix() Wide gamut colors scroll-behavior scroll-padding font-palette :focus-visible :has() pseudo-class :where() pseudo-class Cascade Layers Houdini Paint API and there are several others we are considering We are currently looking for feedback from the community, including suggesting CSS features to ask about, libraries and tools, or even new questions altogether. There are also some design issues to flesh out, youre welcome to weigh in there too. If you want to quickly vote on which features are most important for you to make it into the survey, you can do that either via GitHub reactions, or here (which uses GitHub reactions behind the scenes). Do note that reactions are only one metric among many we will use to consider items. The feedback period will be open until August 20 , then we will start working on launching the survey. Do note that browser makers are looking at this and similar surveys to prioritize what to implement. This is why Google is sponsoring this project. So any effort you put into survey outline feedback, and on responding to the survey when its ready, could come back to you tenfold when your favorite CSS features get implemented faster!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>What is the best way to mark up an exclusive button group?</title>
  <link>https://lea.verou.me/2022/07/button-group/</link>
  <pubDate>Sun, 17 May 2026 03:25:10 +0200</pubDate>
  <description>A few days ago I asked Twitter a seemingly simple question (I meant aria-pressed , not aria-selected but Twitter doesn’t allow edits…) : https://twitter.com/LeaVerou/status/1545712667515654144 For background, I was implementing a web component for an app I’m working on at work and I was getting into some pretty weird rabbit holes with my approach of generating radios and labels. Unsurprisingly, most people thought the best solution is radio buttons and labels . After all, it works without CSS, right? Progressive enhancement and everything? That’s what I thought too. I had contorted my component to generate labels and radios in the Shadow DOM from buttons in the light DOM, which resulted in awkward code and awkward CSS, but I felt I was fighting the good fight and doing the best thing for accessibility. All this was challenged when the actual accessibility expert, Léonie Watson chimed in. For those of you who don’t know her, she is pretty much the expert when it comes to web accessibility and standards. She is also visually impaired herself, giving her a firsthand experience many other a11y aficionados lack. Her recommendation was contrary to what most others were saying: https://twitter.com/LeonieWatson/status/1545745436740313089 She went on to make the point that if a design looks like buttons, it should act like buttons, otherwise there are mismatched expectations and poor UX for AT users: https://twitter.com/LeonieWatson/status/1545762058339319808 https://twitter.com/LeonieWatson/status/1545757022645325824 In case you were wondering if state would be equally noticeable with aria-pressed and buttons, it is: https://twitter.com/LeonieWatson/status/1545763493412052992 And some advice on grouping: https://twitter.com/LeonieWatson/status/1545745923011117057 In theory doing this in Shadow DOM and/or using ElementInternals implicit roles should be fine, though in practice we’ve had some trouble with that . Today I posted my attempt to implement what we’ve discussed in a component , which restarted the discussion. Its implementation is right here if you want to improve it further! And make sure to check out the actual Twitter thread , as there is a lot of good stuff I couldn’t include in this! Edit: Léonie wrote a blog post too, Perceived affordances and the functionality mismatch . It’s a great read.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>What is the best way to mark up an exclusive button group?</title>
  <link>https://lea.verou.me/2022/07/button-group/</link>
  <pubDate>Sun, 17 May 2026 03:25:10 +0200</pubDate>
  <description>A few days ago I asked Twitter a seemingly simple question (I meant aria-pressed , not aria-selected but Twitter doesnt allow edits) : https://twitter.com/LeaVerou/status/1545712667515654144 For background, I was implementing a web component for an app Im working on at work and I was getting into some pretty weird rabbit holes with my approach of generating radios and labels. Unsurprisingly, most people thought the best solution is radio buttons and labels . After all, it works without CSS, right? Progressive enhancement and everything? Thats what I thought too. I had contorted my component to generate labels and radios in the Shadow DOM from buttons in the light DOM, which resulted in awkward code and awkward CSS, but I felt I was fighting the good fight and doing the best thing for accessibility. All this was challenged when the actual accessibility expert, Léonie Watson chimed in. For those of you who dont know her, she is pretty much the expert when it comes to web accessibility and standards. She is also visually impaired herself, giving her a firsthand experience many other a11y aficionados lack. Her recommendation was contrary to what most others were saying: https://twitter.com/LeonieWatson/status/1545745436740313089 She went on to make the point that if a design looks like buttons, it should act like buttons, otherwise there are mismatched expectations and poor UX for AT users: https://twitter.com/LeonieWatson/status/1545762058339319808 https://twitter.com/LeonieWatson/status/1545757022645325824 In case you were wondering if state would be equally noticeable with aria-pressed and buttons, it is: https://twitter.com/LeonieWatson/status/1545763493412052992 And some advice on grouping: https://twitter.com/LeonieWatson/status/1545745923011117057 In theory doing this in Shadow DOM and/or using ElementInternals implicit roles should be fine, though in practice weve had some trouble with that . Today I posted my attempt to implement what weve discussed in a component , which restarted the discussion. Its implementation is right here if you want to improve it further! And make sure to check out the actual Twitter thread , as there is a lot of good stuff I couldnt include in this! Edit: Léonie wrote a blog post too, Perceived affordances and the functionality mismatch . Its a great read.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Rety: live coding, without the stress</title>
  <link>https://lea.verou.me/2022/07/rety/</link>
  <pubDate>Sun, 17 May 2026 03:25:09 +0200</pubDate>
  <description>I recently spoke at CSS Day in Amsterdam. It was only my second f2f talk after the pandemic. It went down really well, both in person, and recently that the video was released: https://www.youtube.com/watch?v=ZuZizqDF4q8 Here is a sample of tweets about it that made me particularly warm and fuzzy inside: https://twitter.com/CSSDayConf/status/1542778793219301376 https://twitter.com/jonpearse/status/1542490268322103296 https://twitter.com/StuRobson/status/1542461048791384066 https://twitter.com/vlh/status/1544483583544463361 https://twitter.com/pawelgrzybek/status/1546861605824126980 https://twitter.com/parker\_codes/status/1547055116221497344 https://twitter.com/polarbirke/status/1547202631315214338 https://twitter.com/unistyler/status/1544619175796252672 There’s a lot more where these came from too . This was not just my second post-pandemic talk, but my first talk using Rety , which is what this post is about. As you may know, I love live coding as a teaching tool, and over the years it has become part of my trademark speaking style . When combined with some kind of interactive preview, it allows the speaker to demonstrate not only the final state of a coding snippet, but also how you get there, and what the intermediate results are. Live coding is to programming what a blackboard is to math or physics. But it does create a unique challenge: My live coded slides don’t make sense without me. This may be acceptable for a conference talk, which is usually recorded, but not in other contexts, such as teaching a university course, where all instructors need to be able to teach all lectures, and students need to be able to quickly refer to examples shown. Back in the fall of 2021, when we were preparing for the second iteration of our course, Design for the Web: Languages and User Interfaces , this came up as a pressing issue. The current state of the course required me to be there to teach my lectures, and this may well be the last year I teach it, since I’m finishing up my PhD soon. I didn’t want to completely remove live coding from my slides, as I truly believe it is the perfect implementation of the “show, don’t tell” teaching adage for certain things, so I thought instead: what if I could record my live coding, and make it replayable? Doing so manually seemed like cruel and unusual punishment. And thus, Rety was born (pronounced like the “rety” in “retype”). While originally the plan was for me to still live code, and have the Rety functionality there for students and future instructors, I ended up using it during my own lectures as well, as I concluded that a well crafted Rety script was strictly superior to me doing the live coding: Same progressive development as a live demo It still affords unplanned demonstrations (e.g. to answer a question), since Rety still works with the same editors, and I could always pause it and take over if needed. I could record myself and edit the script to maximize education value and minimize typos, delays, fumbling etc. People can consume typed text far faster than people can type text. This is why most video tutorials speed up the typing. With Rety, typing speed is adjustable, and doesn’t need to match mine. After test driving it for our course the entire spring 2022 semester, it went through the ultimate test in June 2022: I used it for my CSSDay conference talk. You can watch the talk here (first live demo at 7:15). Right now Rety is just a set of two classes: Recorder and Replayer , which are used entirely independently. The exact UI is left up to the Rety user. E.g. to use it in my slides, I integrated it with the Live Demo plugin of Inspire.js (it is automatically included if a is found in a live demo slide). The library could use more docs and some tests and I have doubts about the API, but I figured I should release it it earlier rather than later (it’s already been sitting in a repo for 7 months). After all, what best time to release it than when the first Rety talk is still making the rounds ? My vision is to ultimately evolve and standardize the Rety script format, so that it can be used to describe a coding interaction across a variety of tools. There are so many possibilities! Wouldn’t it be cool if CodePen and similar playgrounds supported embedding a Rety script into a pen? What if you could store Rety scripts in a repo and editors like VS Code recognized them and let you replay them? Enjoy: Rety</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Rety: live coding, without the stress</title>
  <link>https://lea.verou.me/2022/07/rety/</link>
  <pubDate>Sun, 17 May 2026 03:25:09 +0200</pubDate>
  <description>I recently spoke at CSS Day in Amsterdam. It was only my second f2f talk after the pandemic. It went down really well, both in person, and recently that the video was released: https://www.youtube.com/watch?v=ZuZizqDF4q8 Here is a sample of tweets about it that made me particularly warm and fuzzy inside: https://twitter.com/CSSDayConf/status/1542778793219301376 https://twitter.com/jonpearse/status/1542490268322103296 https://twitter.com/StuRobson/status/1542461048791384066 https://twitter.com/vlh/status/1544483583544463361 https://twitter.com/pawelgrzybek/status/1546861605824126980 https://twitter.com/parker\_codes/status/1547055116221497344 https://twitter.com/polarbirke/status/1547202631315214338 https://twitter.com/unistyler/status/1544619175796252672 Theres a lot more where these came from too . This was not just my second post-pandemic talk, but my first talk using Rety , which is what this post is about. As you may know, I love live coding as a teaching tool, and over the years it has become part of my trademark speaking style . When combined with some kind of interactive preview, it allows the speaker to demonstrate not only the final state of a coding snippet, but also how you get there, and what the intermediate results are. Live coding is to programming what a blackboard is to math or physics. But it does create a unique challenge: My live coded slides dont make sense without me. This may be acceptable for a conference talk, which is usually recorded, but not in other contexts, such as teaching a university course, where all instructors need to be able to teach all lectures, and students need to be able to quickly refer to examples shown. Back in the fall of 2021, when we were preparing for the second iteration of our course, Design for the Web: Languages and User Interfaces , this came up as a pressing issue. The current state of the course required me to be there to teach my lectures, and this may well be the last year I teach it, since Im finishing up my PhD soon. I didnt want to completely remove live coding from my slides, as I truly believe it is the perfect implementation of the show, dont tell teaching adage for certain things, so I thought instead: what if I could record my live coding, and make it replayable? Doing so manually seemed like cruel and unusual punishment. And thus, Rety was born (pronounced like the rety in retype). While originally the plan was for me to still live code, and have the Rety functionality there for students and future instructors, I ended up using it during my own lectures as well, as I concluded that a well crafted Rety script was strictly superior to me doing the live coding: Same progressive development as a live demo It still affords unplanned demonstrations (e.g. to answer a question), since Rety still works with the same editors, and I could always pause it and take over if needed. I could record myself and edit the script to maximize education value and minimize typos, delays, fumbling etc. People can consume typed text far faster than people can type text. This is why most video tutorials speed up the typing. With Rety, typing speed is adjustable, and doesnt need to match mine. After test driving it for our course the entire spring 2022 semester, it went through the ultimate test in June 2022: I used it for my CSSDay conference talk. You can watch the talk here (first live demo at 7:15). Right now Rety is just a set of two classes: Recorder and Replayer , which are used entirely independently. The exact UI is left up to the Rety user. E.g. to use it in my slides, I integrated it with the Live Demo plugin of Inspire.js (it is automatically included if a is found in a live demo slide). The library could use more docs and some tests and I have doubts about the API, but I figured I should release it it earlier rather than later (its already been sitting in a repo for 7 months). After all, what best time to release it than when the first Rety talk is still making the rounds ? My vision is to ultimately evolve and standardize the Rety script format, so that it can be used to describe a coding interaction across a variety of tools. There are so many possibilities! Wouldnt it be cool if CodePen and similar playgrounds supported embedding a Rety script into a pen? What if you could store Rety scripts in a repo and editors like VS Code recognized them and let you replay them? Enjoy: Rety</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Releasing Color.js: A library that takes color seriously</title>
  <link>https://lea.verou.me/2022/06/releasing-colorjs/</link>
  <pubDate>Sun, 17 May 2026 03:25:08 +0200</pubDate>
  <description>Related: Chris’ blog post for the release of Color.js This post has been long overdue: Chris and I started working on Color.js in 2020, over 2 years ago! It was shortly after I had finished the Color lecture for the class I was teaching at MIT and I was appalled by the lack of color libraries that did the things I needed for the demos in my slides. I asked Chris, “Hey, what if we make a Color library? You will bring your Color Science knowledge and I will bring my JS and API design knowledge. Wouldn’t this be the coolest color library ever?”. There was also a fair bit of discussion in the CSS WG about a native Color object for the Web Platform, and we needed to play around with JS for a while before we could work on an API that would be baked into browsers. We had a prototype ready in a few months and presented it to the CSS WG. People loved it and some started using it despite it not being “officially” released. There was even a library that used Color.js as a dependency! Once we got some experience from this usage, we worked on a draft specification for a Color API for the Web. In July 2021 we presented it again in a CSS WG Color breakout and everyone agreed to incubate it in WICG, where it lives now . Why can’t we just standardize the API in Color.js? While one is influenced by the other, a Web Platform API has different constraints and needs to follow more restricted design principles compared to a JS library, which can be more flexible. E.g. exotic properties (things like color.lch.l ) are very common in JS libraries, but are now considered an antipattern in Web Platform APIs . Work on Color.js as well as the Color API continued, on and off as time permitted, but no release. There were always things to do and bugs to fix before more eyes would look at it. Because eyes were looking at it anyway, we even slapped a big fat warning on the homepage: Eventually a few days ago, I discovered that the Color.js package we had published on npm somehow has over 6000 downloads per week, nearly all of them direct. I would not bat an eyelid at those numbers if we had released Color.js into the wild, but for a library we actively avoided mentioning to anyone outside of standards groups, it was rather odd. How did this happen? Maybe it was the HTTP 203 episode that mentioned it in passing ? Regardless, it gave us hope that it’s filling a very real need in the pretty crowded space of color manipulation libraries and it gave us a push to finally get it out there. So here we are, releasing Color.js into the wild. So what’s cool about it? Completely color space agnostic, each Color object just has a reference to a color space, a list of coordinates, and optionally an alpha. Supports a large variety of color spaces including all color spaces from CSS Color 4 , as well as the unofficial CSS Color HDR draft. Supports interpolation as defined in CSS Color 4 Doesn’t skimp on color science: does actual gamut mapping instead of naïve clipping, and actual chromatic adaptation when converting between color spaces with different white points. Multiple DeltaE methods for calculating color difference (2000, CMC, 76, Jz, OK etc) The library itself is written to be very modular and ESM-first (with CJS and IIFE bundles) and provides a tree-shakeable API as well. Enjoy: Color.js There is also an entire (buggy, but usable) script in the website for realtime editable color demos that we call “Color Notebook”. It looks like this: And you can create and share your own documents with live Color.js demos . You log in with GitHub and the app saves in GitHub Gists. Color spaces presently supported by Color.js</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On Yak Shaving and ` `, a new HTML element for Markdown</title>
  <link>https://lea.verou.me/2021/11/on-yak-shaving-and-md-block-an-html-element-for-markdown/</link>
  <pubDate>Sun, 17 May 2026 03:25:08 +0200</pubDate>
  <description>This week has been Yak Shaving Galore. It went a bit like this: I’ve been working on a web component that I need for the project I’m working on. More on that later, but let’s call it for now. Of course that needs to be developed as a separate reusable library and released as a separate open source project. No, this is not the titular component, this was only level 1 of my multi-level yak shaving… 🤦🏽‍♀️ I wanted to showcase various usage examples of that component in its page, so I made another component for these demos: . This demo component would have markup with editable parts on one side and the live rendering on the other side. I wanted the editable parts to autosize as you type. Hey, I’ve written a library for that in the past, it’s called Stretchy ! But Stretchy was not written in ESM, nor did it support Shadow DOM. I must rewrite Stretchy in ESM and support Shadow DOM first! Surely it won’t take more than a half hour, it’s a tiny library. (It took more than a half hour) Ok, now I have a nice lil’ module, but I also need to export IIFE as well, so that it’s compatible with Stretchy v1. Let’s switch to Rollup and npm scripts and ditch Gulp. Oh look, Stretchy’s CSS is still written in Sass, even though it doesn’t really need it now. Let’s rewrite it to use CSS variables, use PostCSS for nesting, and use conic-gradient() instead of inline SVG data URIs. Ok, Stretchy v2 is ready, now I need to update its docs. Oooh, it doesn’t have a README? I should add one. But I don’t want to duplicate content between the page and the README. Hmmm, if only… I know! I’ll make a web component for rendering both inline and remote Markdown! I have an unfinished one lying around somewhere, surely it won’t take more than a couple hours to finish it? (It took almost a day, two with docs, demos etc) Done! Here it is! https://md-block.verou.m e Great! Now I can update Stretchy’s docs and release its v2 Great! Now I can use Stretchy in my component demoing my component and be back to only one level of yak shaving! Wow, it’s already Friday afternoon?! 🤦🏽‍♀️😂 Hopefully you find useful! Enjoy!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Releasing Color.js: A library that takes color seriously</title>
  <link>https://lea.verou.me/2022/06/releasing-colorjs/</link>
  <pubDate>Sun, 17 May 2026 03:25:08 +0200</pubDate>
  <description>Related: Chris blog post for the release of Color.js This post has been long overdue: Chris and I started working on Color.js in 2020, over 2 years ago! It was shortly after I had finished the Color lecture for the class I was teaching at MIT and I was appalled by the lack of color libraries that did the things I needed for the demos in my slides. I asked Chris, Hey, what if we make a Color library? You will bring your Color Science knowledge and I will bring my JS and API design knowledge. Wouldnt this be the coolest color library ever?. There was also a fair bit of discussion in the CSS WG about a native Color object for the Web Platform, and we needed to play around with JS for a while before we could work on an API that would be baked into browsers. We had a prototype ready in a few months and presented it to the CSS WG. People loved it and some started using it despite it not being officially released. There was even a library that used Color.js as a dependency! Once we got some experience from this usage, we worked on a draft specification for a Color API for the Web. In July 2021 we presented it again in a CSS WG Color breakout and everyone agreed to incubate it in WICG, where it lives now . Why cant we just standardize the API in Color.js? While one is influenced by the other, a Web Platform API has different constraints and needs to follow more restricted design principles compared to a JS library, which can be more flexible. E.g. exotic properties (things like color.lch.l ) are very common in JS libraries, but are now considered an antipattern in Web Platform APIs . Work on Color.js as well as the Color API continued, on and off as time permitted, but no release. There were always things to do and bugs to fix before more eyes would look at it. Because eyes were looking at it anyway, we even slapped a big fat warning on the homepage: Eventually a few days ago, I discovered that the Color.js package we had published on npm somehow has over 6000 downloads per week, nearly all of them direct. I would not bat an eyelid at those numbers if we had released Color.js into the wild, but for a library we actively avoided mentioning to anyone outside of standards groups, it was rather odd. How did this happen? Maybe it was the HTTP 203 episode that mentioned it in passing ? Regardless, it gave us hope that its filling a very real need in the pretty crowded space of color manipulation libraries and it gave us a push to finally get it out there. So here we are, releasing Color.js into the wild. So whats cool about it? Completely color space agnostic, each Color object just has a reference to a color space, a list of coordinates, and optionally an alpha. Supports a large variety of color spaces including all color spaces from CSS Color 4 , as well as the unofficial CSS Color HDR draft. Supports interpolation as defined in CSS Color 4 Doesnt skimp on color science: does actual gamut mapping instead of naïve clipping, and actual chromatic adaptation when converting between color spaces with different white points. Multiple DeltaE methods for calculating color difference (2000, CMC, 76, Jz, OK etc) The library itself is written to be very modular and ESM-first (with CJS and IIFE bundles) and provides a tree-shakeable API as well. Enjoy: Color.js There is also an entire (buggy, but usable) script in the website for realtime editable color demos that we call Color Notebook. It looks like this: And you can create and share your own documents with live Color.js demos . You log in with GitHub and the app saves in GitHub Gists. Color spaces presently supported by Color.js</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On Yak Shaving and ` `, a new HTML element for Markdown</title>
  <link>https://lea.verou.me/2021/11/on-yak-shaving-and-md-block-an-html-element-for-markdown/</link>
  <pubDate>Sun, 17 May 2026 03:25:08 +0200</pubDate>
  <description>This week has been Yak Shaving Galore. It went a bit like this: Ive been working on a web component that I need for the project Im working on. More on that later, but lets call it for now. Of course that needs to be developed as a separate reusable library and released as a separate open source project. No, this is not the titular component, this was only level 1 of my multi-level yak shaving I wanted to showcase various usage examples of that component in its page, so I made another component for these demos: . This demo component would have markup with editable parts on one side and the live rendering on the other side. I wanted the editable parts to autosize as you type. Hey, Ive written a library for that in the past, its called Stretchy ! But Stretchy was not written in ESM, nor did it support Shadow DOM. I must rewrite Stretchy in ESM and support Shadow DOM first! Surely it wont take more than a half hour, its a tiny library. (It took more than a half hour) Ok, now I have a nice lil module, but I also need to export IIFE as well, so that its compatible with Stretchy v1. Lets switch to Rollup and npm scripts and ditch Gulp. Oh look, Stretchys CSS is still written in Sass, even though it doesnt really need it now. Lets rewrite it to use CSS variables, use PostCSS for nesting, and use conic-gradient() instead of inline SVG data URIs. Ok, Stretchy v2 is ready, now I need to update its docs. Oooh, it doesnt have a README? I should add one. But I dont want to duplicate content between the page and the README. Hmmm, if only I know! Ill make a web component for rendering both inline and remote Markdown! I have an unfinished one lying around somewhere, surely it wont take more than a couple hours to finish it? (It took almost a day, two with docs, demos etc) Done! Here it is! https://md-block.verou.m e Great! Now I can update Stretchys docs and release its v2 Great! Now I can use Stretchy in my component demoing my component and be back to only one level of yak shaving! Wow, its already Friday afternoon?! Hopefully you find useful! Enjoy!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Custom properties with defaults: 3+1 strategies</title>
  <link>https://lea.verou.me/2021/10/custom-properties-with-defaults/</link>
  <pubDate>Sun, 17 May 2026 03:25:06 +0200</pubDate>
  <description>When developing customizable components, one often wants to expose various parameters of the styling as custom properties, and form a sort of CSS API . This is still underutlized, but there are libraries, e.g. Shoelace , that already list custom properties alongside other parts of each component’s API (even CSS parts !). Note: I’m using “component” here broadly, as any reusable chunk of HTML/CSS/JS, not necessarily a web component or framework component. What we are going to discuss applies to reusable chunks of HTML just as much as it does to “proper” web components. Let’s suppose we are designing a certain button styling, that looks like this: We want to support a --color custom property for creating color variations by setting multiple things internally: .fancy-button { border: .1em solid var(--color); background: transparent; color: var(--color); } .fancy-button:hover { background: var(--color); color: white; } Note that with the code above, if no --color is set, the three declarations using it will be IACVT and thus we’ll get a nearly unstyled text-only button with no background on hover ( transparent ), no border on hover, and the default black text color ( canvastext to be precise). That’s no good! IT’s important that we set defaults. However, using the fallback parameter for this gets tedious, and WET : .fancy-button { border: .1em solid var(--color, black); background: transparent; color: var(--color, black); } .fancy-button:hover { background: var(--color, black); color: white; } To avoid the repetition and still ensure --color always has a value, many people do this: .fancy-button { --color: black; border: .1em solid var(--color); background: transparent; color: var(--color); } .fancy-button:hover { background: var(--color); color: white; } However, this is not ideal for a number of reasons: It means that people cannot take advantage of inheritance to set --color on an ancestor. It means that people need to use specificity that overrides your own rules to set these properties. In this case this may only be 0,1,0 , but if your selectors are complex, it could end up being quite annoying (and introduce tight couplings, because developers should not need to know what your selectors are). If you insist going that route, :where() can be a useful tool to reduce specificity of your selectors while having as fine grained selection criteria as you want. It’s also one of the features I proposed for CSS, so I’m very proud that it’s now supported everywhere . :where() won’t solve the inheritance problem, but at least it will solve the specificity problem. What if we still use the fallback parameter and use a variable for the fallback? .fancy-button { --color-initial: black; border: .1em solid var(--color, var(--color-initial)); background: transparent; color: var(--color, var(--color-initial)); } .fancy-button:hover { background: var(--color, var(--color-initial)); color: white; } This works, and it has the advantage that people could even customize your default if they want to (though I cannot think of any use cases for that). But isn’t it so horribly verbose? What else could we do? My preferred solution is what I call pseudo-private custom properties . You use a different property internally than the one you expose, which is set to the one you expose plus the fallback: .fancy-button { --_color: var(--color, black); border: .1em solid var(--_color); background: transparent; color: var(--_color); } .fancy-button:hover { background: var(--_color); color: white; } I tend to use the same name prepended with an underscore. Some people may flinch at the idea of private properties that aren’t really private, but I will remind you that we’ve done this in JS for over 20 years (we only got real private properties fairly recently). Bonus: Defaults via @property registration If @property is fair game (it’s only supported in Chromium , but these days that still makes it supported in 70% of users’ browsers — which is a bit sad, but that’s another discussion), you could also set defaults that way: @property --color { syntax: &quot; &quot;; inherits: true; initial-value: black; } .fancy-button { border: .1em solid var(--color); background: transparent; color: var(--color); } .fancy-button:hover { background: var(--color); color: white; } Registering your property has several benefits (e.g. it makes it animatable), but if you’re only registering it for the purposes of setting a default, this way has several drawbacks: Property registration is global. Your component’s custom properties may clash with the host page’s custom properties, which is not great. The consequences of this can be quite dire, because @property fails silently, and the last one wins so you may just get the initial value of the host page’s property. In this case, that could very likely be transparent , with terrible results . And if your declaration is last and you get your own registered property, that means the rest of the page will also get yours, with equally potentially terrible results. With this method you cannot set different initial values per declaration (although you usually don’t want to). Not all custom property syntaxes can be described via @property yet. Bonus: Customizable single-checkbox pure CSS switch Just for the lulz, I made a switch (styling loosely inspired from Shoelace switch ) that is just a regular with a pretty extensive custom property API: CodePen Embed Fallback It is using the pseudo-private properties approach. Note how another bonus of this method is that there’s a little self-documentation right there about the component’s custom property API, even before any actual documentation is written. As an aside, things like this switch make me wish it was possible to create web components that subclass existing elements. There is an existing — somewhat awkward — solution with the is attribute , but Apple is blocking it . The alternative is to use a web component with ElementInternals to make it form-associated and accessible and mirror all checkbox methods and properties, but that is way too heavyweight, and prone to breakage in the future, as native checkboxes add more methods. There is also a polyfill , but for a simple switch it may be a bit overkill. We really shouldn’t need to be painstakingly mirroring native elements to subclass them… Enjoyed this article and want to learn more? I do teach courses on unlocking the full potential of CSS custom properties. You can watch my Frontend Masters Dynamic CSS course (currently in production) , or attend my upcoming Smashing workshop .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Custom properties with defaults: 3+1 strategies</title>
  <link>https://lea.verou.me/2021/10/custom-properties-with-defaults/</link>
  <pubDate>Sun, 17 May 2026 03:25:06 +0200</pubDate>
  <description>When developing customizable components, one often wants to expose various parameters of the styling as custom properties, and form a sort of CSS API . This is still underutlized, but there are libraries, e.g. Shoelace , that already list custom properties alongside other parts of each components API (even CSS parts !). Note: Im using component here broadly, as any reusable chunk of HTML/CSS/JS, not necessarily a web component or framework component. What we are going to discuss applies to reusable chunks of HTML just as much as it does to proper web components. Lets suppose we are designing a certain button styling, that looks like this: We want to support a --color custom property for creating color variations by setting multiple things internally: .fancy-button { border: .1em solid var(--color); background: transparent; color: var(--color); } .fancy-button:hover { background: var(--color); color: white; } Note that with the code above, if no --color is set, the three declarations using it will be IACVT and thus well get a nearly unstyled text-only button with no background on hover ( transparent ), no border on hover, and the default black text color ( canvastext to be precise). Thats no good! ITs important that we set defaults. However, using the fallback parameter for this gets tedious, and WET : .fancy-button { border: .1em solid var(--color, black); background: transparent; color: var(--color, black); } .fancy-button:hover { background: var(--color, black); color: white; } To avoid the repetition and still ensure --color always has a value, many people do this: .fancy-button { --color: black; border: .1em solid var(--color); background: transparent; color: var(--color); } .fancy-button:hover { background: var(--color); color: white; } However, this is not ideal for a number of reasons: It means that people cannot take advantage of inheritance to set --color on an ancestor. It means that people need to use specificity that overrides your own rules to set these properties. In this case this may only be 0,1,0 , but if your selectors are complex, it could end up being quite annoying (and introduce tight couplings, because developers should not need to know what your selectors are). If you insist going that route, :where() can be a useful tool to reduce specificity of your selectors while having as fine grained selection criteria as you want. Its also one of the features I proposed for CSS, so Im very proud that its now supported everywhere . :where() wont solve the inheritance problem, but at least it will solve the specificity problem. What if we still use the fallback parameter and use a variable for the fallback? .fancy-button { --color-initial: black; border: .1em solid var(--color, var(--color-initial)); background: transparent; color: var(--color, var(--color-initial)); } .fancy-button:hover { background: var(--color, var(--color-initial)); color: white; } This works, and it has the advantage that people could even customize your default if they want to (though I cannot think of any use cases for that). But isnt it so horribly verbose? What else could we do? My preferred solution is what I call pseudo-private custom properties . You use a different property internally than the one you expose, which is set to the one you expose plus the fallback: .fancy-button { --_color: var(--color, black); border: .1em solid var(--_color); background: transparent; color: var(--_color); } .fancy-button:hover { background: var(--_color); color: white; } I tend to use the same name prepended with an underscore. Some people may flinch at the idea of private properties that arent really private, but I will remind you that weve done this in JS for over 20 years (we only got real private properties fairly recently). Bonus: Defaults via @property registration If @property is fair game (its only supported in Chromium , but these days that still makes it supported in 70% of users browsers which is a bit sad, but thats another discussion), you could also set defaults that way: @property --color { syntax: &quot; &quot;; inherits: true; initial-value: black; } .fancy-button { border: .1em solid var(--color); background: transparent; color: var(--color); } .fancy-button:hover { background: var(--color); color: white; } Registering your property has several benefits (e.g. it makes it animatable), but if youre only registering it for the purposes of setting a default, this way has several drawbacks: Property registration is global. Your components custom properties may clash with the host pages custom properties, which is not great. The consequences of this can be quite dire, because @property fails silently, and the last one wins so you may just get the initial value of the host pages property. In this case, that could very likely be transparent , with terrible results . And if your declaration is last and you get your own registered property, that means the rest of the page will also get yours, with equally potentially terrible results. With this method you cannot set different initial values per declaration (although you usually dont want to). Not all custom property syntaxes can be described via @property yet. Bonus: Customizable single-checkbox pure CSS switch Just for the lulz, I made a switch (styling loosely inspired from Shoelace switch ) that is just a regular with a pretty extensive custom property API: CodePen Embed Fallback It is using the pseudo-private properties approach. Note how another bonus of this method is that theres a little self-documentation right there about the components custom property API, even before any actual documentation is written. As an aside, things like this switch make me wish it was possible to create web components that subclass existing elements. There is an existing somewhat awkward solution with the is attribute , but Apple is blocking it . The alternative is to use a web component with ElementInternals to make it form-associated and accessible and mirror all checkbox methods and properties, but that is way too heavyweight, and prone to breakage in the future, as native checkboxes add more methods. There is also a polyfill , but for a simple switch it may be a bit overkill. We really shouldnt need to be painstakingly mirroring native elements to subclass them Enjoyed this article and want to learn more? I do teach courses on unlocking the full potential of CSS custom properties. You can watch my Frontend Masters Dynamic CSS course (currently in production) , or attend my upcoming Smashing workshop .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Inherit ancestor font-size, for fun and profit</title>
  <link>https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/</link>
  <pubDate>Sun, 17 May 2026 03:25:05 +0200</pubDate>
  <description>If you’ve been writing CSS for any length of time, you’re probably familiar with the em unit, and possibly the other type-relative units . We are going to refer to em for the rest of this post, but anything described works for all type-relative units. As you well know, em resolves to the current font size on all properties except font-size , where it resolves to the parent font size. It can be quite useful for making scalable components that adapt to their context size. However, I have often come across cases where you actually need to “circumvent” one level of this. Either you need to set font-size to the grandparent font size instead of the parent one, or you need to set other properties to the parent font size, not the current one. If you’re already familiar with the problem and just want the solution , skip ahead . The next few paragraphs are for those thinking “but when would you ever need this?” Sometimes, there are workarounds, and it’s just a matter of keeping DRY. For example, take a look at this speech bubble: CodePen Embed Fallback Note this in the CSS: /* This needs to change every time the font-size changes: */ top: calc(100% + 1em / 2.5); font-size: 250%; Note that every time we change the font size we also need to adjust top . And ok, when they’re both defined in the same rule we can just delegate this to a variable: --m: 2.5; top: calc(100% + 1em / var(--m)); font-size: calc(var(--m) * 100%); However, in the general case the font size may be defined elsewhere. For example, a third party author may want to override the emoji size, they shouldn’t also need to override anything else, our CSS should just adapt. In other cases, it is simply not possible to multiply and divide by a factor and restore the ancestor font size. Most notably, when the current (or parent) font-size is set to 0 and we need to recover what it was one level up. I’ve come across many instances of this in the 16 years I’ve been writing CSS. Admittedly, there were way more use cases pre-Flexbox and friends, but it’s still useful, as we will see. In fact, it was the latest one that prompted this post. I needed to wrap elements by a generic container for a library I’m working on. Let me stop you there, no, I could not just set classes on the options, I needed an actual container in the DOM. As you can see in this pen , neither nor custom elements work here: when included in the markup they are just discarded by the parser, and when inserted via script they are in the DOM, but the options they contain are not visible. The only elements that work inside a are: , , and script-supporting elements (currently and ). Except , none of the rest renders any contents and thus, is not fit for my use case. It had to be , sadly. However, using , even without a label attribute inserts an ugly gap in the select menu, where the label would have gone ( pen ): (There were also gaps on the left of the labels, but we applied some CSS to remove them) There appears to be no way to remove said gap. Ideally, this should be fixed on the user agent level: Browsers should not generate a label box when there is no label attribute. However, I needed a solution now, not in the far future. There was no pseudo-element for targeting the generated label. The only solution that worked was along these lines ([pen](optgroup:not([label]) { display: contents; font-size: 0; } optgroup:not([label])&gt; * { font-size: 13.333px; })): optgroup:not([label]) { font-size: 0; } optgroup:not([label]) &gt; * { font-size: 13.333px; } The weird 13.333px value was taken directly from the Chrome UA stylesheet (as inspected). However, it is obviously flimsy, and will break any additional author styling. It would be far better if we could say “give me whatever 1em is on the grandparent”. Can we? The solution What if we could use custom properties to solve this? Our first attempt might look something like this: select { --em: 1em; } optgroup:not([label]) { font-size: 0; } optgroup:not([label]) &gt; * { font-size: var(--em); } However this is horribly broken : All the options have disappeared!! What on Earth happened here?! By default, custom properties are just containers for CSS tokens.When they inherit, they inherit as specified, with only any var() references substituted and no other processing. This means that the 1em we specified inherits as the 1em token , not as whatever absolute length it happens to resolve to on select . It only becomes an absolute length at the point of usage, and this is whatever 1em would be there, i.e. 0 . So all our options disappeared because we set their font size to 0 ! If only we could make 1em resolve to an actual absolute length at the point of declaration and inherit as that, just like native properties that accept lengths? Well, you’re in luck, because today we can! You may be familiar with the @property rule as “the thing that allows us to animate custom properties”. However, it is useful for so much more than that. If we register our custom property as a , this makes the 1em resolve on the element we specified it on, and inherit as an absolute length! Let’s try this : @property --em { syntax: &quot; &quot;; initial-value: 0; inherits: true; } select { --em: 1em; } optgroup:not([label]) { display: contents; font-size: 0; } optgroup:not([label]) &gt; * { font-size: var(--em); } /* Remove Chrome gap */ :where(optgroup:not([label]) &gt; option)::before { content: &quot;&quot;; } CodePen Embed Fallback And here is the same technique used for the speech bubble: CodePen Embed Fallback Fallback This is all fine and dandy for the 68% (as of June 2021) of users that are using a browser that supports @property , but what happens in the remaining 32%? It’s not pretty: We get the default behavior of an unregistered property, and thus none of our options show up! This is bad . We should clearly either provide a fallback or conditionally apply these rules only in browsers that support @property . We can easily detect @property support in JS and add a class to our root element: if (window.CSSPropertyRule) { let root = document.documentElement; root.classList.add(&quot;supports-atproperty&quot;); } Then we can just use the descendant combinator: :root.supports-atproperty optgroup:not([label]) { font-size: 0; } CSS-only fallback for @property While the JS fallback works great, I couldn’t help but wonder if there’s a CSS only way. My first thought was to use @supports : @supports (--em: flugelhorn) { /* Does not support @property */ } The theory was, if a browser supported any value to be assigned on a property registered as a , surely it does not support property registration. It turns out, registered properties do not validate their syntax at parse time, and thus are always valid for @supports . This is explained in the spec : When parsing a page’s CSS, UAs commonly make a number of optimizations to help with both speed and memory. One of those optimizations is that they only store the properties that will actually have an effect; they throw away invalid properties, and if you write the same property multiple times in a single declaration block, all but the last valid one will be thrown away. (This is an important part of CSS’s error-recovery and forward-compatibility behavior.) This works fine if the syntax of a property never changes over the lifetime of a page. If a custom property is registered, however, it can change its syntax, so that a property that was previously invalid suddenly becomes valid. The only ways to handle this are to either store every declaration, even those that were initially invalid (increasing the memory cost of pages), or to re-parse the entire page’s CSS with the new syntax rules (increasing the processing cost of registering a custom property). Neither of these are very desirable. Further, UA-defined properties have their syntax determined by the version of the UA the user is viewing the page with; this is out of the page author’s control, which is the entire reason for CSS’s error-recovery behavior and the practice of writing multiple declarations for varying levels of support. A custom property, on the other hand, has its syntax controlled by the page author, according to whatever stylesheet or script they’ve included in the page; there’s no unpredictability to be managed. Throwing away syntax-violating custom properties would thus only be, at best, a convenience for the page author, not a necessity like for UA-defined properties. Ok this is great, and totally makes sense, but what can we do? How can we provide a fallback? It turns out that there is a way, but brace yourself, as it’s quite hacky. I’m only going to describe it for entertainment purposes, but I think for real usage, the JS way is far more straightforward, and it’s the one I’ll be using myself. The main idea is to take advantage of the var() fallback argument of a second registered variable, that is registered as non-inheriting. We set it to the fallback value on an ancestor. If @property is supported, then this property will not be defined on the element of interest, since it does not inherit. Any other properties referencing it will be invalid at computed value time , and thus any var() fallbacks will apply. If @property is not supported, the property will inherit as normal and thus using it becomes our fallback. Here is an example with a simple green/red test to illustrate this concept: @property --test { syntax: &quot;*&quot;; inherits: false; } html { --test: red; } body { background: var(--test, green); } CodePen Embed Fallback And here is how we can use the same concept to provide a fallback for the example: @property --test { syntax: &quot;*&quot;; inherits: false; } select { --test: 1em; /* fallback */ --em: 1em; } optgroup:not([label]) { font-size: var(--test, 0); } Here is the finished demo .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Inherit ancestor font-size, for fun and profit</title>
  <link>https://lea.verou.me/2021/06/inherit-ancestor-font-size-for-fun-and-profit/</link>
  <pubDate>Sun, 17 May 2026 03:25:05 +0200</pubDate>
  <description>If youve been writing CSS for any length of time, youre probably familiar with the em unit, and possibly the other type-relative units . We are going to refer to em for the rest of this post, but anything described works for all type-relative units. As you well know, em resolves to the current font size on all properties except font-size , where it resolves to the parent font size. It can be quite useful for making scalable components that adapt to their context size. However, I have often come across cases where you actually need to circumvent one level of this. Either you need to set font-size to the grandparent font size instead of the parent one, or you need to set other properties to the parent font size, not the current one. If youre already familiar with the problem and just want the solution , skip ahead . The next few paragraphs are for those thinking but when would you ever need this? Sometimes, there are workarounds, and its just a matter of keeping DRY. For example, take a look at this speech bubble: CodePen Embed Fallback Note this in the CSS: /* This needs to change every time the font-size changes: */ top: calc(100% + 1em / 2.5); font-size: 250%; Note that every time we change the font size we also need to adjust top . And ok, when theyre both defined in the same rule we can just delegate this to a variable: --m: 2.5; top: calc(100% + 1em / var(--m)); font-size: calc(var(--m) * 100%); However, in the general case the font size may be defined elsewhere. For example, a third party author may want to override the emoji size, they shouldnt also need to override anything else, our CSS should just adapt. In other cases, it is simply not possible to multiply and divide by a factor and restore the ancestor font size. Most notably, when the current (or parent) font-size is set to 0 and we need to recover what it was one level up. Ive come across many instances of this in the 16 years Ive been writing CSS. Admittedly, there were way more use cases pre-Flexbox and friends, but its still useful, as we will see. In fact, it was the latest one that prompted this post. I needed to wrap elements by a generic container for a library Im working on. Let me stop you there, no, I could not just set classes on the options, I needed an actual container in the DOM. As you can see in this pen , neither nor custom elements work here: when included in the markup they are just discarded by the parser, and when inserted via script they are in the DOM, but the options they contain are not visible. The only elements that work inside a are: , , and script-supporting elements (currently and ). Except , none of the rest renders any contents and thus, is not fit for my use case. It had to be , sadly. However, using , even without a label attribute inserts an ugly gap in the select menu, where the label would have gone ( pen ): (There were also gaps on the left of the labels, but we applied some CSS to remove them) There appears to be no way to remove said gap. Ideally, this should be fixed on the user agent level: Browsers should not generate a label box when there is no label attribute. However, I needed a solution now, not in the far future. There was no pseudo-element for targeting the generated label. The only solution that worked was along these lines ([pen](optgroup:not([label]) { display: contents; font-size: 0; } optgroup:not([label])&gt; * { font-size: 13.333px; })): optgroup:not([label]) { font-size: 0; } optgroup:not([label]) &gt; * { font-size: 13.333px; } The weird 13.333px value was taken directly from the Chrome UA stylesheet (as inspected). However, it is obviously flimsy, and will break any additional author styling. It would be far better if we could say give me whatever 1em is on the grandparent. Can we? The solution What if we could use custom properties to solve this? Our first attempt might look something like this: select { --em: 1em; } optgroup:not([label]) { font-size: 0; } optgroup:not([label]) &gt; * { font-size: var(--em); } However this is horribly broken : All the options have disappeared!! What on Earth happened here?! By default, custom properties are just containers for CSS tokens.When they inherit, they inherit as specified, with only any var() references substituted and no other processing. This means that the 1em we specified inherits as the 1em token , not as whatever absolute length it happens to resolve to on select . It only becomes an absolute length at the point of usage, and this is whatever 1em would be there, i.e. 0 . So all our options disappeared because we set their font size to 0 ! If only we could make 1em resolve to an actual absolute length at the point of declaration and inherit as that, just like native properties that accept lengths? Well, youre in luck, because today we can! You may be familiar with the @property rule as the thing that allows us to animate custom properties. However, it is useful for so much more than that. If we register our custom property as a , this makes the 1em resolve on the element we specified it on, and inherit as an absolute length! Lets try this : @property --em { syntax: &quot; &quot;; initial-value: 0; inherits: true; } select { --em: 1em; } optgroup:not([label]) { display: contents; font-size: 0; } optgroup:not([label]) &gt; * { font-size: var(--em); } /* Remove Chrome gap */ :where(optgroup:not([label]) &gt; option)::before { content: &quot;&quot;; } CodePen Embed Fallback And here is the same technique used for the speech bubble: CodePen Embed Fallback Fallback This is all fine and dandy for the 68% (as of June 2021) of users that are using a browser that supports @property , but what happens in the remaining 32%? Its not pretty: We get the default behavior of an unregistered property, and thus none of our options show up! This is bad . We should clearly either provide a fallback or conditionally apply these rules only in browsers that support @property . We can easily detect @property support in JS and add a class to our root element: if (window.CSSPropertyRule) { let root = document.documentElement; root.classList.add(&quot;supports-atproperty&quot;); } Then we can just use the descendant combinator: :root.supports-atproperty optgroup:not([label]) { font-size: 0; } CSS-only fallback for @property While the JS fallback works great, I couldnt help but wonder if theres a CSS only way. My first thought was to use @supports : @supports (--em: flugelhorn) { /* Does not support @property */ } The theory was, if a browser supported any value to be assigned on a property registered as a , surely it does not support property registration. It turns out, registered properties do not validate their syntax at parse time, and thus are always valid for @supports . This is explained in the spec : When parsing a pages CSS, UAs commonly make a number of optimizations to help with both speed and memory. One of those optimizations is that they only store the properties that will actually have an effect; they throw away invalid properties, and if you write the same property multiple times in a single declaration block, all but the last valid one will be thrown away. (This is an important part of CSSs error-recovery and forward-compatibility behavior.) This works fine if the syntax of a property never changes over the lifetime of a page. If a custom property is registered, however, it can change its syntax, so that a property that was previously invalid suddenly becomes valid. The only ways to handle this are to either store every declaration, even those that were initially invalid (increasing the memory cost of pages), or to re-parse the entire pages CSS with the new syntax rules (increasing the processing cost of registering a custom property). Neither of these are very desirable. Further, UA-defined properties have their syntax determined by the version of the UA the user is viewing the page with; this is out of the page authors control, which is the entire reason for CSSs error-recovery behavior and the practice of writing multiple declarations for varying levels of support. A custom property, on the other hand, has its syntax controlled by the page author, according to whatever stylesheet or script theyve included in the page; theres no unpredictability to be managed. Throwing away syntax-violating custom properties would thus only be, at best, a convenience for the page author, not a necessity like for UA-defined properties. Ok this is great, and totally makes sense, but what can we do? How can we provide a fallback? It turns out that there is a way, but brace yourself, as its quite hacky. Im only going to describe it for entertainment purposes, but I think for real usage, the JS way is far more straightforward, and its the one Ill be using myself. The main idea is to take advantage of the var() fallback argument of a second registered variable, that is registered as non-inheriting. We set it to the fallback value on an ancestor. If @property is supported, then this property will not be defined on the element of interest, since it does not inherit. Any other properties referencing it will be invalid at computed value time , and thus any var() fallbacks will apply. If @property is not supported, the property will inherit as normal and thus using it becomes our fallback. Here is an example with a simple green/red test to illustrate this concept: @property --test { syntax: &quot;*&quot;; inherits: false; } html { --test: red; } body { background: var(--test, green); } CodePen Embed Fallback And here is how we can use the same concept to provide a fallback for the example: @property --test { syntax: &quot;*&quot;; inherits: false; } select { --test: 1em; /* fallback */ --em: 1em; } optgroup:not([label]) { font-size: var(--test, 0); } Here is the finished demo .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Is the current tab active?</title>
  <link>https://lea.verou.me/2021/05/is-the-current-tab-active/</link>
  <pubDate>Sun, 17 May 2026 03:25:04 +0200</pubDate>
  <description>Today I ran into an interesting problem. Interesting because it’s one of those very straightforward, deceptively simple questions, that after a fair amount of digging, does not appear to have a definite answer (though I would love to be wrong!). The problem was to determine if the current tab is active . Yes, as simple as that. Why? (i.e. my use case) I was working on my slide deck framework, Inspire.js . There is a presenter mode plugin , which spawns a new window with your slides (“projector view”), whereas your current window becomes a “presenter view”, with open notes, preview of the next slide, optional progress indicator for time etc. However, this plugin was not very good . The two windows are synced, but only if you use presenter view to navigate slides. If you use the projector view to advance slides, the syncing breaks. Why would you use the projector mode? Many reasons, e.g. to interact with a live demo, or even play a video. If you have a live demo heavy presentation, you may even want to mirror your screen and only ever interact with the projector mode, while having the presenter mode on a secondary screen, just to look at. The way the plugin worked was that every time the slide changed in the presenter view, it propagated the change in the projector view. To make the syncing bidirectional, it would be good to know if the current window is the active tab, and if so, propagate all slide navigation to the other one, regardless of which one is the projector view and which one is the presenter view. And this, my friends, is how I ended up in this rabbit hole. (Yes, there are other solutions to this particular problem. I could just always propagate regardless and have checks in place to avoid infinite loops. But that’s beside the point.) What about the Visibility API? In most resources around the Web, people were rejoicing about how the Visibility API makes this problem trivial. “Just use document.hidden !” people would gleefully recommend to others. Yes, the Visibility API is great, when you want to determine whether the current tab is visible . That is not the same as whether it is active . You may have two windows side by side, both visible, but only one of them is active. You may even have a window entirely obscuring another window, but you can still tab through to it and make it active. Active and visible are entirely orthogonal states, which are only loosely correlated. In my use case, given that both the projector view and presenter view would be visible at all times, this is a no-go that doesn’t even solve a subset of use cases. What about focus and blur events on window? The other solution that was heavily recommended was using the focus and blur events on window . This does get us partway there. Indeed, when the current tab becomes active, the focus event fires. When another tab becomes active, the blur event fires. Notice the emphasis on “becomes”. Events notify us about a state change, but they are no help for determining the current state . If we get a focus or blur event, we know whether our tab is active or not, but if we don’t get any, we simply don’t know. A tab can start off as active or not, and there is no way to tell. How can a tab possibly start off as inactive ? One easy way to reproduce this is to hit Return on the address bar and immediately switch to another window. The tab you just loaded just starts off as inactive and no blur event is ever fired. What about document.activeElement? The document.activeElement property will always return the currently focused element in a page. Can we use it to determine if a window currently has focus? Nope, cause that would be too easy. Run setTimeout(() =&gt; console.log(document.activeElement), 2000) in the console and quickly switch windows. Return &gt;2 seconds later and see what was logged. It’s the element! Wait, maybe we can assume that if the currently focused element is a element then the current window is inactive? Nope, you get the same result in an active tab, if you simply haven’t focused anywhere. What about document.hasFocus()? When I discovered document.hasFocus() I thought that was the end of it. Surely, this is exactly what I need?!? The spec made it sound so promising. I quickly switched to my about:blank tab that I use for trying things out, and ran it in the console. &gt; document.hasFocus() 🤦🏽‍♀️🤦🏽‍♀️🤦🏽‍♀️ Neeeext! Edit: document.hasFocus() may be the solution after all! As pointed out to me on Twitter, the problem above was that unlike I did with document.activeElement , I ran this synchronously in the console and it returned false because the console as the active window. An asynchronous log while I make sure the actual window is focused would do the trick. The anti-climactic conclusion Edit: I left this section in because the moral is still valid for other cases, but it looks like document.hasFocus() was the solution after all. If you’re expecting this to end with a revelation of an amazing API that I had originally missed and addresses this, you will be disappointed. If there is such a silver bullet, I did not find it. Maybe someone will point it out to me after publishing this blog post, in which case I will update it so that you don’t struggle like I did. But in my case, I simply gave up trying to find a general solution. Instead, I took advantage of the knowledge my code had in this specific situation: I knew what the other window was, and I primarily cared which one of the two (if any) had focus. // Track whether presenter or projector is the active window addEventListener(&quot;focus&quot;, _ =&gt; { Inspire.isActive = true; // If this window is focused, no other can be if (Inspire.projector) { Inspire.projector.Inspire.isActive = false; } else if (Inspire.presenter) { Inspire.presenter.Inspire.isActive = false; } }); addEventListener(&quot;blur&quot;, _ =&gt; { Inspire.isActive = false; // If this window is not focused, // we cannot make assumptions about which one is. }); Given that the presenter view calls window.focus() after opening the projector view, in practice this was pretty bulletproof. What’s the moral of this story? Sometimes simple questions do not have a good answer when it comes to the Web Platform If your code cannot answer the general question correctly in all cases, maybe it can answer a specific one that solves your particular problem, even if that leads to a less elegant solution. That’s it folks.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Is the current tab active?</title>
  <link>https://lea.verou.me/2021/05/is-the-current-tab-active/</link>
  <pubDate>Sun, 17 May 2026 03:25:04 +0200</pubDate>
  <description>Today I ran into an interesting problem. Interesting because its one of those very straightforward, deceptively simple questions, that after a fair amount of digging, does not appear to have a definite answer (though I would love to be wrong!). The problem was to determine if the current tab is active . Yes, as simple as that. Why? (i.e. my use case) I was working on my slide deck framework, Inspire.js . There is a presenter mode plugin , which spawns a new window with your slides (projector view), whereas your current window becomes a presenter view, with open notes, preview of the next slide, optional progress indicator for time etc. However, this plugin was not very good . The two windows are synced, but only if you use presenter view to navigate slides. If you use the projector view to advance slides, the syncing breaks. Why would you use the projector mode? Many reasons, e.g. to interact with a live demo, or even play a video. If you have a live demo heavy presentation, you may even want to mirror your screen and only ever interact with the projector mode, while having the presenter mode on a secondary screen, just to look at. The way the plugin worked was that every time the slide changed in the presenter view, it propagated the change in the projector view. To make the syncing bidirectional, it would be good to know if the current window is the active tab, and if so, propagate all slide navigation to the other one, regardless of which one is the projector view and which one is the presenter view. And this, my friends, is how I ended up in this rabbit hole. (Yes, there are other solutions to this particular problem. I could just always propagate regardless and have checks in place to avoid infinite loops. But thats beside the point.) What about the Visibility API? In most resources around the Web, people were rejoicing about how the Visibility API makes this problem trivial. Just use document.hidden ! people would gleefully recommend to others. Yes, the Visibility API is great, when you want to determine whether the current tab is visible . That is not the same as whether it is active . You may have two windows side by side, both visible, but only one of them is active. You may even have a window entirely obscuring another window, but you can still tab through to it and make it active. Active and visible are entirely orthogonal states, which are only loosely correlated. In my use case, given that both the projector view and presenter view would be visible at all times, this is a no-go that doesnt even solve a subset of use cases. What about focus and blur events on window? The other solution that was heavily recommended was using the focus and blur events on window . This does get us partway there. Indeed, when the current tab becomes active, the focus event fires. When another tab becomes active, the blur event fires. Notice the emphasis on becomes. Events notify us about a state change, but they are no help for determining the current state . If we get a focus or blur event, we know whether our tab is active or not, but if we dont get any, we simply dont know. A tab can start off as active or not, and there is no way to tell. How can a tab possibly start off as inactive ? One easy way to reproduce this is to hit Return on the address bar and immediately switch to another window. The tab you just loaded just starts off as inactive and no blur event is ever fired. What about document.activeElement? The document.activeElement property will always return the currently focused element in a page. Can we use it to determine if a window currently has focus? Nope, cause that would be too easy. Run setTimeout(() =&gt; console.log(document.activeElement), 2000) in the console and quickly switch windows. Return &gt;2 seconds later and see what was logged. Its the element! Wait, maybe we can assume that if the currently focused element is a element then the current window is inactive? Nope, you get the same result in an active tab, if you simply havent focused anywhere. What about document.hasFocus()? When I discovered document.hasFocus() I thought that was the end of it. Surely, this is exactly what I need?!? The spec made it sound so promising. I quickly switched to my about:blank tab that I use for trying things out, and ran it in the console. &gt; document.hasFocus() Neeeext! Edit: document.hasFocus() may be the solution after all! As pointed out to me on Twitter, the problem above was that unlike I did with document.activeElement , I ran this synchronously in the console and it returned false because the console as the active window. An asynchronous log while I make sure the actual window is focused would do the trick. The anti-climactic conclusion Edit: I left this section in because the moral is still valid for other cases, but it looks like document.hasFocus() was the solution after all. If youre expecting this to end with a revelation of an amazing API that I had originally missed and addresses this, you will be disappointed. If there is such a silver bullet, I did not find it. Maybe someone will point it out to me after publishing this blog post, in which case I will update it so that you dont struggle like I did. But in my case, I simply gave up trying to find a general solution. Instead, I took advantage of the knowledge my code had in this specific situation: I knew what the other window was, and I primarily cared which one of the two (if any) had focus. // Track whether presenter or projector is the active window addEventListener(&quot;focus&quot;, _ =&gt; { Inspire.isActive = true; // If this window is focused, no other can be if (Inspire.projector) { Inspire.projector.Inspire.isActive = false; } else if (Inspire.presenter) { Inspire.presenter.Inspire.isActive = false; } }); addEventListener(&quot;blur&quot;, _ =&gt; { Inspire.isActive = false; // If this window is not focused, // we cannot make assumptions about which one is. }); Given that the presenter view calls window.focus() after opening the projector view, in practice this was pretty bulletproof. Whats the moral of this story? Sometimes simple questions do not have a good answer when it comes to the Web Platform If your code cannot answer the general question correctly in all cases, maybe it can answer a specific one that solves your particular problem, even if that leads to a less elegant solution. Thats it folks.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>82% of developers get this 3 line CSS quiz wrong</title>
  <link>https://lea.verou.me/2021/05/82-of-developers-get-this-3-line-css-quiz-wrong/</link>
  <pubDate>Sun, 17 May 2026 03:25:03 +0200</pubDate>
  <description>( I always wanted to do a clickbait title like this and when this chance came along I could not pass it up. 😅 Sorry! ) While putting my ideas into slides for my Dynamic CSS workshop for next week, I was working on a slide explaining how the CSS wide keywords work with custom properties. inherit , initial , unset I had used numerous times and knew well. But what about revert ? How did that work? I had an idea, but quickly coded up a demo to try it out. The code was: :root { --accent-color: skyblue; } div { --accent-color: revert; background: var(--accent-color, orange); } Phew, I was correct, but the amount of uncertainty I had before seeing the result tipped me that I might be on to something. Before you read on, take a moment to think about what you would vote. Warning: Spoilers ahead! 🤔 🤔 🤔 🤔 So I posted a quiz on Twitter: https://twitter.com/LeaVerou/status/1395379573190168576 These were the results after the 24 hours it ran for: orange was the clear winner, and the actual correct answer, skyblue only got 18.1%, nearly the same as transparent ! If you got it wrong, you’re in very good company: not only did 82% of poll respondents get it wrong as well, but even the editor of the CSS Variables spec and co-editor of CSS Cascading and Inheritance (which defines revert ), Tab Atkins , told me privately that he got it wrong too: he voted for orange ! (Yes, I did get his permission to mention this) So what actually happens? Why do we get skyblue ? I will try to explain as best as I can. Let’s start by what revert does: It reverts the cascaded value of the property from its current value to the value the property would have had if no changes had been made by the current style origin to the current element. This means it cancels out any author styles, and resets back to whatever value the property would have from the user stylesheet and UA stylesheet. Assuming there is no --accent-color declaration in the user stylesheet, and of course UA stylesheets don’t set custom properties, then that means the property doesn’t have a value. Since custom properties are inherited properties (unless they are registered with inherits: false , but this one is not), this means the inherited value trickles in, which is — you guessed it — skyblue . You can see for yourself in this codepen . CodePen Embed Fallback What if our property were registered as non-inheriting? Would it then be orange ? Nice try, but no. When we register a custom property, it is mandatory to provide an initial value. This means that the property always resolves to a value, even --accent-color: initial does not trigger the fallback anymore. You can see this for yourself in this codepen ( Chrome only as of May 2021 ). CodePen Embed Fallback Liked this? Then you will love the workshop ! There are still a few tickets left !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>82% of developers get this 3 line CSS quiz wrong</title>
  <link>https://lea.verou.me/2021/05/82-of-developers-get-this-3-line-css-quiz-wrong/</link>
  <pubDate>Sun, 17 May 2026 03:25:03 +0200</pubDate>
  <description>( I always wanted to do a clickbait title like this and when this chance came along I could not pass it up. Sorry! ) While putting my ideas into slides for my Dynamic CSS workshop for next week, I was working on a slide explaining how the CSS wide keywords work with custom properties. inherit , initial , unset I had used numerous times and knew well. But what about revert ? How did that work? I had an idea, but quickly coded up a demo to try it out. The code was: :root { --accent-color: skyblue; } div { --accent-color: revert; background: var(--accent-color, orange); } Phew, I was correct, but the amount of uncertainty I had before seeing the result tipped me that I might be on to something. Before you read on, take a moment to think about what you would vote. Warning: Spoilers ahead! So I posted a quiz on Twitter: https://twitter.com/LeaVerou/status/1395379573190168576 These were the results after the 24 hours it ran for: orange was the clear winner, and the actual correct answer, skyblue only got 18.1%, nearly the same as transparent ! If you got it wrong, youre in very good company: not only did 82% of poll respondents get it wrong as well, but even the editor of the CSS Variables spec and co-editor of CSS Cascading and Inheritance (which defines revert ), Tab Atkins , told me privately that he got it wrong too: he voted for orange ! (Yes, I did get his permission to mention this) So what actually happens? Why do we get skyblue ? I will try to explain as best as I can. Lets start by what revert does: It reverts the cascaded value of the property from its current value to the value the property would have had if no changes had been made by the current style origin to the current element. This means it cancels out any author styles, and resets back to whatever value the property would have from the user stylesheet and UA stylesheet. Assuming there is no --accent-color declaration in the user stylesheet, and of course UA stylesheets dont set custom properties, then that means the property doesnt have a value. Since custom properties are inherited properties (unless they are registered with inherits: false , but this one is not), this means the inherited value trickles in, which is you guessed it skyblue . You can see for yourself in this codepen . CodePen Embed Fallback What if our property were registered as non-inheriting? Would it then be orange ? Nice try, but no. When we register a custom property, it is mandatory to provide an initial value. This means that the property always resolves to a value, even --accent-color: initial does not trigger the fallback anymore. You can see this for yourself in this codepen ( Chrome only as of May 2021 ). CodePen Embed Fallback Liked this? Then you will love the workshop ! There are still a few tickets left !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Dark mode in 5 minutes, with inverted lightness variables</title>
  <link>https://lea.verou.me/2021/03/inverted-lightness-variables/</link>
  <pubDate>Sun, 17 May 2026 03:05:24 +0200</pubDate>
  <description>By now, you probably know that you can use custom properties for individual color components, to avoid repeating the same color coordinates multiple times throughout your theme. You may even know that you can use the same variable for multiple components, e.g. HSL hue and lightness: :root { --primary-hs: 250 30%; } h1 { color: hsl(var(--primary-hs) 30%); } article { background: hsl(var(--primary-hs) 90%); } article h2 { background: hsl(var(--primary-hs) 40%); color: white; } Here is a very simple page designed with this technque : CodePen Embed Fallback Unlike preprocessor variables, you could even locally override the variable, to have blocks with a different accent color : :root { --primary-hs: 250 30%; --secondary-hs: 190 40%; } article { background: hsl(var(--primary-hs) 90%); } article.alt { --primary-hs: var(--secondary-hs); } CodePen Embed Fallback This is all fine and dandy, until dark mode comes into play. The idea of using custom properties to make it easier to adapt a theme to dark mode is not new. However, in every article I have seen , the strategy suggested is to create a bunch of custom properties, one for each color, and override them in a media query. This is a fine approach, and you’ll likely want to do that for at least part of your colors eventually. However, even in the most disciplined of designs, not every color is a CSS variable. You often have colors declared inline, especially grays (e.g. the footer color in our example). This means that adding a dark mode is taxing enough that you may put it off for later, especially on side projects. The trick I’m going to show you will make anyone who knows enough about color cringe (sorry Chris!) but it does help you create a dark mode that works in minutes. It won’t be great, and you should eventually tweak it to create a proper dark mode (also dark mode is not just about swapping colors ) but it’s better than nothing and can serve as a base. The basic idea is to use custom properties for the lightness of colors instead of the entire color. Then, in dark mode, you override these variables with 100% - lightness . This generally produces light colors for dark colors, medium colors for medium colors, and dark colors for light colors, and still allows you to define colors inline, instead of forcing you to use a variable for every single color. This is what the code would look like for our example: root { --primary-hs: 250 30%; --secondary-hs: 190 40%; --l-0: 0%; --l-30: 30%; --l-40: 40%; --l-50: 50%; --l-90: 90%; --l-100: 100%; } @media (prefers-color-scheme: dark) { :root { --l-0: 100%; --l-30: 70%; --l-40: 60%; --l-90: 10%; --l-100: 0%; } } body { background: hsl(0 0% var(--l-100)); color: hsl(0 0% var(--l-0)); } h1 { color: hsl(var(--primary-hs) var(--l-30)); } article { background: hsl(var(--primary-hs) var(--l-90)); } article h2 { background: hsl(var(--primary-hs) 40%); color: white; } footer { color: hsl(0 0% var(--l-40)); } CodePen Embed Fallback The result looks like this in light &amp; dark mode: The light mode we designed and the auto-generated dark mode, side by side Note that here we indiscriminately replaced all lightnesses with lightness variables. In reality, we don’t need to be quite as sweeping. For example, the article titles would actually look better and would have better contrast if we just kept them the same: Comparison of dark mode with every lightness becoming a variable versus a more refined approach, where we make exceptions as needed (in this case the background and text colors for article &gt; h2 ). These are decisions that are easy to make while you go through your CSS replacing lightness percentages with variables and previewing the result. The problem with HSL But why were the article headers easier to read with their original colors than with inverted lightness? The root cause is that HSL lightness does not actually correspond to what humans perceive as lightness, and the same lightness difference can produce vastly different perceptual differences. That is the big problem with this approach: it assumes that HSL lightness actually means something, but as we’ve discussed before , it does not. Yellow and blue have the same HSL lightness (50%) for crying out loud! Also, you will notice that your dark colors have smaller differences between them than your light colors, because HSL is not perceptually uniform . Does that mean the technique is not useful for anything other than a placeholder while we develop our real dark mode, if that? Well, things are not quite as grim. Soon enough, we will get LCH colors in the browser. The first browser implementation just recently shipped in Safari and there is activity in that space among the other browser vendors too. LCH is a much better color space for this technique, because its lightness actually means something, not just across different lightnesses of the same color, but across different hues and chromas. This next example needs Safari TP 120+ . Compare these two gradients, the top one showing various HSL colors all with lightness 50%, and the bottom various LCH colors, all with lightness 50%. You can even adjust the slider and try different lightnesses: CodePen Embed Fallback Here is a screenshot for those of you who don’t have access to Safari TP 120+: Notice that in HSL, some colors (like yellow and cyan) are much lighter than others. In LCH, all colors at the same lightness are, well, the same lightness. Keep in mind that LCH chroma doesn’t really correspond to HSL lightness, so even though we’ve set it to the same number, it doesn’t correspond to the same thing. So, how would this technique work with LCH colors? Let’s try it out! I used this tool to convert the existing HSL colors to LCH, then tweaked the values manually a bit as the initially converted colors didn’t look nice across all LCH lightnesses (note that HSL colors with the same hue and saturation may have different hue and chromas in LCH. The opposite would defeat the point!). This is what this technique looks like with LCH colors instead (you will need Safari TP 120 or later to view this): CodePen Embed Fallback And here is a screenshot: Light mode and auto-generated dark mode via inverted lightness variables in LCH. Not only does dark mode look a lot better, but even in light mode, our two alternate colors actually look more uniform since they have the same LCH lightness. Here is a comparison of the two dark modes: Light mode and auto-generated dark mode via inverted lightness variables in LCH. Comparison of the two auto-generated dark modes, via HSL lightness on the left and LCH lightness on the right. Here you can see an animated comparison of them over each other: Note that in reality, until LCH colors are reliably supported everywhere you’d need to provide a fallback via @supports , but for brevity, I did not include one in this demo. Automating generation of lightness variables If you are using a preprocessor that supports loops, such as Sass, you can automate the generation of these variables, and make them even more granular, e.g. every 5%: :root { @for $i from 0 through 20 { --l-#{$i * 5}: #{$i * 5}%; } } @media (prefers-color-scheme: dark) { :root { @for $i from 0 through 20 { --l-#{$i * 5}: #{100 - $i * 5}%; } } } Can we make lightness variables more DRY? Some of you may have disliked the repetition of values: we need to declare e.g. --l-40 as 40%, then set it to 60% in dark mode. Can’t we derive it somehow, by subtracting the value we already have from 100%? Those with experience in programming may try something like this: --l-40: calc(100% - var(--l-40)); However, this will not work. CSS is not an imperative language. It does not have steps of calculation, where variables have different values before and after each step. There is no such concept of time, all declarations that are currently applied, need to be true at once. It’s more similar to the reactive evaluation of spreadsheet formulas than to computation in JS and other popular programming languages (there are general purpose reactive programming languages, but they are less well known). Therefore, declarations like the one above are considered cycles: since --l-40 cannot refer to itself, this is an error, and --l-40 would be set to its initial value as an error recovery mechanism (since CSS cannot throw errors). So, is there a way to avoid declaring lightness variables twice, once for light mode and once for dark mode? There is , but I wouldn’t recommend it. It makes the code more convoluted to read and comprehend, for little benefit. But for the sake of intellectual amusement, I’m going to describe it here. Instead of setting --l-40 to 40%, we are going to set it in terms of its difference from 50% , i.e. -10% . Then, calc(50% + var(--l-40)) gives us 40% and calc(50% - var(--l-40)) gives us 60%, the two values we need. We can therefore declare one variable that is -1 in dark mode and 1 in light mode, and just multiply with that. Here is a subset of what our code would be like with this: :root { --dm: 1; /* Example declaration: */ --l-40: -10%; } @media (prefers-color-scheme: dark) { :root { --dm: -1; } } /* Example usage: */ footer { color: hsl(0 0% calc(50% + var(--dm) * var(--l-40)); /* Ewww! */ } And hopefully now you can see why I wouldn’t recommend this: it makes usage much more complicated, to DRY up a few declarations that would only be specified once. It’s this kind of obsessive adherence to DRY that programmers eventually realize is counterproductive . Liked this article? Sign up for my Smashing Workshop on Dynamic CSS for more content like this!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Dark mode in 5 minutes, with inverted lightness variables</title>
  <link>https://lea.verou.me/2021/03/inverted-lightness-variables/</link>
  <pubDate>Sun, 17 May 2026 03:05:24 +0200</pubDate>
  <description>By now, you probably know that you can use custom properties for individual color components, to avoid repeating the same color coordinates multiple times throughout your theme. You may even know that you can use the same variable for multiple components, e.g. HSL hue and lightness: :root { --primary-hs: 250 30%; } h1 { color: hsl(var(--primary-hs) 30%); } article { background: hsl(var(--primary-hs) 90%); } article h2 { background: hsl(var(--primary-hs) 40%); color: white; } Here is a very simple page designed with this technque : CodePen Embed Fallback Unlike preprocessor variables, you could even locally override the variable, to have blocks with a different accent color : :root { --primary-hs: 250 30%; --secondary-hs: 190 40%; } article { background: hsl(var(--primary-hs) 90%); } article.alt { --primary-hs: var(--secondary-hs); } CodePen Embed Fallback This is all fine and dandy, until dark mode comes into play. The idea of using custom properties to make it easier to adapt a theme to dark mode is not new. However, in every article I have seen , the strategy suggested is to create a bunch of custom properties, one for each color, and override them in a media query. This is a fine approach, and youll likely want to do that for at least part of your colors eventually. However, even in the most disciplined of designs, not every color is a CSS variable. You often have colors declared inline, especially grays (e.g. the footer color in our example). This means that adding a dark mode is taxing enough that you may put it off for later, especially on side projects. The trick Im going to show you will make anyone who knows enough about color cringe (sorry Chris!) but it does help you create a dark mode that works in minutes. It wont be great, and you should eventually tweak it to create a proper dark mode (also dark mode is not just about swapping colors ) but its better than nothing and can serve as a base. The basic idea is to use custom properties for the lightness of colors instead of the entire color. Then, in dark mode, you override these variables with 100% - lightness . This generally produces light colors for dark colors, medium colors for medium colors, and dark colors for light colors, and still allows you to define colors inline, instead of forcing you to use a variable for every single color. This is what the code would look like for our example: root { --primary-hs: 250 30%; --secondary-hs: 190 40%; --l-0: 0%; --l-30: 30%; --l-40: 40%; --l-50: 50%; --l-90: 90%; --l-100: 100%; } @media (prefers-color-scheme: dark) { :root { --l-0: 100%; --l-30: 70%; --l-40: 60%; --l-90: 10%; --l-100: 0%; } } body { background: hsl(0 0% var(--l-100)); color: hsl(0 0% var(--l-0)); } h1 { color: hsl(var(--primary-hs) var(--l-30)); } article { background: hsl(var(--primary-hs) var(--l-90)); } article h2 { background: hsl(var(--primary-hs) 40%); color: white; } footer { color: hsl(0 0% var(--l-40)); } CodePen Embed Fallback The result looks like this in light &amp; dark mode: The light mode we designed and the auto-generated dark mode, side by side Note that here we indiscriminately replaced all lightnesses with lightness variables. In reality, we dont need to be quite as sweeping. For example, the article titles would actually look better and would have better contrast if we just kept them the same: Comparison of dark mode with every lightness becoming a variable versus a more refined approach, where we make exceptions as needed (in this case the background and text colors for article &gt; h2 ). These are decisions that are easy to make while you go through your CSS replacing lightness percentages with variables and previewing the result. The problem with HSL But why were the article headers easier to read with their original colors than with inverted lightness? The root cause is that HSL lightness does not actually correspond to what humans perceive as lightness, and the same lightness difference can produce vastly different perceptual differences. That is the big problem with this approach: it assumes that HSL lightness actually means something, but as weve discussed before , it does not. Yellow and blue have the same HSL lightness (50%) for crying out loud! Also, you will notice that your dark colors have smaller differences between them than your light colors, because HSL is not perceptually uniform . Does that mean the technique is not useful for anything other than a placeholder while we develop our real dark mode, if that? Well, things are not quite as grim. Soon enough, we will get LCH colors in the browser. The first browser implementation just recently shipped in Safari and there is activity in that space among the other browser vendors too. LCH is a much better color space for this technique, because its lightness actually means something, not just across different lightnesses of the same color, but across different hues and chromas. This next example needs Safari TP 120+ . Compare these two gradients, the top one showing various HSL colors all with lightness 50%, and the bottom various LCH colors, all with lightness 50%. You can even adjust the slider and try different lightnesses: CodePen Embed Fallback Here is a screenshot for those of you who dont have access to Safari TP 120+: Notice that in HSL, some colors (like yellow and cyan) are much lighter than others. In LCH, all colors at the same lightness are, well, the same lightness. Keep in mind that LCH chroma doesnt really correspond to HSL lightness, so even though weve set it to the same number, it doesnt correspond to the same thing. So, how would this technique work with LCH colors? Lets try it out! I used this tool to convert the existing HSL colors to LCH, then tweaked the values manually a bit as the initially converted colors didnt look nice across all LCH lightnesses (note that HSL colors with the same hue and saturation may have different hue and chromas in LCH. The opposite would defeat the point!). This is what this technique looks like with LCH colors instead (you will need Safari TP 120 or later to view this): CodePen Embed Fallback And here is a screenshot: Light mode and auto-generated dark mode via inverted lightness variables in LCH. Not only does dark mode look a lot better, but even in light mode, our two alternate colors actually look more uniform since they have the same LCH lightness. Here is a comparison of the two dark modes: Light mode and auto-generated dark mode via inverted lightness variables in LCH. Comparison of the two auto-generated dark modes, via HSL lightness on the left and LCH lightness on the right. Here you can see an animated comparison of them over each other: Note that in reality, until LCH colors are reliably supported everywhere youd need to provide a fallback via @supports , but for brevity, I did not include one in this demo. Automating generation of lightness variables If you are using a preprocessor that supports loops, such as Sass, you can automate the generation of these variables, and make them even more granular, e.g. every 5%: :root { @for $i from 0 through 20 { --l-#{$i * 5}: #{$i * 5}%; } } @media (prefers-color-scheme: dark) { :root { @for $i from 0 through 20 { --l-#{$i * 5}: #{100 - $i * 5}%; } } } Can we make lightness variables more DRY? Some of you may have disliked the repetition of values: we need to declare e.g. --l-40 as 40%, then set it to 60% in dark mode. Cant we derive it somehow, by subtracting the value we already have from 100%? Those with experience in programming may try something like this: --l-40: calc(100% - var(--l-40)); However, this will not work. CSS is not an imperative language. It does not have steps of calculation, where variables have different values before and after each step. There is no such concept of time, all declarations that are currently applied, need to be true at once. Its more similar to the reactive evaluation of spreadsheet formulas than to computation in JS and other popular programming languages (there are general purpose reactive programming languages, but they are less well known). Therefore, declarations like the one above are considered cycles: since --l-40 cannot refer to itself, this is an error, and --l-40 would be set to its initial value as an error recovery mechanism (since CSS cannot throw errors). So, is there a way to avoid declaring lightness variables twice, once for light mode and once for dark mode? There is , but I wouldnt recommend it. It makes the code more convoluted to read and comprehend, for little benefit. But for the sake of intellectual amusement, Im going to describe it here. Instead of setting --l-40 to 40%, we are going to set it in terms of its difference from 50% , i.e. -10% . Then, calc(50% + var(--l-40)) gives us 40% and calc(50% - var(--l-40)) gives us 60%, the two values we need. We can therefore declare one variable that is -1 in dark mode and 1 in light mode, and just multiply with that. Here is a subset of what our code would be like with this: :root { --dm: 1; /* Example declaration: */ --l-40: -10%; } @media (prefers-color-scheme: dark) { :root { --dm: -1; } } /* Example usage: */ footer { color: hsl(0 0% calc(50% + var(--dm) * var(--l-40)); /* Ewww! */ } And hopefully now you can see why I wouldnt recommend this: it makes usage much more complicated, to DRY up a few declarations that would only be specified once. Its this kind of obsessive adherence to DRY that programmers eventually realize is counterproductive . Liked this article? Sign up for my Smashing Workshop on Dynamic CSS for more content like this!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Mass function overloading: why and how?</title>
  <link>https://lea.verou.me/2021/02/mass-function-overloading-why-and-how/</link>
  <pubDate>Sun, 17 May 2026 03:05:23 +0200</pubDate>
  <description>One of the things I’ve been doing for the past few months (on and off—more off than on TBH) is rewriting Bliss to use ESM 1 . Since Bliss v1 was not using a modular architecture at all, this introduced some interesting challenges. Bliss is essentially a collection of helper functions. Most of these functions have a number of different signatures , to allow for more compact, readable code. The functions can be used for single things (one element, one set of arguments) or they can operate en masse (arrays of elements, object literals with multiple key-value pairs). As you might guess, this practice has been strongly inspired by the heavy use of overloading in jQuery, which was one of the driving factors behind its huge success. For example, let’s take $.style() . It can be used to set a single CSS property, on a single element, being a rather thin abstraction over element.style : $.style(element, &quot;top&quot;, rect.top); It can also be used to set a single CSS property on multiple elements: $.style($$(&quot;.popup&quot;), &quot;top&quot;, rect.top); It can also be used to set multiple properties on a single element: $.style(element, { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left ); Or to set multiple properties on multiple elements: $.style($$(&quot;.popup&quot;), { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left }); I’m a strong believer in overloading for handling both aggregate operations, as well as singular data. Supporting only aggregate operations would mean that developers have to pointlessly wrap single values in object literals or arrays. E.g. if $.style() only accepted arrays and object literals, our first example would be: $.style([element], {top: rect.top}); Not the end of the world, but certainly annoying and error-prone. Developers would often try setting the pair as separate arguments because it’s more natural, remember it doesn’t work, then adjust their code. The opposite situation is much worse. If $.style() only supported singular operations, our last example would be: let values = { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left }; for (let element of $$(&quot;.popup&quot;)) { for (let property in values) { $.style(element, property, values[property]); } } Yikes! You don’t need a library for that! Just using element.style and Object.assign() would have actually fared better here: for (let element of $$(&quot;.popup&quot;)) { Object.assign(element.style, { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left }); } $.style() is not unique here: any Bliss function that accepts a main target element (the function’s subject as it’s called in the Bliss docs) also accepts arrays of elements. Similarly, any Bliss function that accepts key-value pairs as separate arguments, also accepts object literals with multiple of them. In talks about API Design, I have presented this pattern (and overloading in general) as an instance of the Robustness principle in action: “Be liberal in what you accept” is good practice for designing any user interface, and APIs are no exception. An analog in GUI design would be bulk operations : imagine if e.g. you could only delete emails one by one? In JS, overloading is typically implemented by inspecting the types and number of a function’s arguments in the function, and branching accordingly. However, doing this individually on every function would get quite repetitive. Consider the following, very simplified implementation of $.style() with the overloading logic inlined: style(subject, ...args) { if (Array.isArray(subject)) { subject.forEach(e =&gt; style(e, ...args)); } else if ($.type(args[0]) === &quot;object&quot; &amp;&amp; args.length = 1) { for (let p in args[0]) { style(subject, p, args[0][p]); } } else { subject.style[args[0]] = args[1]; } return subject; } Note that the actual code of this function is only 1 line out of the 13 lines of code it contains. The other 12 are just boilerplate for overloading. What a nightmare for maintainability and readability! In Bliss v1, all functions were contained a single file, so they could be defined in their most singular version (one element, a single key-value pair as separate arguments etc), and the aggregate signatures could be automatically generated by looping over all defined functions and wrapping them accordingly. However, in Bliss v2, each function is defined in its own module, as a default export. There is also a module pulling them all together and adding them on $ , but people should be able to do things like: import style from &quot;https://v2.blissfuljs.com/src/dom/style.js&quot;; And style() would need to support its full functionality, not be some cut down version allowing only single elements and one property-value pair. What use would that be? This means that the overloading needs to happen in the module defining each function. It cannot happen via a loop in the index.js module. How can we do this and still keep our code maintainable, short, and easy to change? I explored several alternatives. (We are not going to discuss the implementation of overload() in each case below, but if you’re interested in the current one, it’s on Github . Do note that just like everything in Bliss v2, it’s subject to heavy change before release) Option 1: Inside each function export default function style(subject, ...args) { return overload(subject, args, (element, property, value) =&gt; { element.style[property] = value; }) } While this at first seems like the most natural way to abstract the inlined code we previously had, it’s the most verbose and hard to read. Furthermore, it adds extra code that needs to be executed every time the function is called and needs us to pass the current execution context through. It’s far better to go with a solution that takes the singular function as input, and gives you a modified function that just works. That’s what the next two options use. Option 2: Wrap with overload() export default overload(function style(element, property, value) { element.style[property] = value; }); Option 3: Overload at export function style(element, property, value) { element.style[property] = value; } export default overload(style); Options 2 and 3 are very similar. I was originally inclined to go with 2 to avoid typing the function name twice, but I eventually concluded that it made the code harder to read, so I went with option 3: Declaring the function, then overloading it &amp; exporting it. I wasn’t super happy with any of these options. Something inside me protested the idea of having to include even a line of boilerplate in every single module, and almost every Bliss function depending on another module. However, in the large scheme of things, I think this boilerplate is as minimal as it gets, and certainly beats the alternatives. Have you had to perform a transform on a number of different modules in your code? How did you abstract it away? 1 You can see the rewrite progress in the v2 branch on Github , and even use v2.blissfuljs.com to import modules from and experiment. Note that at the time of writing, all of the progress is in the code, the docs and tests are still all about v1.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Mass function overloading: why and how?</title>
  <link>https://lea.verou.me/2021/02/mass-function-overloading-why-and-how/</link>
  <pubDate>Sun, 17 May 2026 03:05:23 +0200</pubDate>
  <description>One of the things Ive been doing for the past few months (on and offmore off than on TBH) is rewriting Bliss to use ESM 1 . Since Bliss v1 was not using a modular architecture at all, this introduced some interesting challenges. Bliss is essentially a collection of helper functions. Most of these functions have a number of different signatures , to allow for more compact, readable code. The functions can be used for single things (one element, one set of arguments) or they can operate en masse (arrays of elements, object literals with multiple key-value pairs). As you might guess, this practice has been strongly inspired by the heavy use of overloading in jQuery, which was one of the driving factors behind its huge success. For example, lets take $.style() . It can be used to set a single CSS property, on a single element, being a rather thin abstraction over element.style : $.style(element, &quot;top&quot;, rect.top); It can also be used to set a single CSS property on multiple elements: $.style($$(&quot;.popup&quot;), &quot;top&quot;, rect.top); It can also be used to set multiple properties on a single element: $.style(element, { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left ); Or to set multiple properties on multiple elements: $.style($$(&quot;.popup&quot;), { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left }); Im a strong believer in overloading for handling both aggregate operations, as well as singular data. Supporting only aggregate operations would mean that developers have to pointlessly wrap single values in object literals or arrays. E.g. if $.style() only accepted arrays and object literals, our first example would be: $.style([element], {top: rect.top}); Not the end of the world, but certainly annoying and error-prone. Developers would often try setting the pair as separate arguments because its more natural, remember it doesnt work, then adjust their code. The opposite situation is much worse. If $.style() only supported singular operations, our last example would be: let values = { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left }; for (let element of $$(&quot;.popup&quot;)) { for (let property in values) { $.style(element, property, values[property]); } } Yikes! You dont need a library for that! Just using element.style and Object.assign() would have actually fared better here: for (let element of $$(&quot;.popup&quot;)) { Object.assign(element.style, { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left }); } $.style() is not unique here: any Bliss function that accepts a main target element (the functions subject as its called in the Bliss docs) also accepts arrays of elements. Similarly, any Bliss function that accepts key-value pairs as separate arguments, also accepts object literals with multiple of them. In talks about API Design, I have presented this pattern (and overloading in general) as an instance of the Robustness principle in action: Be liberal in what you accept is good practice for designing any user interface, and APIs are no exception. An analog in GUI design would be bulk operations : imagine if e.g. you could only delete emails one by one? In JS, overloading is typically implemented by inspecting the types and number of a functions arguments in the function, and branching accordingly. However, doing this individually on every function would get quite repetitive. Consider the following, very simplified implementation of $.style() with the overloading logic inlined: style(subject, ...args) { if (Array.isArray(subject)) { subject.forEach(e =&gt; style(e, ...args)); } else if ($.type(args[0]) === &quot;object&quot; &amp;&amp; args.length = 1) { for (let p in args[0]) { style(subject, p, args[0][p]); } } else { subject.style[args[0]] = args[1]; } return subject; } Note that the actual code of this function is only 1 line out of the 13 lines of code it contains. The other 12 are just boilerplate for overloading. What a nightmare for maintainability and readability! In Bliss v1, all functions were contained a single file, so they could be defined in their most singular version (one element, a single key-value pair as separate arguments etc), and the aggregate signatures could be automatically generated by looping over all defined functions and wrapping them accordingly. However, in Bliss v2, each function is defined in its own module, as a default export. There is also a module pulling them all together and adding them on $ , but people should be able to do things like: import style from &quot;https://v2.blissfuljs.com/src/dom/style.js&quot;; And style() would need to support its full functionality, not be some cut down version allowing only single elements and one property-value pair. What use would that be? This means that the overloading needs to happen in the module defining each function. It cannot happen via a loop in the index.js module. How can we do this and still keep our code maintainable, short, and easy to change? I explored several alternatives. (We are not going to discuss the implementation of overload() in each case below, but if youre interested in the current one, its on Github . Do note that just like everything in Bliss v2, its subject to heavy change before release) Option 1: Inside each function export default function style(subject, ...args) { return overload(subject, args, (element, property, value) =&gt; { element.style[property] = value; }) } While this at first seems like the most natural way to abstract the inlined code we previously had, its the most verbose and hard to read. Furthermore, it adds extra code that needs to be executed every time the function is called and needs us to pass the current execution context through. Its far better to go with a solution that takes the singular function as input, and gives you a modified function that just works. Thats what the next two options use. Option 2: Wrap with overload() export default overload(function style(element, property, value) { element.style[property] = value; }); Option 3: Overload at export function style(element, property, value) { element.style[property] = value; } export default overload(style); Options 2 and 3 are very similar. I was originally inclined to go with 2 to avoid typing the function name twice, but I eventually concluded that it made the code harder to read, so I went with option 3: Declaring the function, then overloading it &amp; exporting it. I wasnt super happy with any of these options. Something inside me protested the idea of having to include even a line of boilerplate in every single module, and almost every Bliss function depending on another module. However, in the large scheme of things, I think this boilerplate is as minimal as it gets, and certainly beats the alternatives. Have you had to perform a transform on a number of different modules in your code? How did you abstract it away? 1 You can see the rewrite progress in the v2 branch on Github , and even use v2.blissfuljs.com to import modules from and experiment. Note that at the time of writing, all of the progress is in the code, the docs and tests are still all about v1.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Writable getters</title>
  <link>https://lea.verou.me/2020/12/writable-getters/</link>
  <pubDate>Sun, 17 May 2026 03:05:22 +0200</pubDate>
  <description>Setters removing themselves are reminiscent of Ouroboros , the serpent eating its own tail, an ancient symbol. Media credit A pattern that has come up a few times in my code is the following: an object has a property which defaults to an expression based on its other properties unless it’s explicitly set, in which case it functions like a normal property. Essentially, the expression functions as a default value. Some examples of use cases: An object where a default id is generated from its name or title , but can also have custom ids. An object with information about a human, where name can be either specified explicitly or generated from firstName and lastName if not specified. An object with parameters for drawing an ellipse, where ry defaults to rx if not explicitly set. An object literal with date information, and a readable property which formats the date, but can be overwritten with a custom human-readable format. An object representing parts of a Github URL (e.g. username, repo, branch) with an apiCall property which can be either customized or generated from the parts (this is actually the example which prompted this blog post) Ok, so now that I convinced you about the utility of this pattern, how do we implement it in JS? Our first attempt may look something like this: let lea = { name: &quot;Lea Verou&quot;, get id() { return this.name.toLowerCase().replace(/\W+/g, &quot;-&quot;); } } Note: We are going to use object literals in this post for simplicity, but the same logic applies to variations using Object.create() , or a class Person of which lea is an instance. Our first attempt doesn’t quite work as you might expect: lea.id; // &quot;lea-verou&quot; lea.id = &quot;lv&quot;; lea.id; // Still &quot;lea-verou&quot;! Why does this happen? The reason is that the presence of the getter turns the property into an accessor , and thus, it cannot also hold data . If it doesn’t have a setter, then simply nothing happens when it is set. However, we can have a setter that, when invoked, deletes the accessor and replaces it with a data property : let lea = { name: &quot;Lea Verou&quot;, get id() { return this.name.toLowerCase().replace(/\W+/g, &quot;-&quot;); }, set id(v) { delete this.id; return this.id = v; } } Abstracting the pattern into a helper If we find ourselves needing this pattern in more than one places in our codebase, we could abstract it into a helper: function writableGetter(o, property, getter, options = {}) { Object.defineProperty(o, property, { get: getter, set (v) { delete this[property]; return this[property] = v; }, enumerable: true, configurable: true, ...options }); } Note that we used Object.defineProperty() here instead of the succinct get / set syntax. Not only is the former more convenient for augmenting pre-existing objects, but also it allows us to customize enumerability, while the latter just defaults to enumerable: true . We’d use the helper like this: let lea = {name: &quot;Lea Verou&quot;}; writableGetter(lea, &quot;id&quot;, function() { return this.name.toLowerCase().replace(/\W+/g, &quot;-&quot;); }, {enumerable: false}); Overwriting the getter with a different getter This works when we want to overwrite with a static value, but what if we want to overwrite with a different getter? For example, consider the date use case: what if we want to maintain a single source of truth for the date components and only overwrite the format, as a function, so that when the date components change, the formatted date updates accordingly? If we are confident that setting the property to an actual function value wouldn’t make sense, we could handle that case specially, and create a new getter instead of a data property: function writableGetter(o, property, getter, options = {}) { return Object.defineProperty(o, property, { get () { return getter.call(this); }, set (v) { if (typeof v === &quot;function&quot;) { getter = v; } else { delete this[property]; return this[property] = v; } }, enumerable: true, configurable: true, ...options }); } Do note that if we set the property to a static value, and try to set it to a function after that, it will just be a data property that creates a function, since we’ve deleted the accessor that handled functions specially. If that is a significant concern, we can maintain the accessor and just update the getter: function writableGetter(o, property, getter, options = {}) { return Object.defineProperty(o, property, { get () { return getter.call(this); }, set (v) { if (typeof v === &quot;function&quot;) { getter = v; } else { getter = () =&gt; v; } }, enumerable: true, configurable: true, ...options }); } Improving the DX of our helper While this was the most straightforward way to define a helper, it doesn’t feel very natural to use. Our object definition is now scattered in multiple places, and readability is poor. This is often the case when we start implementing before designing a UI. In this case, writing the helper is the implementation, and its calling code is effectively the UI. It’s always a good practice to start designing functions by writing a call to that function , as if a tireless elf working for us had already written the implementation of our dreams. So how would we prefer to write our object? I’d actually prefer to use the more readable get() syntax, and have everything in one place, then somehow convert that getter to a writable getter. Something like this: let lea = { name: &quot;Lea Verou&quot;, get id() { return this.name.toLowerCase().replace(/\W+/g, &quot;-&quot;); } } makeGetterWritable(lea, &quot;id&quot;, {enumerable: true}); Can we implement something like this? Of course. This is JS, we can do anything! The main idea is that we read back the descriptor our get syntax created, fiddle with it, then stuff it back in as a new property: function makeGetterWritable(o, property, options) { let d = Object.getOwnPropertyDescriptor(o, property); let getter = d.get; d.get = function() { return getter.call(this); }; d.set = function(v) { if (typeof v === &quot;function&quot;) { getter = v; } else { delete this[property]; return this[property] = v; } }; // Apply any overrides, e.g. enumerable Object.assign(d, options); // Redefine the property with the new descriptor Object.defineProperty(o, property, d) } Other mixed data-accessor properties While JS is very firm in its distinction of accessor properties and data properties, the reality is that we often need to combine the two in different ways, and conceptually it’s more of a data-accessor spectrum than two distinct categories. Here are a few more examples where the boundary between data property and accessor property is somewhat …murky: “Live” data properties : properties which execute code to produce side effects when they are get or set, but still hold data like a regular data property. This can be faked by having a helper that creates a hidden data property. This idea is the core of Bliss.live() . Lazy evaluation : Properties which are evaluated when they are first read (via a getter), then replace themselves with a regular data property. If they are set before they are read, they function exactly like a writable getter. This idea is the core of Bliss.lazy() . MDN mentions this pattern too. Note: Please don’t actually implement id/slug generation with name.toLowerCase().replace(/\W+/g, &quot;-&quot;) . That’s very simplistic, to keep examples short. It privileges English/ASCII over other languages and writing systems, and thus, should be avoided.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Writable getters</title>
  <link>https://lea.verou.me/2020/12/writable-getters/</link>
  <pubDate>Sun, 17 May 2026 03:05:22 +0200</pubDate>
  <description>Setters removing themselves are reminiscent of Ouroboros , the serpent eating its own tail, an ancient symbol. Media credit A pattern that has come up a few times in my code is the following: an object has a property which defaults to an expression based on its other properties unless its explicitly set, in which case it functions like a normal property. Essentially, the expression functions as a default value. Some examples of use cases: An object where a default id is generated from its name or title , but can also have custom ids. An object with information about a human, where name can be either specified explicitly or generated from firstName and lastName if not specified. An object with parameters for drawing an ellipse, where ry defaults to rx if not explicitly set. An object literal with date information, and a readable property which formats the date, but can be overwritten with a custom human-readable format. An object representing parts of a Github URL (e.g. username, repo, branch) with an apiCall property which can be either customized or generated from the parts (this is actually the example which prompted this blog post) Ok, so now that I convinced you about the utility of this pattern, how do we implement it in JS? Our first attempt may look something like this: let lea = { name: &quot;Lea Verou&quot;, get id() { return this.name.toLowerCase().replace(/\W+/g, &quot;-&quot;); } } Note: We are going to use object literals in this post for simplicity, but the same logic applies to variations using Object.create() , or a class Person of which lea is an instance. Our first attempt doesnt quite work as you might expect: lea.id; // &quot;lea-verou&quot; lea.id = &quot;lv&quot;; lea.id; // Still &quot;lea-verou&quot;! Why does this happen? The reason is that the presence of the getter turns the property into an accessor , and thus, it cannot also hold data . If it doesnt have a setter, then simply nothing happens when it is set. However, we can have a setter that, when invoked, deletes the accessor and replaces it with a data property : let lea = { name: &quot;Lea Verou&quot;, get id() { return this.name.toLowerCase().replace(/\W+/g, &quot;-&quot;); }, set id(v) { delete this.id; return this.id = v; } } Abstracting the pattern into a helper If we find ourselves needing this pattern in more than one places in our codebase, we could abstract it into a helper: function writableGetter(o, property, getter, options = {}) { Object.defineProperty(o, property, { get: getter, set (v) { delete this[property]; return this[property] = v; }, enumerable: true, configurable: true, ...options }); } Note that we used Object.defineProperty() here instead of the succinct get / set syntax. Not only is the former more convenient for augmenting pre-existing objects, but also it allows us to customize enumerability, while the latter just defaults to enumerable: true . Wed use the helper like this: let lea = {name: &quot;Lea Verou&quot;}; writableGetter(lea, &quot;id&quot;, function() { return this.name.toLowerCase().replace(/\W+/g, &quot;-&quot;); }, {enumerable: false}); Overwriting the getter with a different getter This works when we want to overwrite with a static value, but what if we want to overwrite with a different getter? For example, consider the date use case: what if we want to maintain a single source of truth for the date components and only overwrite the format, as a function, so that when the date components change, the formatted date updates accordingly? If we are confident that setting the property to an actual function value wouldnt make sense, we could handle that case specially, and create a new getter instead of a data property: function writableGetter(o, property, getter, options = {}) { return Object.defineProperty(o, property, { get () { return getter.call(this); }, set (v) { if (typeof v === &quot;function&quot;) { getter = v; } else { delete this[property]; return this[property] = v; } }, enumerable: true, configurable: true, ...options }); } Do note that if we set the property to a static value, and try to set it to a function after that, it will just be a data property that creates a function, since weve deleted the accessor that handled functions specially. If that is a significant concern, we can maintain the accessor and just update the getter: function writableGetter(o, property, getter, options = {}) { return Object.defineProperty(o, property, { get () { return getter.call(this); }, set (v) { if (typeof v === &quot;function&quot;) { getter = v; } else { getter = () =&gt; v; } }, enumerable: true, configurable: true, ...options }); } Improving the DX of our helper While this was the most straightforward way to define a helper, it doesnt feel very natural to use. Our object definition is now scattered in multiple places, and readability is poor. This is often the case when we start implementing before designing a UI. In this case, writing the helper is the implementation, and its calling code is effectively the UI. Its always a good practice to start designing functions by writing a call to that function , as if a tireless elf working for us had already written the implementation of our dreams. So how would we prefer to write our object? Id actually prefer to use the more readable get() syntax, and have everything in one place, then somehow convert that getter to a writable getter. Something like this: let lea = { name: &quot;Lea Verou&quot;, get id() { return this.name.toLowerCase().replace(/\W+/g, &quot;-&quot;); } } makeGetterWritable(lea, &quot;id&quot;, {enumerable: true}); Can we implement something like this? Of course. This is JS, we can do anything! The main idea is that we read back the descriptor our get syntax created, fiddle with it, then stuff it back in as a new property: function makeGetterWritable(o, property, options) { let d = Object.getOwnPropertyDescriptor(o, property); let getter = d.get; d.get = function() { return getter.call(this); }; d.set = function(v) { if (typeof v === &quot;function&quot;) { getter = v; } else { delete this[property]; return this[property] = v; } }; // Apply any overrides, e.g. enumerable Object.assign(d, options); // Redefine the property with the new descriptor Object.defineProperty(o, property, d) } Other mixed data-accessor properties While JS is very firm in its distinction of accessor properties and data properties, the reality is that we often need to combine the two in different ways, and conceptually its more of a data-accessor spectrum than two distinct categories. Here are a few more examples where the boundary between data property and accessor property is somewhat murky: Live data properties : properties which execute code to produce side effects when they are get or set, but still hold data like a regular data property. This can be faked by having a helper that creates a hidden data property. This idea is the core of Bliss.live() . Lazy evaluation : Properties which are evaluated when they are first read (via a getter), then replace themselves with a regular data property. If they are set before they are read, they function exactly like a writable getter. This idea is the core of Bliss.lazy() . MDN mentions this pattern too. Note: Please dont actually implement id/slug generation with name.toLowerCase().replace(/\W+/g, &quot;-&quot;) . Thats very simplistic, to keep examples short. It privileges English/ASCII over other languages and writing systems, and thus, should be avoided.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Position Statement for the 2020 W3C TAG Election</title>
  <link>https://lea.verou.me/2020/11/tag/</link>
  <pubDate>Sun, 17 May 2026 03:05:21 +0200</pubDate>
  <description>Update: I got elected!! Thank you so much to every W3C member organization who voted for me. 🙏🏼 Now on to making the Web better, alongside fellow TAG members! Context: I’m running for one of the four open seats in this year’s W3C TAG election . The W3C Technical Architecture Group (TAG) is the Working Group that ensures that Web Platform technologies are usable and follow consistent design principles, whether they are created inside or outside W3C. It advocates for the needs of everyone who uses the Web and everyone who works on the Web. If you work for a company that is a W3C Member , please consider encouraging your AC rep to vote for me! My candidate statement follows. Hi, I’m Lea Verou . Equally at home in Web development, the standards process, and programming language design, I bring a rarely-found cross-disciplinary understanding of the full stack of front-end development. I have a thorough and fundamental understanding of all the core technologies of the Web Platform: HTML, CSS, JS, DOM, and SVG. I bring the experience and perspective of having worked as a web designer &amp; developer in the trenches — not in large corporate systems, but on smaller, independent projects for clients, the type of projects that form the majority of the Web. I have started many open source projects, used on millions of websites, large and small. Some of my work has been incorporated in browser dev tools, and some has helped push CSS implementations forwards. However, unlike most web developers, I am experienced in working within W3C, both as a longtime member of the CSS Working Group , as well as a W3C Staff alumnus. This experience has given me a fuller grasp of Web technology development: not just the authoring side, but also the needs and constraints of implementation teams, the kinds of problems that tend to show up in our work, and the design principles we apply. I understand in practice how the standards process at W3C addresses the problems and weighs up the necessary compromises — from high-level design changes to minute details — to create successful standards for the Web. I have spent over six years doing PhD research at MIT on the intersection of programming language design and human-computer interaction. My research has been published in top-tier peer-reviewed academic venues. My strong usability background gives me the ability to identify API design pitfalls early on in the design process. In addition, I have been teaching web technologies for over a decade, both to professional web developers, through my numerous talks, workshops , and bestselling book , and as an instructor and course co-creator for MIT . This experience helps me to easily identify aspects of API design that can make a technology difficult to learn and conceptualize. If elected, I will work with the rest of the TAG to: Ensure that web technologies are not only powerful, but also learnable and approachable, with a smooth ease-of-use to complexity curve. Ensure that where possible, commonly needed functionality is available through approachable declarative HTML or CSS syntax and not solely through JS APIs. Work towards making the Web platform more extensible, to allow experienced developers to encapsulate complexity and make it available to novice authors, empowering the latter to create compelling content. Steps have been made in this direction with Web Components and the Houdini specifications, but there are still many gaps that need to be addressed. Record design principles that are often implicit knowledge in standards groups, passed on but never recorded. Explicit design principles help us keep technologies internally consistent, but also assist library developers who want to design APIs that are consistent with the Web Platform and feel like a natural extension of it. A great start has been made with the initial drafts of the Design Principles document , but there is still a lot to be done. Guide those seeking TAG review, some of whom may be new to the standards process, to improve their specifications. Having worn all these hats, I can understand and empathize with the needs of designers and developers, authors and implementers, practitioners and academics, putting me in a unique position to help ensure the Web Platform remains consistent, usable, and inclusive. I would like to thank Open JS Foundation and Bocoup for graciously funding my TAG-related travel, in the event that I am elected. Selected endorsements Tantek Çelik , Mozilla’s AC representative, longtime CSS WG member, and creator of many popular technologies : I have had the privilege of working with Lea in the CSS Working Group, and in the broader web development community for many years. Lea is an expert in the practical real-world-web technologies of the W3C, how they fit together, has put them into practice, has helped contribute to their evolution, directly in specs and in working groups. She’s also a passionate user &amp; developer advocate, both of which I think are excellent for the TAG. Source : https://lists.w3.org/Archives/Member/w3c-ac-forum/2021JanMar/0015.html Florian Rivoal , CSS WG Invited Expert and editor of several specifications , elected W3C AB member , ex-Opera: https://twitter.com/frivoal/status/1336857605063417856 Elika Etemad aka fantasai , prolific editor of dozens of W3C specifications, CSS WG member for over 16 years, and elected W3C AB member : One TPAC long ago, several members of the TAG on a recruiting spree went around asking people to run for the TAG. I personally turned them down for multiple reasons (including that I’m only a very poor substitute for David Baron), but it occurred to me recently that there was a candidate that they do need: Lea Verou. Lea is one of those elite developers whose technical expertise ranges across the entire Web platform. She doesn’t just use HTML, CSS, JS, and SVG, she pushes the boundaries of what they’re capable of. Meanwhile her authoring experience spans JS libraries to small site design to CSS+HTML print publications, giving her a personal appreciation of a wide variety of use cases. Unlike most other developers in her class, however, Lea also brings her experience working within W3C as a longtime member of the CSS Working Group. I’ve seen firsthand that she is capable of participating at the deep and excruciatingly detailed level that we operate here, and that her attention is not just on the feature at hand but also the system and its usability and coherence as a whole. She knows how the standards process works, how use cases and implementation constraints drive our design decisions, and how participation in the arcane discussions at W3C can make a real difference in the future usability of the Web. I’m recommending her for the TAG because she’s able to bring a perspective that is needed and frequently missing from our technical discussions which so often revolve around implementers, and because elevating her to TAG would give her both the opportunity and the empowerment to bring that perspective to more of our Web technology development here at W3C and beyond. Source : https://lists.w3.org/Archives/Member/w3c-ac-forum/2020OctDec/0055.html Bruce Lawson , Opera alumni, world renowned accessibility expert, speaker, author: https://twitter.com/brucel/status/1336260046691438594 Brian Kardell, AC representative for both Open JS Foundation and Igalia: The OpenJS Foundation is very pleased to nominate and offer our support for Lea Verou to the W3C TAG. We believe that she brings a fresh perspective, diverse background and several kinds of insight that would be exceptionally useful in the TAG’s work. Source : https://www.w3.org/2020/12/07-tag-nominations#lv Lea Verou is another easy choice for me. Lea brings a really diverse background, set of perspectives and skills to the table. She’s worked for the W3C, she’s a great communicator to developers (this is definitely a great skill in TAG whose outreach is important), she’s worked with small teams, produced a number of popular libraries and helped drive some interesting standards. The OpenJS Foundation was pleased to nominate her, but Frontiers and several others were also supportive. Lea also deserves “high marks”. Source : https://bkardell.com/blog/TAG-2021.html</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Position Statement for the 2020 W3C TAG Election</title>
  <link>https://lea.verou.me/2020/11/tag/</link>
  <pubDate>Sun, 17 May 2026 03:05:21 +0200</pubDate>
  <description>Update: I got elected!! Thank you so much to every W3C member organization who voted for me. Now on to making the Web better, alongside fellow TAG members! Context: Im running for one of the four open seats in this years W3C TAG election . The W3C Technical Architecture Group (TAG) is the Working Group that ensures that Web Platform technologies are usable and follow consistent design principles, whether they are created inside or outside W3C. It advocates for the needs of everyone who uses the Web and everyone who works on the Web. If you work for a company that is a W3C Member , please consider encouraging your AC rep to vote for me! My candidate statement follows. Hi, Im Lea Verou . Equally at home in Web development, the standards process, and programming language design, I bring a rarely-found cross-disciplinary understanding of the full stack of front-end development. I have a thorough and fundamental understanding of all the core technologies of the Web Platform: HTML, CSS, JS, DOM, and SVG. I bring the experience and perspective of having worked as a web designer &amp; developer in the trenches not in large corporate systems, but on smaller, independent projects for clients, the type of projects that form the majority of the Web. I have started many open source projects, used on millions of websites, large and small. Some of my work has been incorporated in browser dev tools, and some has helped push CSS implementations forwards. However, unlike most web developers, I am experienced in working within W3C, both as a longtime member of the CSS Working Group , as well as a W3C Staff alumnus. This experience has given me a fuller grasp of Web technology development: not just the authoring side, but also the needs and constraints of implementation teams, the kinds of problems that tend to show up in our work, and the design principles we apply. I understand in practice how the standards process at W3C addresses the problems and weighs up the necessary compromises from high-level design changes to minute details to create successful standards for the Web. I have spent over six years doing PhD research at MIT on the intersection of programming language design and human-computer interaction. My research has been published in top-tier peer-reviewed academic venues. My strong usability background gives me the ability to identify API design pitfalls early on in the design process. In addition, I have been teaching web technologies for over a decade, both to professional web developers, through my numerous talks, workshops , and bestselling book , and as an instructor and course co-creator for MIT . This experience helps me to easily identify aspects of API design that can make a technology difficult to learn and conceptualize. If elected, I will work with the rest of the TAG to: Ensure that web technologies are not only powerful, but also learnable and approachable, with a smooth ease-of-use to complexity curve. Ensure that where possible, commonly needed functionality is available through approachable declarative HTML or CSS syntax and not solely through JS APIs. Work towards making the Web platform more extensible, to allow experienced developers to encapsulate complexity and make it available to novice authors, empowering the latter to create compelling content. Steps have been made in this direction with Web Components and the Houdini specifications, but there are still many gaps that need to be addressed. Record design principles that are often implicit knowledge in standards groups, passed on but never recorded. Explicit design principles help us keep technologies internally consistent, but also assist library developers who want to design APIs that are consistent with the Web Platform and feel like a natural extension of it. A great start has been made with the initial drafts of the Design Principles document , but there is still a lot to be done. Guide those seeking TAG review, some of whom may be new to the standards process, to improve their specifications. Having worn all these hats, I can understand and empathize with the needs of designers and developers, authors and implementers, practitioners and academics, putting me in a unique position to help ensure the Web Platform remains consistent, usable, and inclusive. I would like to thank Open JS Foundation and Bocoup for graciously funding my TAG-related travel, in the event that I am elected. Selected endorsements Tantek Çelik , Mozillas AC representative, longtime CSS WG member, and creator of many popular technologies : I have had the privilege of working with Lea in the CSS Working Group, and in the broader web development community for many years. Lea is an expert in the practical real-world-web technologies of the W3C, how they fit together, has put them into practice, has helped contribute to their evolution, directly in specs and in working groups. Shes also a passionate user &amp; developer advocate, both of which I think are excellent for the TAG. Source : https://lists.w3.org/Archives/Member/w3c-ac-forum/2021JanMar/0015.html Florian Rivoal , CSS WG Invited Expert and editor of several specifications , elected W3C AB member , ex-Opera: https://twitter.com/frivoal/status/1336857605063417856 Elika Etemad aka fantasai , prolific editor of dozens of W3C specifications, CSS WG member for over 16 years, and elected W3C AB member : One TPAC long ago, several members of the TAG on a recruiting spree went around asking people to run for the TAG. I personally turned them down for multiple reasons (including that Im only a very poor substitute for David Baron), but it occurred to me recently that there was a candidate that they do need: Lea Verou. Lea is one of those elite developers whose technical expertise ranges across the entire Web platform. She doesnt just use HTML, CSS, JS, and SVG, she pushes the boundaries of what theyre capable of. Meanwhile her authoring experience spans JS libraries to small site design to CSS+HTML print publications, giving her a personal appreciation of a wide variety of use cases. Unlike most other developers in her class, however, Lea also brings her experience working within W3C as a longtime member of the CSS Working Group. Ive seen firsthand that she is capable of participating at the deep and excruciatingly detailed level that we operate here, and that her attention is not just on the feature at hand but also the system and its usability and coherence as a whole. She knows how the standards process works, how use cases and implementation constraints drive our design decisions, and how participation in the arcane discussions at W3C can make a real difference in the future usability of the Web. Im recommending her for the TAG because shes able to bring a perspective that is needed and frequently missing from our technical discussions which so often revolve around implementers, and because elevating her to TAG would give her both the opportunity and the empowerment to bring that perspective to more of our Web technology development here at W3C and beyond. Source : https://lists.w3.org/Archives/Member/w3c-ac-forum/2020OctDec/0055.html Bruce Lawson , Opera alumni, world renowned accessibility expert, speaker, author: https://twitter.com/brucel/status/1336260046691438594 Brian Kardell, AC representative for both Open JS Foundation and Igalia: The OpenJS Foundation is very pleased to nominate and offer our support for Lea Verou to the W3C TAG. We believe that she brings a fresh perspective, diverse background and several kinds of insight that would be exceptionally useful in the TAGs work. Source : https://www.w3.org/2020/12/07-tag-nominations#lv Lea Verou is another easy choice for me. Lea brings a really diverse background, set of perspectives and skills to the table. Shes worked for the W3C, shes a great communicator to developers (this is definitely a great skill in TAG whose outreach is important), shes worked with small teams, produced a number of popular libraries and helped drive some interesting standards. The OpenJS Foundation was pleased to nominate her, but Frontiers and several others were also supportive. Lea also deserves high marks. Source : https://bkardell.com/blog/TAG-2021.html</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The case for Weak Dependencies in JS</title>
  <link>https://lea.verou.me/2020/11/the-case-for-weak-dependencies-in-js/</link>
  <pubDate>Sun, 17 May 2026 03:05:20 +0200</pubDate>
  <description>Earlier today, I was briefly entertaining the idea of writing a library to wrap and enhance querySelectorAll in certain ways. I thought I’d rather not introduce a Parsel dependency out of the box, but only use it to parse selectors properly when it’s available, and use more crude regex when it’s not (which would cover most use cases for what I wanted to do). In the olden days, where every library introduced a global, I could just do: if (window.Parsel) { let ast = Parsel.parse(); // rewrite selector properly, with AST } else { // crude regex replace } However, with ESM, there doesn’t seem to be a way to detect whether a module is imported, without actually importing it yourself. I tweeted about this… https://twitter.com/LeaVerou/status/1329389035249422336 I thought this was a common paradigm, and everyone would understand why this was useful. However, I was surprised to find that most people were baffled about my use case. Most of them thought I was either talking about conditional imports, or error recovery after failed imports. I suspect it might be because my primary perspective for writing JS is that of a library author, where I do not control the host environment, whereas for most developers, their primary perspective is that of writing JS for a specific app or website. After Kyle Simpson asked me to elaborate about the use case, I figured a blog post was in order. The use case is essentially progressive enhancement (in fact, I toyed with the idea of titling this blog post “Progressively Enhanced JS” ). If library X is loaded already by other code, do a more elaborate thing and cover all the edge cases, otherwise do a more basic thing. It’s for dependencies that are not really depend encies, but more like nice-to-haves . We often see modules that do things really well, but use a ton of dependencies and add a lot of weight, even to the simplest of projects, because they need to cater to all the edge cases that we may not care about. We also see modules that are dependency free, but that’s because lots of things are implemented more crudely, or certain features are not there. This paradigm gives you the best of both worlds: Dependency free (or low dependency) modules, that can use what’s available to improve how they do things with zero additional impact . Using this paradigm, the size of these dependencies is not a concern , because they are optional peer dependencies , so one can pick the best library for the job without being affected by bundle size. Or even use multiple! One does not even need to pick one dependency for each thing, they can support bigger, more complete libraries when they’re available and fall back to micro-libraries when they are not. Some examples besides the one in the first paragraph: A Markdown to HTML converter that also syntax highlights blocks of code if Prism is present. Or it could even support multiple different highlighters! A code editor that uses Incrementable to make numbers incrementable via arrow keys, if it’s present A templating library that also uses Dragula to make items rearrangable via drag &amp; drop, if present A testing framework that uses Tippy for nice informational popups, when it’s available A code editor that shows code size (in KB) if a library to measure that is included. Same editor can also show gzipped code size if a gzip library is included. A UI library that uses a custom element if it’s available or the closest native one when it’s not (e.g. a fancy date picker vs ) when it isn’t. Or Awesomplete for autocomplete when it’s available, and fall back to a simple when it isn’t. Code that uses a date formatting library when one is already loaded, and falls back to Intl.DateTimeFormat when it’s not. This pattern can even be combined with conditional loading : e.g. we check for all known syntax highlighters and load Prism if none are present. To recap, some of the main benefits are: Performance: If you’re loading modules over the network HTTP requests are expensive. If you’re pre-bundling it increases bundle size. Even if code size is not a concern, runtime performance is affected if you take the slow but always correct path when you don’t need it and a more crude approach would satisfice. Choice: Instead of picking one library for the thing you need, you can support multiple. E.g. multiple syntax highlighters, multiple Markdown parsers etc. If a library is always needed to do the thing you want, you can load it conditionally, if none of the ones you support are loaded already. Are weak dependencies an antipattern? Since this article was posted, some of the feedback I got was along the lines of “Weak dependencies are an antipattern because they are unpredictable. What if you have included a library but don’t want another library to use it? You should instead use parameters to explicitly provide references to these libraries.” There are several counterpoints to make here. First, if weak dependencies are used well, they are only used to enhance the default/basic behavior, so it’s highly unlikely that you’d want to turn that off and fall back to the default behavior. Second, weak dependencies and parameter injection are not mutually exclusive. They can work together and complement each other, so that the weak dependencies provide sensible defaults that the parameters can then tweak further (or disable altogether). Only having parameter injection imposes a high upfront cognitive cost for using the library (see Convention over Configuration ). Good APIs make simple things easy and complex things possible. The common case is that if you’ve loaded e.g. a syntax highlighter, you’d want to use it to syntax highlight, and if you’ve loaded a parser, you’d prefer it over parsing with regexes. The obscure edge cases where you wouldn’t want to highlight or you want to provide a different parser can still be possible via parameters, but should not be the only way. Third, the end user-developer may not even be aware of all the libraries that are being loaded, so they may already have a library loaded for a certain task but not know about it. The weak dependencies pattern operates directly on which modules are loaded so it doesn’t suffer from this problem. How could this work with ESM? Some people (mostly fellow library authors) *did* understand what I was talking about, and expressed some ideas about how this would work. Idea 1: A global module loaded cache could be a low-level way to implement this, and something CJS supports out of the box apparently. https://twitter.com/WebReflection/status/1329396560694796290 Idea 2: A global registry where modules can register themselves on, either with an identifier, or a SHA hash Idea 3: An import.whenDefined(moduleURL) promise, though that makes it difficult to deal with the module not being present at all, which is the whole point. https://twitter.com/WebReflection/status/1329420308491677696 https://twitter.com/jcampbell\_05/status/1329413956474187777 Idea 4: Monitoring . The problem is that not all modules are loaded this way. https://twitter.com/getify/status/1329407281797222401 Idea 5: I was thinking of a function like import() that resolves with the module (same as a regular dynamic import) only when the module is already loaded, or rejects when it’s not (which can be caught). In fact, it could even use the same functional notation, with a second argument, like so: import(&quot;https://cool-library&quot;, {weak: true}); Nearly all of these proposals suffer from one of the following problems. Those that are URL based mean that only modules loaded from the same URL would be recognized. The same library loaded over a CDN vs locally would not be recognized as the same library. One way around this is to expose a list of URLs, like the first idea, and allow to listen for changes to it. Then these URLs can be inspected and those which might belong to the module we are looking for can be further inspected by dynamically importing and inspecting their exports (importing already imported modules is a pretty cheap operation, the browser does de-duplicate the request). Those that are identifier based , depend on the module to register itself with an identifier, so only modules that want to be exposed, will be. This is the closest to the old global situation, but would suffer in the transitional period until most modules use it. And of course, there is the potential for clashes. Though the API could take care of that, by essentially using a hashtable and adding all modules that register themselves with the same identifier under the same “bucket”. Code reading the registry would then be responsible for filtering.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The case for Weak Dependencies in JS</title>
  <link>https://lea.verou.me/2020/11/the-case-for-weak-dependencies-in-js/</link>
  <pubDate>Sun, 17 May 2026 03:05:20 +0200</pubDate>
  <description>Earlier today, I was briefly entertaining the idea of writing a library to wrap and enhance querySelectorAll in certain ways. I thought Id rather not introduce a Parsel dependency out of the box, but only use it to parse selectors properly when its available, and use more crude regex when its not (which would cover most use cases for what I wanted to do). In the olden days, where every library introduced a global, I could just do: if (window.Parsel) { let ast = Parsel.parse(); // rewrite selector properly, with AST } else { // crude regex replace } However, with ESM, there doesnt seem to be a way to detect whether a module is imported, without actually importing it yourself. I tweeted about this https://twitter.com/LeaVerou/status/1329389035249422336 I thought this was a common paradigm, and everyone would understand why this was useful. However, I was surprised to find that most people were baffled about my use case. Most of them thought I was either talking about conditional imports, or error recovery after failed imports. I suspect it might be because my primary perspective for writing JS is that of a library author, where I do not control the host environment, whereas for most developers, their primary perspective is that of writing JS for a specific app or website. After Kyle Simpson asked me to elaborate about the use case, I figured a blog post was in order. The use case is essentially progressive enhancement (in fact, I toyed with the idea of titling this blog post Progressively Enhanced JS ). If library X is loaded already by other code, do a more elaborate thing and cover all the edge cases, otherwise do a more basic thing. Its for dependencies that are not really depend encies, but more like nice-to-haves . We often see modules that do things really well, but use a ton of dependencies and add a lot of weight, even to the simplest of projects, because they need to cater to all the edge cases that we may not care about. We also see modules that are dependency free, but thats because lots of things are implemented more crudely, or certain features are not there. This paradigm gives you the best of both worlds: Dependency free (or low dependency) modules, that can use whats available to improve how they do things with zero additional impact . Using this paradigm, the size of these dependencies is not a concern , because they are optional peer dependencies , so one can pick the best library for the job without being affected by bundle size. Or even use multiple! One does not even need to pick one dependency for each thing, they can support bigger, more complete libraries when theyre available and fall back to micro-libraries when they are not. Some examples besides the one in the first paragraph: A Markdown to HTML converter that also syntax highlights blocks of code if Prism is present. Or it could even support multiple different highlighters! A code editor that uses Incrementable to make numbers incrementable via arrow keys, if its present A templating library that also uses Dragula to make items rearrangable via drag &amp; drop, if present A testing framework that uses Tippy for nice informational popups, when its available A code editor that shows code size (in KB) if a library to measure that is included. Same editor can also show gzipped code size if a gzip library is included. A UI library that uses a custom element if its available or the closest native one when its not (e.g. a fancy date picker vs ) when it isnt. Or Awesomplete for autocomplete when its available, and fall back to a simple when it isnt. Code that uses a date formatting library when one is already loaded, and falls back to Intl.DateTimeFormat when its not. This pattern can even be combined with conditional loading : e.g. we check for all known syntax highlighters and load Prism if none are present. To recap, some of the main benefits are: Performance: If youre loading modules over the network HTTP requests are expensive. If youre pre-bundling it increases bundle size. Even if code size is not a concern, runtime performance is affected if you take the slow but always correct path when you dont need it and a more crude approach would satisfice. Choice: Instead of picking one library for the thing you need, you can support multiple. E.g. multiple syntax highlighters, multiple Markdown parsers etc. If a library is always needed to do the thing you want, you can load it conditionally, if none of the ones you support are loaded already. Are weak dependencies an antipattern? Since this article was posted, some of the feedback I got was along the lines of Weak dependencies are an antipattern because they are unpredictable. What if you have included a library but dont want another library to use it? You should instead use parameters to explicitly provide references to these libraries. There are several counterpoints to make here. First, if weak dependencies are used well, they are only used to enhance the default/basic behavior, so its highly unlikely that youd want to turn that off and fall back to the default behavior. Second, weak dependencies and parameter injection are not mutually exclusive. They can work together and complement each other, so that the weak dependencies provide sensible defaults that the parameters can then tweak further (or disable altogether). Only having parameter injection imposes a high upfront cognitive cost for using the library (see Convention over Configuration ). Good APIs make simple things easy and complex things possible. The common case is that if youve loaded e.g. a syntax highlighter, youd want to use it to syntax highlight, and if youve loaded a parser, youd prefer it over parsing with regexes. The obscure edge cases where you wouldnt want to highlight or you want to provide a different parser can still be possible via parameters, but should not be the only way. Third, the end user-developer may not even be aware of all the libraries that are being loaded, so they may already have a library loaded for a certain task but not know about it. The weak dependencies pattern operates directly on which modules are loaded so it doesnt suffer from this problem. How could this work with ESM? Some people (mostly fellow library authors) *did* understand what I was talking about, and expressed some ideas about how this would work. Idea 1: A global module loaded cache could be a low-level way to implement this, and something CJS supports out of the box apparently. https://twitter.com/WebReflection/status/1329396560694796290 Idea 2: A global registry where modules can register themselves on, either with an identifier, or a SHA hash Idea 3: An import.whenDefined(moduleURL) promise, though that makes it difficult to deal with the module not being present at all, which is the whole point. https://twitter.com/WebReflection/status/1329420308491677696 https://twitter.com/jcampbell\_05/status/1329413956474187777 Idea 4: Monitoring . The problem is that not all modules are loaded this way. https://twitter.com/getify/status/1329407281797222401 Idea 5: I was thinking of a function like import() that resolves with the module (same as a regular dynamic import) only when the module is already loaded, or rejects when its not (which can be caught). In fact, it could even use the same functional notation, with a second argument, like so: import(&quot;https://cool-library&quot;, {weak: true}); Nearly all of these proposals suffer from one of the following problems. Those that are URL based mean that only modules loaded from the same URL would be recognized. The same library loaded over a CDN vs locally would not be recognized as the same library. One way around this is to expose a list of URLs, like the first idea, and allow to listen for changes to it. Then these URLs can be inspected and those which might belong to the module we are looking for can be further inspected by dynamically importing and inspecting their exports (importing already imported modules is a pretty cheap operation, the browser does de-duplicate the request). Those that are identifier based , depend on the module to register itself with an identifier, so only modules that want to be exposed, will be. This is the closest to the old global situation, but would suffer in the transitional period until most modules use it. And of course, there is the potential for clashes. Though the API could take care of that, by essentially using a hashtable and adding all modules that register themselves with the same identifier under the same bucket. Code reading the registry would then be responsible for filtering.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Simple pie charts with fallback, today</title>
  <link>https://lea.verou.me/2020/11/simple-pie-charts-with-fallback-today/</link>
  <pubDate>Sun, 17 May 2026 03:05:19 +0200</pubDate>
  <description>Five years ago, I had written this extensive Smashing Magazine article detailing multiple different methods for creating simple pie charts, either with clever use of transforms and pseudo-elements, or with SVG stroke-dasharray . In the end, I mentioned creating pie charts with conic gradients, as a future technique. It was actually a writeup of my “The Missing Slice” talk , and an excerpt of my CSS Secrets book , which had just been published. I was reminded of this article today by someone on Twitter: https://twitter.com/sam\_kent\_/status/1326805431390531584 I suggested conic gradients, since they are now supported in &gt;87% of users’ browsers , but he needed to support IE11. He suggested using my polyfill from back then, but this is not a very good idea today. https://twitter.com/fcorradini/status/1326880827825905665 Indeed, unless you really need to display conic gradients, even I would not recommend using the polyfill on a production facing site. It requires -prefix-free , which re-fetches (albeit from cache) your entire CSS and sticks it in a element, with no sourcemaps since those were not a thing back when -prefix-free was written. If you’re already using -prefix-free, the polyfill is great, but if not, it’s way too heavy a dependency. Pie charts with fallback (modern browsers) Instead, what I would recommend is graceful degradation, i.e. to use the same color stops, but in a linear gradient. We can use @supports and have quite an elaborate progress bar fallback. For example, take a look at this 40% pie chart: .pie { height: 20px; background: linear-gradient(to right, deeppink 40%, transparent 0); background-color: gold; } @supports (background: conic-gradient(white, black)) { .pie { width: 200px; height: 200px; background-image: conic-gradient(deeppink 40%, transparent 0); border-radius: 50%; } } This is what it looks like in Firefox 82 (conic gradients are scheduled to ship unflagged in Firefox 83) or IE11: Note that because @supports is only used for the pie and not the fallback, the lack of IE11 support for it doesn’t affect us one iota. If relatively modern browsers are all we care about, we could even use CSS variables for the percentage and the color stops, to avoid duplication, and to be able to set the percentage from the markup: .pie { height: 20px; --stops: deeppink var(--p, 0%), transparent 0; background: linear-gradient(to right, var(--stops)); background-color: gold; } @supports (background: conic-gradient(white, black)) { .pie { width: 200px; height: 200px; background-image: conic-gradient(var(--stops)); border-radius: 50%; } } You can use a similar approach for 3 or more segments, or for a vertical bar. One issue with this approach is that our layout needs to work well with two charts of completely different proportions. To avoid that, we could just use a square: .pie { width: 200px; height: 200px; background: linear-gradient(to right, deeppink 40%, transparent 0) gold; } @supports (background: conic-gradient(white, black)) { .pie { background-image: conic-gradient(deeppink 40%, transparent 0); border-radius: 50%; } } which produces this in IE11: Granted, a square progress bar is not the same, but it can still convey the same relationship and is easier to design a layout around it since it always has the same aspect ratio. Why not use radial gradients? You might be wondering, why not just use a radial gradient, which could use the same dimensions and rounding. Something like this: There are two problems with this. The first one may be obvious: Horizontal or vertical bars are common for showing the proportional difference between two amounts, albeit less good than a pie chart because it’s harder to compare with 50% at a glance ( yes Tufte, pie charts can be better for some things! ). Such circular graphs are very uncommon. And for good reason: Drawn naively (e.g. in our case if the radius of the pink circle is 40% of the radius of the yellow circle), their areas do not have the relative relationship we want to depict. Why is that? Let r be the radius of the yellow circle. As we know from middle school, the area of the entire circle is π_r_², so the area of the yellow ring is π_r_² - (area of pink circle). The area of the pink circle is π(0.4_r_)² = 0.16π_r_². Therefore, the area of the yellow ring is π_r_² - 0.16π_r_² = 0.84π_r_² and their relative ratio is 0.16π_r_² / 0.84π_r_² = 0.16 / 0.84 ≅ 0.19 which is a far cry from the 40/60 (≅ 0.67) we were looking for! Instead, if we wanted to draw a similar visualization to depict the correct relationship, we need to start from the ratio and work our way backwards. Let r be the radius of the yellow circle and kr the radius of the pink circle. Their ratio is π( kr )² / (π_r_² - π( kr )²) = 4/6 ⇒ _k_² / (1 - _k_²) = 4/6 ⇒ (1 - _k_²) / _k_² = 6/4 ⇒ 1/_k_² - 1 = 6/4 ⇒ 1/_k_² = 10/4 ⇒ k = 2 / sqrt(10) ≅ .632 Therefore, the radius of the pink circle should be around 63.2% of the radius of the yellow circle, and a more correct chart would look like this: In the general case where the pink circle is depicting the percentage p , we’d want the radius of the pink circle to be sqrt(1 / p ) the size of the yellow circle. That’s a fair bit of calculations that we can’t yet automate (though sqrt() is coming !). Moral of the story: use a bar as your fallback!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Simple pie charts with fallback, today</title>
  <link>https://lea.verou.me/2020/11/simple-pie-charts-with-fallback-today/</link>
  <pubDate>Sun, 17 May 2026 03:05:19 +0200</pubDate>
  <description>Five years ago, I had written this extensive Smashing Magazine article detailing multiple different methods for creating simple pie charts, either with clever use of transforms and pseudo-elements, or with SVG stroke-dasharray . In the end, I mentioned creating pie charts with conic gradients, as a future technique. It was actually a writeup of my The Missing Slice talk , and an excerpt of my CSS Secrets book , which had just been published. I was reminded of this article today by someone on Twitter: https://twitter.com/sam\_kent\_/status/1326805431390531584 I suggested conic gradients, since they are now supported in &gt;87% of users browsers , but he needed to support IE11. He suggested using my polyfill from back then, but this is not a very good idea today. https://twitter.com/fcorradini/status/1326880827825905665 Indeed, unless you really need to display conic gradients, even I would not recommend using the polyfill on a production facing site. It requires -prefix-free , which re-fetches (albeit from cache) your entire CSS and sticks it in a element, with no sourcemaps since those were not a thing back when -prefix-free was written. If youre already using -prefix-free, the polyfill is great, but if not, its way too heavy a dependency. Pie charts with fallback (modern browsers) Instead, what I would recommend is graceful degradation, i.e. to use the same color stops, but in a linear gradient. We can use @supports and have quite an elaborate progress bar fallback. For example, take a look at this 40% pie chart: .pie { height: 20px; background: linear-gradient(to right, deeppink 40%, transparent 0); background-color: gold; } @supports (background: conic-gradient(white, black)) { .pie { width: 200px; height: 200px; background-image: conic-gradient(deeppink 40%, transparent 0); border-radius: 50%; } } This is what it looks like in Firefox 82 (conic gradients are scheduled to ship unflagged in Firefox 83) or IE11: Note that because @supports is only used for the pie and not the fallback, the lack of IE11 support for it doesnt affect us one iota. If relatively modern browsers are all we care about, we could even use CSS variables for the percentage and the color stops, to avoid duplication, and to be able to set the percentage from the markup: .pie { height: 20px; --stops: deeppink var(--p, 0%), transparent 0; background: linear-gradient(to right, var(--stops)); background-color: gold; } @supports (background: conic-gradient(white, black)) { .pie { width: 200px; height: 200px; background-image: conic-gradient(var(--stops)); border-radius: 50%; } } You can use a similar approach for 3 or more segments, or for a vertical bar. One issue with this approach is that our layout needs to work well with two charts of completely different proportions. To avoid that, we could just use a square: .pie { width: 200px; height: 200px; background: linear-gradient(to right, deeppink 40%, transparent 0) gold; } @supports (background: conic-gradient(white, black)) { .pie { background-image: conic-gradient(deeppink 40%, transparent 0); border-radius: 50%; } } which produces this in IE11: Granted, a square progress bar is not the same, but it can still convey the same relationship and is easier to design a layout around it since it always has the same aspect ratio. Why not use radial gradients? You might be wondering, why not just use a radial gradient, which could use the same dimensions and rounding. Something like this: There are two problems with this. The first one may be obvious: Horizontal or vertical bars are common for showing the proportional difference between two amounts, albeit less good than a pie chart because its harder to compare with 50% at a glance ( yes Tufte, pie charts can be better for some things! ). Such circular graphs are very uncommon. And for good reason: Drawn naively (e.g. in our case if the radius of the pink circle is 40% of the radius of the yellow circle), their areas do not have the relative relationship we want to depict. Why is that? Let r be the radius of the yellow circle. As we know from middle school, the area of the entire circle is _r_, so the area of the yellow ring is _r_ - (area of pink circle). The area of the pink circle is (0.4_r_) = 0.16_r_. Therefore, the area of the yellow ring is _r_ - 0.16_r_ = 0.84_r_ and their relative ratio is 0.16_r_ / 0.84_r_ = 0.16 / 0.84 0.19 which is a far cry from the 40/60 ( 0.67) we were looking for! Instead, if we wanted to draw a similar visualization to depict the correct relationship, we need to start from the ratio and work our way backwards. Let r be the radius of the yellow circle and kr the radius of the pink circle. Their ratio is ( kr ) / (_r_ - ( kr )) = 4/6 _k_ / (1 - _k_) = 4/6 (1 - _k_) / _k_ = 6/4 1/_k_ - 1 = 6/4 1/_k_ = 10/4 k = 2 / sqrt(10) .632 Therefore, the radius of the pink circle should be around 63.2% of the radius of the yellow circle, and a more correct chart would look like this: In the general case where the pink circle is depicting the percentage p , wed want the radius of the pink circle to be sqrt(1 / p ) the size of the yellow circle. Thats a fair bit of calculations that we cant yet automate (though sqrt() is coming !). Moral of the story: use a bar as your fallback!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The -​-var: ; hack to toggle multiple values with one custom property</title>
  <link>https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/</link>
  <pubDate>Sun, 17 May 2026 03:05:18 +0200</pubDate>
  <description>What if I told you you could use a single property value to turn multiple different values on and off across multiple different properties and even across multiple CSS rules? What if I told you you could turn this flat button into a glossy skeuomorphic button by just tweaking one custom property --is-raised , and that would set its border, background image, box and text shadows in one fell swoop? How , you may ask? The crux of this technique is this: There are two custom property values that work almost everywhere there is a var() call with a fallback. The more obvious one that you probably already know is the initial value, which makes the property just apply its fallback. So, in the following code: background: var(--foo, linear-gradient(white, transparent)) hsl(220 10% 50%); border: 1px solid var(--foo, rgb(0 0 0 / .1)); color: rgb(0 0 0 var(--foo, / .8)); We can set --foo to initial to enable these “fallbacks” and append these values to the property value, adding a gradient, setting a border-color, and making the text color translucent in one go. But what to do when we want to turn these values off ? Any non-initial value for --foo (that doesn’t create cycles) should work. But is there one that works in all three declarations? It turns out there is another value that works everywhere, in every property a var() reference is present, and you’d likely never guess what it is (unless you have watched any of my CSS variable talks and have a good memory for passing mentions of things). Intrigued? It’s whitespace! Whitespace is significant in a custom property. When you write something like this: --foo: ; This is not an invalid declaration. This is a declaration where the value of --foo is literally one space character. However, whitespace is valid in every CSS property value, everywhere a var() is allowed, and does not affect its computed value in any way. So, we can just set our property to one space (or even a comment) and not affect any other value present in the declaration. E.g. this: --foo: ; background: var(--foo, linear-gradient(white, transparent)) hsl(220 10% 50%); produces the same result as: background: hsl(220 10% 50%); We can take advantage of this to essentially turn var() into a single-clause if() function and conditionally append values based on a single custom property. As a proof of concept, here is the two button demo refactored using this approach: Limitations I originally envisioned this as a building block for a technique horrible hack to enable “mixins” in the browser, since @apply is now defunct . However, the big limitation is that this only works for appending values to existing values — or setting a property to either a whole value or initial . There is no way to say “the background should be red if --foo is set and white otherwise”. Some such conditionals can be emulated with clever use of appending, but not most. And of course there’s a certain readability issue: --foo: ; looks like a mistake and --foo: initial looks pretty weird, unless you’re aware of this technique. However, that can easily be solved with comments. Or even constants: :root { --ON: initial; --OFF: ; } button { --is-raised: var(--OFF); /* ... */ } #foo { --is-raised: var(--ON); } Also do note that eventually we will get a proper if() and won’t need such horrible hacks to emulate it, discussions are already underway [ w3c/csswg-drafts#5009 w3c/csswg-drafts#4731 ]. So what do you think? Horrible hack, useful technique, or both? 😀 Prior art Turns out this was independently discovered by two people before me: First, the brilliant Ana Tudor circa 2017 Then James0x57 in April 2020 And it was called “space toggle hack” in case you want to google it!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The --var: ; hack to toggle multiple values with one custom property</title>
  <link>https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/</link>
  <pubDate>Sun, 17 May 2026 03:05:18 +0200</pubDate>
  <description>What if I told you you could use a single property value to turn multiple different values on and off across multiple different properties and even across multiple CSS rules? What if I told you you could turn this flat button into a glossy skeuomorphic button by just tweaking one custom property --is-raised , and that would set its border, background image, box and text shadows in one fell swoop? How , you may ask? The crux of this technique is this: There are two custom property values that work almost everywhere there is a var() call with a fallback. The more obvious one that you probably already know is the initial value, which makes the property just apply its fallback. So, in the following code: background: var(--foo, linear-gradient(white, transparent)) hsl(220 10% 50%); border: 1px solid var(--foo, rgb(0 0 0 / .1)); color: rgb(0 0 0 var(--foo, / .8)); We can set --foo to initial to enable these fallbacks and append these values to the property value, adding a gradient, setting a border-color, and making the text color translucent in one go. But what to do when we want to turn these values off ? Any non-initial value for --foo (that doesnt create cycles) should work. But is there one that works in all three declarations? It turns out there is another value that works everywhere, in every property a var() reference is present, and youd likely never guess what it is (unless you have watched any of my CSS variable talks and have a good memory for passing mentions of things). Intrigued? Its whitespace! Whitespace is significant in a custom property. When you write something like this: --foo: ; This is not an invalid declaration. This is a declaration where the value of --foo is literally one space character. However, whitespace is valid in every CSS property value, everywhere a var() is allowed, and does not affect its computed value in any way. So, we can just set our property to one space (or even a comment) and not affect any other value present in the declaration. E.g. this: --foo: ; background: var(--foo, linear-gradient(white, transparent)) hsl(220 10% 50%); produces the same result as: background: hsl(220 10% 50%); We can take advantage of this to essentially turn var() into a single-clause if() function and conditionally append values based on a single custom property. As a proof of concept, here is the two button demo refactored using this approach: Limitations I originally envisioned this as a building block for a technique horrible hack to enable mixins in the browser, since @apply is now defunct . However, the big limitation is that this only works for appending values to existing values or setting a property to either a whole value or initial . There is no way to say the background should be red if --foo is set and white otherwise. Some such conditionals can be emulated with clever use of appending, but not most. And of course theres a certain readability issue: --foo: ; looks like a mistake and --foo: initial looks pretty weird, unless youre aware of this technique. However, that can easily be solved with comments. Or even constants: :root { --ON: initial; --OFF: ; } button { --is-raised: var(--OFF); /* ... */ } #foo { --is-raised: var(--ON); } Also do note that eventually we will get a proper if() and wont need such horrible hacks to emulate it, discussions are already underway [ w3c/csswg-drafts#5009 w3c/csswg-drafts#4731 ]. So what do you think? Horrible hack, useful technique, or both? Prior art Turns out this was independently discovered by two people before me: First, the brilliant Ana Tudor circa 2017 Then James0x57 in April 2020 And it was called space toggle hack in case you want to google it!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The failed promise of Web Components</title>
  <link>https://lea.verou.me/2020/09/the-failed-promise-of-web-components/</link>
  <pubDate>Sun, 17 May 2026 03:05:17 +0200</pubDate>
  <description>Web Components had so much potential to empower HTML to do more, and make web development more accessible to non-programmers and easier for programmers. Remember how exciting it was every time we got new shiny HTML elements that actually do stuff ? Remember how exciting it was to be able to do sliders, color pickers, dialogs, disclosure widgets straight in the HTML, without having to include any widget libraries? The promise of Web Components was that we’d get this convenience, but for a much wider range of HTML elements, developed much faster, as nobody needs to wait for the full spec + implementation process. We’d just include a script, and boom, we have more elements at our disposal! Or, that was the idea. Somewhere along the way, the space got flooded by JS frameworks aficionados, who revel in complex APIs, overengineered build processes and dependency graphs that look like the roots of a banyan tree. This is what the roots of a Banyan tree look like. Photo by David Stanley on Flickr (CC-BY) . Perusing the components on webcomponents.org fills me with anxiety, and I’m perfectly comfortable writing JS — I write JS for a living! What hope do those who can’t write JS have? Using a custom element from the directory often needs to be preceded by a ritual of npm flugelhorn, import clownshoes, build quux, all completely unapologetically because “here is my truckload of dependencies, yeah, what”. Many steps are even omitted, likely because they are “obvious”. Often, you wade through the maze only to find the component doesn’t work anymore, or is not fit for your purpose. Besides setup, the main problem is that HTML is not treated with the appropriate respect in the design of these components. They are not designed as closely as possible to standard HTML elements, but expect JS to be written for them to do anything. HTML is simply treated as a shorthand, or worse, as merely a marker to indicate where the element goes in the DOM, with all parameters passed in via JS. I recall a wonderful talk by Jeremy Keith a few years ago about this very phenomenon, where he discussed this e-shop Web components demo by Google , which is the poster child of this practice. These are the entire contents of its element: SHOP window.performance&amp;&amp;performance.mark&amp;&amp;performance.mark(&quot;index.html&quot;); If this is how Google is leading the way, how can we hope for contributors to design components that follow established HTML conventions? Jeremy criticized this practice from the aspect of backwards compatibility: when JS is broken or not enabled, or the browser doesn’t support Web Components, the entire website is blank. While this is indeed a serious concern, my primary concern is one of usability : HTML is a lower barrier to entry language . Far more people can write HTML than JS. Even for those who do eventually write JS, it often comes after spending years writing HTML &amp; CSS. If components are designed in a way that requires JS, this excludes thousands of people from using them. And even for those who can write JS, HTML is often easier: you don’t see many people rolling their own sliders or using JS-based ones once became widely supported, right? Even when JS is unavoidable, it’s not black and white. A well designed HTML element can reduce the amount and complexity of JS needed to a minimum. Think of the element: it usually does require *some* JS, but it’s usually rather simple JS. Similarly, the element is perfectly usable just by writing HTML, and has a comprehensive JS API for anyone who wants to do fancy custom things. The other day I was looking for a simple, dependency free, tabs component. You know, the canonical example of something that is easy to do with Web Components, the example 50% of tutorials mention. I didn’t even care what it looked like, it was for a testing interface. I just wanted something that is small and works like a normal HTML element. Yet, it proved so hard I ended up writing my own! Can we fix this? I’m not sure if this is a design issue, or a documentation issue. Perhaps for many of these web components, there are easier ways to use them. Perhaps there are vanilla web components out there that I just can’t find. Perhaps I’m looking in the wrong place and there is another directory somewhere with different goals and a different target audience. But if not, and if I’m not alone in feeling this way, we need a directory of web components with strict inclusion criteria: Plug and play. No dependencies, no setup beyond including one tag. If a dependency is absolutely needed (e.g. in a map component it doesn’t make sense to draw your own maps), the component loads it automatically if it’s not already loaded. Syntax and API follows conventions established by built-in HTML elements and anything that can be done without the component user writing JS, is doable without JS, per the W3C principle of least power . Accessible by default via sensible ARIA defaults, just like normal HTML elements. Themable via ::part() , selective inheritance and custom properties. Very minimal style by default. Normal CSS properties should just “work” to the the extent possible. Only one component of a given type in the directory, that is flexible and extensible and continuously iterated on and improved by the community. Not 30 different sliders and 15 different tabs that users have to wade through. No branding, no silos of “component libraries”. Only elements that are designed as closely as possible to what a browser would implement in every way the current technology allows. I would be up for working on this if others feel the same way, since that is not a project for one person to tackle. Who’s with me? UPDATE: Wow this post blew up! Thank you all for your interest in participating in a potential future effort. I’m currently talking to stakeholders of some of the existing efforts to see if there are any potential collaborations before I go off and create a new one. Follow me on Twitter to hear about the outcome !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The failed promise of Web Components</title>
  <link>https://lea.verou.me/2020/09/the-failed-promise-of-web-components/</link>
  <pubDate>Sun, 17 May 2026 03:05:17 +0200</pubDate>
  <description>Web Components had so much potential to empower HTML to do more, and make web development more accessible to non-programmers and easier for programmers. Remember how exciting it was every time we got new shiny HTML elements that actually do stuff ? Remember how exciting it was to be able to do sliders, color pickers, dialogs, disclosure widgets straight in the HTML, without having to include any widget libraries? The promise of Web Components was that wed get this convenience, but for a much wider range of HTML elements, developed much faster, as nobody needs to wait for the full spec + implementation process. Wed just include a script, and boom, we have more elements at our disposal! Or, that was the idea. Somewhere along the way, the space got flooded by JS frameworks aficionados, who revel in complex APIs, overengineered build processes and dependency graphs that look like the roots of a banyan tree. This is what the roots of a Banyan tree look like. Photo by David Stanley on Flickr (CC-BY) . Perusing the components on webcomponents.org fills me with anxiety, and Im perfectly comfortable writing JS I write JS for a living! What hope do those who cant write JS have? Using a custom element from the directory often needs to be preceded by a ritual of npm flugelhorn, import clownshoes, build quux, all completely unapologetically because here is my truckload of dependencies, yeah, what. Many steps are even omitted, likely because they are obvious. Often, you wade through the maze only to find the component doesnt work anymore, or is not fit for your purpose. Besides setup, the main problem is that HTML is not treated with the appropriate respect in the design of these components. They are not designed as closely as possible to standard HTML elements, but expect JS to be written for them to do anything. HTML is simply treated as a shorthand, or worse, as merely a marker to indicate where the element goes in the DOM, with all parameters passed in via JS. I recall a wonderful talk by Jeremy Keith a few years ago about this very phenomenon, where he discussed this e-shop Web components demo by Google , which is the poster child of this practice. These are the entire contents of its element: SHOP window.performance&amp;&amp;performance.mark&amp;&amp;performance.mark(&quot;index.html&quot;); If this is how Google is leading the way, how can we hope for contributors to design components that follow established HTML conventions? Jeremy criticized this practice from the aspect of backwards compatibility: when JS is broken or not enabled, or the browser doesnt support Web Components, the entire website is blank. While this is indeed a serious concern, my primary concern is one of usability : HTML is a lower barrier to entry language . Far more people can write HTML than JS. Even for those who do eventually write JS, it often comes after spending years writing HTML &amp; CSS. If components are designed in a way that requires JS, this excludes thousands of people from using them. And even for those who can write JS, HTML is often easier: you dont see many people rolling their own sliders or using JS-based ones once became widely supported, right? Even when JS is unavoidable, its not black and white. A well designed HTML element can reduce the amount and complexity of JS needed to a minimum. Think of the element: it usually does require *some* JS, but its usually rather simple JS. Similarly, the element is perfectly usable just by writing HTML, and has a comprehensive JS API for anyone who wants to do fancy custom things. The other day I was looking for a simple, dependency free, tabs component. You know, the canonical example of something that is easy to do with Web Components, the example 50% of tutorials mention. I didnt even care what it looked like, it was for a testing interface. I just wanted something that is small and works like a normal HTML element. Yet, it proved so hard I ended up writing my own! Can we fix this? Im not sure if this is a design issue, or a documentation issue. Perhaps for many of these web components, there are easier ways to use them. Perhaps there are vanilla web components out there that I just cant find. Perhaps Im looking in the wrong place and there is another directory somewhere with different goals and a different target audience. But if not, and if Im not alone in feeling this way, we need a directory of web components with strict inclusion criteria: Plug and play. No dependencies, no setup beyond including one tag. If a dependency is absolutely needed (e.g. in a map component it doesnt make sense to draw your own maps), the component loads it automatically if its not already loaded. Syntax and API follows conventions established by built-in HTML elements and anything that can be done without the component user writing JS, is doable without JS, per the W3C principle of least power . Accessible by default via sensible ARIA defaults, just like normal HTML elements. Themable via ::part() , selective inheritance and custom properties. Very minimal style by default. Normal CSS properties should just work to the the extent possible. Only one component of a given type in the directory, that is flexible and extensible and continuously iterated on and improved by the community. Not 30 different sliders and 15 different tabs that users have to wade through. No branding, no silos of component libraries. Only elements that are designed as closely as possible to what a browser would implement in every way the current technology allows. I would be up for working on this if others feel the same way, since that is not a project for one person to tackle. Whos with me? UPDATE: Wow this post blew up! Thank you all for your interest in participating in a potential future effort. Im currently talking to stakeholders of some of the existing efforts to see if there are any potential collaborations before I go off and create a new one. Follow me on Twitter to hear about the outcome !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Developer priorities throughout their career</title>
  <link>https://lea.verou.me/2020/09/developer-priorities-throughout-their-career/</link>
  <pubDate>Sun, 17 May 2026 03:05:16 +0200</pubDate>
  <description>I made this chart in the amazing Excalidraw about two weeks ago: It only took me 10 minutes! Shortly after, my laptop broke down into repeated kernel panics, and it spent about 10 days in service (I was in a remote place when it broke, so it took some time to get it to service). Yesterday, I was finally reunited with it, turned it on, launched Chrome, and saw it again. It gave me a smile, and I realized I never got to post it, so I tweeted this : https://twitter.com/LeaVerou/status/1306001020636540934 The tweet kinda blew up! It seems many, many developers identify with it. A few also disagreed with it, especially with the “Does it actually work?” line. So I figured I should write a bit about the rationale behind it. I originally wrote it in a tweet , but then I realized I should probably post it in a less transient medium, that is more well suited to longer text. When somebody starts coding, getting the code to work is already difficult enough, so there is no space for other priorities. Learning to formalize one’s thought to the degree a computer demands, and then serialize this thinking with an unforgiving syntax, is hard . Writing code that works is THE priority, and whether it’s good code is not even a consideration. For more experienced programmers, whether it works is ephemeral: today it works, tomorrow a commit causes a regression, the day after another commit fixes it (yes, even with TDD. No testsuite gets close to 100% coverage). Whereas readability &amp; maintainability do not fluctuate much. If they are not prioritized from the beginning, they are much harder to accomplish when you already have a large codebase full of technical debt. Code written by experienced programmers that doesn’t work, can often be fixed with hours or days of debugging. A nontrivial codebase that is not readable can take months or years to rewrite. So one tends to gravitate towards prioritizing what is easier to fix. The “peak of drought” and other over-abstractions Many developers identified with the “peak of drought” . Indeed, like other aspects of maintainability, DRY is not even a concern at first. At some point, a programmer learns about the importance of DRY and gradually begins abstracting away duplication. However, you can have too much of a good thing: soon the need to abstract away any duplication becomes all consuming and leads to absurd, awkward abstractions which actually get in the way and produce needless couplings, often to avoid duplicating very little code, once. In my own “peak of drought” (which lasted far longer than the graph above suggests), I’ve written many useless functions, with parameters that make no sense, just to avoid duplicating a few lines of code once. Many articles have been written about this phenomenon, so I’m not going to repeat their arguments here. As a programmer accumulates even more experience, they start seeing the downsides of over-abstraction and over-normalization and start favoring a more moderate approach which prioritizes readability over DRY when they are at odds. A similar thing happens with design patterns too. At some point, a few years in, a developer reads a book or takes a course about design patterns. Soon thereafter, their code becomes so littered with design patterns that it is practically incomprehensible. “When all you have is a hammer, everything looks like a nail” . I have a feeling that Java and Java-like languages are particularly accommodating to this ailment, so this phenomenon tends to proliferate in their codebases. At some point, the developer has to go back to their past code, and they realize themselves that it is unreadable. Eventually, they learn to use design patterns when they are actually useful, and favor readability over design patterns when the two are at odds. What aspects of your coding practice have changed over the years? How has your perspective shifted? What mistakes of the past did you eventually realize?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Developer priorities throughout their career</title>
  <link>https://lea.verou.me/2020/09/developer-priorities-throughout-their-career/</link>
  <pubDate>Sun, 17 May 2026 03:05:16 +0200</pubDate>
  <description>I made this chart in the amazing Excalidraw about two weeks ago: It only took me 10 minutes! Shortly after, my laptop broke down into repeated kernel panics, and it spent about 10 days in service (I was in a remote place when it broke, so it took some time to get it to service). Yesterday, I was finally reunited with it, turned it on, launched Chrome, and saw it again. It gave me a smile, and I realized I never got to post it, so I tweeted this : https://twitter.com/LeaVerou/status/1306001020636540934 The tweet kinda blew up! It seems many, many developers identify with it. A few also disagreed with it, especially with the Does it actually work? line. So I figured I should write a bit about the rationale behind it. I originally wrote it in a tweet , but then I realized I should probably post it in a less transient medium, that is more well suited to longer text. When somebody starts coding, getting the code to work is already difficult enough, so there is no space for other priorities. Learning to formalize ones thought to the degree a computer demands, and then serialize this thinking with an unforgiving syntax, is hard . Writing code that works is THE priority, and whether its good code is not even a consideration. For more experienced programmers, whether it works is ephemeral: today it works, tomorrow a commit causes a regression, the day after another commit fixes it (yes, even with TDD. No testsuite gets close to 100% coverage). Whereas readability &amp; maintainability do not fluctuate much. If they are not prioritized from the beginning, they are much harder to accomplish when you already have a large codebase full of technical debt. Code written by experienced programmers that doesnt work, can often be fixed with hours or days of debugging. A nontrivial codebase that is not readable can take months or years to rewrite. So one tends to gravitate towards prioritizing what is easier to fix. The peak of drought and other over-abstractions Many developers identified with the peak of drought . Indeed, like other aspects of maintainability, DRY is not even a concern at first. At some point, a programmer learns about the importance of DRY and gradually begins abstracting away duplication. However, you can have too much of a good thing: soon the need to abstract away any duplication becomes all consuming and leads to absurd, awkward abstractions which actually get in the way and produce needless couplings, often to avoid duplicating very little code, once. In my own peak of drought (which lasted far longer than the graph above suggests), Ive written many useless functions, with parameters that make no sense, just to avoid duplicating a few lines of code once. Many articles have been written about this phenomenon, so Im not going to repeat their arguments here. As a programmer accumulates even more experience, they start seeing the downsides of over-abstraction and over-normalization and start favoring a more moderate approach which prioritizes readability over DRY when they are at odds. A similar thing happens with design patterns too. At some point, a few years in, a developer reads a book or takes a course about design patterns. Soon thereafter, their code becomes so littered with design patterns that it is practically incomprehensible. When all you have is a hammer, everything looks like a nail . I have a feeling that Java and Java-like languages are particularly accommodating to this ailment, so this phenomenon tends to proliferate in their codebases. At some point, the developer has to go back to their past code, and they realize themselves that it is unreadable. Eventually, they learn to use design patterns when they are actually useful, and favor readability over design patterns when the two are at odds. What aspects of your coding practice have changed over the years? How has your perspective shifted? What mistakes of the past did you eventually realize?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Parsel: A tiny, permissive CSS selector parser</title>
  <link>https://lea.verou.me/2020/09/parsel-a-tiny-permissive-css-selector-parser/</link>
  <pubDate>Sun, 17 May 2026 03:05:15 +0200</pubDate>
  <description>I’ve posted before about my work for the Web Almanac this year. To make it easier to calculate the stats about CSS selectors, we looked to use an existing selector parser, but most were too big and/or had dependencies or didn’t account for all selectors we wanted to parse, and we’d need to write our own walk and specificity methods anyway. So I did what I usually do in these cases: I wrote my own! You can find it here : https://projects.verou.me/parsel/ It not only parses CSS selectors, but also includes methods to walk the AST produced, as well as calculate specificity as an array and convert it to a number for easy comparison. It is one of my first libraries released as an ES module, and there are instructions about both using it as a module, and as a global, for those who would rather not deal with ES modules yet, because convenient as ESM are, I wouldn’t want to exclude those less familiar with modern JS. Please try it out and report any bugs! We plan to use it for Almanac stats in the next few days, so if you can spot bugs sooner rather than later, you can help that volunteer effort. I’m primarily interested in (realistic) valid selectors that are parsed incorrectly . I’m aware there are many invalid selectors that are parsed weirdly, but that’s not a focus (hence the “permissive” aspect, there are many invalid selectors it won’t throw on, and that’s by design to keep the code small, the logic simple, and the functionality future-proof). How it works If you’re just interested in using this selector parser, read no further. This section is about how the parser works, for those interested in this kind of thing. :) I first started by writing a typical parser, with character-by-character gobbling and different modes , with code somewhat inspired by my familiarity with jsep . I quickly realized that was a more fragile approach for what I wanted to do, and would result in a much larger module. I also missed the ease and flexibility of doing things with regexes. However, since CSS selectors include strings and parens that can be nested, parsing them with regexes is a fool’s errand. Nested structures are not regular languages as my CS friends know. You cannot use a regex to find the closing parenthesis that corresponds to an opening parenthesis, since you can have other nested parens inside it. And it gets even more complex when there are other tokens that can nest, such as strings or comments. What if you have an opening paren that contains a string with a closing paren, like e.g. (&quot;foo)&quot;) ? A regex would match the closing paren inside the string. In fact, parsing the language of nested parens (strings like (()(())) ) with regexes is one of the typical (futile) exercises in a compilers course. Students struggle to do it because it’s an impossible task, and learn the hard way that not everything can be parsed with regexes. Unlike a typical programming language with lots of nested structures however, the language of CSS selectors is more limited. There are only two nested structures: strings and parens, and they only appear in specific types of selectors (namely attribute selectors, pseudo-classes and pseudo-elements). Once we get those out of the way, everything else can be easily parsed by regexes. So I decided to go with a hybrid approach: The selector is first looked at character-by-character, to extract strings and parens. We only extract top-level parens, since anything inside them can be parsed separately (when it’s a selector), or not at all. The strings are replaced by a single character, as many times as the length of the string, so that any character offsets do not change, and the strings themselves are stored in a stack. Same with parens. After that point, this modified selector language is a regular language that can be parsed with regexes. To do so, I follow an approach inspired by the early days of Prism : An object literal of tokens in the order they should be matched in, and a function that tokenizes a string by iteratively matching tokens from an object literal . In fact, this function was taken from an early version of Prism and modified. After we have the list of tokens as a flat array, we can restore strings and parens, and then nest them appropriately to create an AST. Also note that the token regexes use the new-ish named capture groups feature in ES2018, since it’s now supported pretty widely in terms of market share. For wider support, you can transpile :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Parsel: A tiny, permissive CSS selector parser</title>
  <link>https://lea.verou.me/2020/09/parsel-a-tiny-permissive-css-selector-parser/</link>
  <pubDate>Sun, 17 May 2026 03:05:15 +0200</pubDate>
  <description>Ive posted before about my work for the Web Almanac this year. To make it easier to calculate the stats about CSS selectors, we looked to use an existing selector parser, but most were too big and/or had dependencies or didnt account for all selectors we wanted to parse, and wed need to write our own walk and specificity methods anyway. So I did what I usually do in these cases: I wrote my own! You can find it here : https://projects.verou.me/parsel/ It not only parses CSS selectors, but also includes methods to walk the AST produced, as well as calculate specificity as an array and convert it to a number for easy comparison. It is one of my first libraries released as an ES module, and there are instructions about both using it as a module, and as a global, for those who would rather not deal with ES modules yet, because convenient as ESM are, I wouldnt want to exclude those less familiar with modern JS. Please try it out and report any bugs! We plan to use it for Almanac stats in the next few days, so if you can spot bugs sooner rather than later, you can help that volunteer effort. Im primarily interested in (realistic) valid selectors that are parsed incorrectly . Im aware there are many invalid selectors that are parsed weirdly, but thats not a focus (hence the permissive aspect, there are many invalid selectors it wont throw on, and thats by design to keep the code small, the logic simple, and the functionality future-proof). How it works If youre just interested in using this selector parser, read no further. This section is about how the parser works, for those interested in this kind of thing. :) I first started by writing a typical parser, with character-by-character gobbling and different modes , with code somewhat inspired by my familiarity with jsep . I quickly realized that was a more fragile approach for what I wanted to do, and would result in a much larger module. I also missed the ease and flexibility of doing things with regexes. However, since CSS selectors include strings and parens that can be nested, parsing them with regexes is a fools errand. Nested structures are not regular languages as my CS friends know. You cannot use a regex to find the closing parenthesis that corresponds to an opening parenthesis, since you can have other nested parens inside it. And it gets even more complex when there are other tokens that can nest, such as strings or comments. What if you have an opening paren that contains a string with a closing paren, like e.g. (&quot;foo)&quot;) ? A regex would match the closing paren inside the string. In fact, parsing the language of nested parens (strings like (()(())) ) with regexes is one of the typical (futile) exercises in a compilers course. Students struggle to do it because its an impossible task, and learn the hard way that not everything can be parsed with regexes. Unlike a typical programming language with lots of nested structures however, the language of CSS selectors is more limited. There are only two nested structures: strings and parens, and they only appear in specific types of selectors (namely attribute selectors, pseudo-classes and pseudo-elements). Once we get those out of the way, everything else can be easily parsed by regexes. So I decided to go with a hybrid approach: The selector is first looked at character-by-character, to extract strings and parens. We only extract top-level parens, since anything inside them can be parsed separately (when its a selector), or not at all. The strings are replaced by a single character, as many times as the length of the string, so that any character offsets do not change, and the strings themselves are stored in a stack. Same with parens. After that point, this modified selector language is a regular language that can be parsed with regexes. To do so, I follow an approach inspired by the early days of Prism : An object literal of tokens in the order they should be matched in, and a function that tokenizes a string by iteratively matching tokens from an object literal . In fact, this function was taken from an early version of Prism and modified. After we have the list of tokens as a flat array, we can restore strings and parens, and then nest them appropriately to create an AST. Also note that the token regexes use the new-ish named capture groups feature in ES2018, since its now supported pretty widely in terms of market share. For wider support, you can transpile :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introspecting CSS via the CSS OM: Get supported properties, shorthands, longhands</title>
  <link>https://lea.verou.me/2020/07/introspecting-css-via-the-css-om-getting-supported-properties-shorthands-longhands/</link>
  <pubDate>Sun, 17 May 2026 03:05:14 +0200</pubDate>
  <description>For some of the statistics we are going to study for this year’s Web Almanac we may end up needing a list of CSS shorthands and their longhands. Now this is typically done by maintaining a data structure by hand or guessing based on property name structure . But I knew that if we were going to do it by hand, it’s very easy to miss a few of the less popular ones, and the naming rule where shorthands are a prefix of their longhands has failed to get standardized and now has even more exceptions than it used to. And even if we do an incredibly thorough job, next year the data structure will be inaccurate, because CSS and its implementations evolve fast. The browser knows what the shorthands are, surely we should be able to get the information from it …right? Then we could use it directly if this is a client-side library, or in the case of the Almanac, where code needs to be fast because it will run on millions of websites, paste the precomputed result into whatever script we run. There are essentially two steps for this: Get a list of all CSS properties Figure out how to test if a given property is a shorthand and how to get its longhands if so. I decided to tell this story in the inverse order. In my exploration, I first focused on figuring out shorthands (2), because I had coded getting a list of properties many times before, but since (1) is useful in its own right (and probably in more use cases), I felt it makes more sense to examine that first. Note: I’m using document.body instead of a dummy element in these examples, because I like to experiment in about:blank , and it’s just there and because this way you can just copy stuff to the console and try it wherever, even right here while reading this post. However, if you use this as part of code that runs on a real website, it goes without saying that you should create and test things on a dummy element instead! Getting a list of all CSS properties from the browser In Chrome and Safari, this is as simple as Object.getOwnPropertyNames(document.body.style) . However, in Firefox, this doesn’t work. Why is that? To understand this (and how to work around it), we need to dig a bit deeper. In Chrome and Safari, element.style is a CSSStyleDeclaration instance. In Firefox however, it is a CSS2Properties instance, which inherits from CSSStyleDeclaration . CSS2Properties is an older interface, defined in the DOM 2 Specification , which is now obsolete. In the current relevant specification , CSS2Properties is gone, and has been merged with CSSStyleDeclaration . However, Firefox hasn’t caught up yet . Firefox on the left, Safari on the right. Chrome behaves like Safari. Since the properties are on CSSStyleDeclaration , they are not own properties of element.style , so Object.getOwnPropertyNames() fails to return them. However, we can extract the CSSStyleDeclaration instance by using __proto__ or Object.getPrototypeOf() , and then Object.getOwnPropertyNames(Object.getPrototypeOf(document.body.style)) gives us what we want! So we can combine the two to get a list of properties regardless of browser: let properties = Object.getOwnPropertyNames( style.hasOwnProperty(&quot;background&quot;)? style : style.__proto__ ); And then, we just drop non-properties, and de-camelCase: properties = properties.filter(p =&gt; style[p] === &quot;&quot;) // drop functions etc .map(prop =&gt; { // de-camelCase prop = prop.replace(/[A-Z]/g, function($0) { return &#39;-&#39; + $0.toLowerCase() }); if (prop.indexOf(&quot;webkit-&quot;) &gt; -1) { prop = &quot;-&quot; + prop; } return prop; }); You can see a codepen with the result here : https://codepen.io/leaverou/pen/eYJodjb?editors=0010 Testing if a property is a shorthand and getting a list of longhands The main things to note are: When you set a shorthand on an element’s inline style, you are essentially setting all its longhands. element.style is actually array-like, with numerical properties and .length that gives you the number of properties set on it. This means you can use the spread operator on it: &gt; document.body.style.background = &quot;red&quot;; &gt; [...document.body.style] Interestingly, document.body.style.cssText serializes to background: red and not all the longhands. There is one exception: The all property . In Chrome, it does not quite behave as a shorthand: &gt; document.body.style.all = &quot;inherit&quot;; &gt; [...document.body.style] Whereas in Safari and Firefox, it actually returns every single property that is not a shorthand ! Firefox and Safari expand all to literally all non-shorthand properties. While this is interesting from a trivia point of view, it doesn’t actually matter for our use case, since we don’t typically care about all when constructing a list of shorthands, and if we do we can always add or remove it manually. So, to recap, we can easily get the longhands of a given shorthand: function getLonghands(property) { let style = document.body.style; style[property] = &quot;inherit&quot;; // a value that works in every property let ret = [...style]; style.cssText = &quot;&quot;; // clean up return ret; } Putting the pieces together You can see how all the pieces fit together (and the output!) in this codepen : https://codepen.io/leaverou/pen/gOPEJxz?editors=0010 How many of these shorthands did you already know?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introspecting CSS via the CSS OM: Get supported properties, shorthands, longhands</title>
  <link>https://lea.verou.me/2020/07/introspecting-css-via-the-css-om-getting-supported-properties-shorthands-longhands/</link>
  <pubDate>Sun, 17 May 2026 03:05:14 +0200</pubDate>
  <description>For some of the statistics we are going to study for this years Web Almanac we may end up needing a list of CSS shorthands and their longhands. Now this is typically done by maintaining a data structure by hand or guessing based on property name structure . But I knew that if we were going to do it by hand, its very easy to miss a few of the less popular ones, and the naming rule where shorthands are a prefix of their longhands has failed to get standardized and now has even more exceptions than it used to. And even if we do an incredibly thorough job, next year the data structure will be inaccurate, because CSS and its implementations evolve fast. The browser knows what the shorthands are, surely we should be able to get the information from it right? Then we could use it directly if this is a client-side library, or in the case of the Almanac, where code needs to be fast because it will run on millions of websites, paste the precomputed result into whatever script we run. There are essentially two steps for this: Get a list of all CSS properties Figure out how to test if a given property is a shorthand and how to get its longhands if so. I decided to tell this story in the inverse order. In my exploration, I first focused on figuring out shorthands (2), because I had coded getting a list of properties many times before, but since (1) is useful in its own right (and probably in more use cases), I felt it makes more sense to examine that first. Note: Im using document.body instead of a dummy element in these examples, because I like to experiment in about:blank , and its just there and because this way you can just copy stuff to the console and try it wherever, even right here while reading this post. However, if you use this as part of code that runs on a real website, it goes without saying that you should create and test things on a dummy element instead! Getting a list of all CSS properties from the browser In Chrome and Safari, this is as simple as Object.getOwnPropertyNames(document.body.style) . However, in Firefox, this doesnt work. Why is that? To understand this (and how to work around it), we need to dig a bit deeper. In Chrome and Safari, element.style is a CSSStyleDeclaration instance. In Firefox however, it is a CSS2Properties instance, which inherits from CSSStyleDeclaration . CSS2Properties is an older interface, defined in the DOM 2 Specification , which is now obsolete. In the current relevant specification , CSS2Properties is gone, and has been merged with CSSStyleDeclaration . However, Firefox hasnt caught up yet . Firefox on the left, Safari on the right. Chrome behaves like Safari. Since the properties are on CSSStyleDeclaration , they are not own properties of element.style , so Object.getOwnPropertyNames() fails to return them. However, we can extract the CSSStyleDeclaration instance by using __proto__ or Object.getPrototypeOf() , and then Object.getOwnPropertyNames(Object.getPrototypeOf(document.body.style)) gives us what we want! So we can combine the two to get a list of properties regardless of browser: let properties = Object.getOwnPropertyNames( style.hasOwnProperty(&quot;background&quot;)? style : style.__proto__ ); And then, we just drop non-properties, and de-camelCase: properties = properties.filter(p =&gt; style[p] === &quot;&quot;) // drop functions etc .map(prop =&gt; { // de-camelCase prop = prop.replace(/[A-Z]/g, function($0) { return &#39;-&#39; + $0.toLowerCase() }); if (prop.indexOf(&quot;webkit-&quot;) &gt; -1) { prop = &quot;-&quot; + prop; } return prop; }); You can see a codepen with the result here : https://codepen.io/leaverou/pen/eYJodjb?editors=0010 Testing if a property is a shorthand and getting a list of longhands The main things to note are: When you set a shorthand on an elements inline style, you are essentially setting all its longhands. element.style is actually array-like, with numerical properties and .length that gives you the number of properties set on it. This means you can use the spread operator on it: &gt; document.body.style.background = &quot;red&quot;; &gt; [...document.body.style] Interestingly, document.body.style.cssText serializes to background: red and not all the longhands. There is one exception: The all property . In Chrome, it does not quite behave as a shorthand: &gt; document.body.style.all = &quot;inherit&quot;; &gt; [...document.body.style] Whereas in Safari and Firefox, it actually returns every single property that is not a shorthand ! Firefox and Safari expand all to literally all non-shorthand properties. While this is interesting from a trivia point of view, it doesnt actually matter for our use case, since we dont typically care about all when constructing a list of shorthands, and if we do we can always add or remove it manually. So, to recap, we can easily get the longhands of a given shorthand: function getLonghands(property) { let style = document.body.style; style[property] = &quot;inherit&quot;; // a value that works in every property let ret = [...style]; style.cssText = &quot;&quot;; // clean up return ret; } Putting the pieces together You can see how all the pieces fit together (and the output!) in this codepen : https://codepen.io/leaverou/pen/gOPEJxz?editors=0010 How many of these shorthands did you already know?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Import non-ESM libraries in ES Modules, with client-side vanilla JS</title>
  <link>https://lea.verou.me/2020/07/import-non-esm-libraries-in-es-modules-with-client-side-vanilla-js/</link>
  <pubDate>Sun, 17 May 2026 03:05:13 +0200</pubDate>
  <description>In case you haven’t heard, ECMAScript modules (ESM) are now supported everywhere ! While I do have some gripes with them , it’s too late for any of these things to change, so I’m embracing the good parts and have cautiously started using them in new projects. I do quite like that I can just use import statements and dynamic import() for dependencies with URLs right from my JS, without module loaders, extra tags in my HTML, or hacks with dynamic tags and load events (in fact, Bliss has had a helper for this very thing that I’ve used extensively in older projects). I love that I don’t need any libraries for this, and I can use it client-side, anywhere, even in my codepens. Once you start using ESM, you realize that most libraries out there are not written in ESM, nor do they include ESM builds. Many are still using globals, and those that target Node.js use CommonJS (CJS). What can we do in that case? Unfortunately, ES Modules are not really designed with any import (pun intended) mechanism for these syntaxes, but, there are some strategies we could employ. Libraries using globals Technically, a JS file can be parsed as a module even with no imports or exports. Therefore, almost any library that uses globals can be fair game, it can just be imported as a module with no exports! How do we do that? While you may not see this syntax a lot, you don’t actually need to name anything in the import statement. There is a syntax to import a module entirely for its side effects: import &quot;url/to/library.js&quot;; This syntax works fine for libraries that use globals, since declaring a global is essentially a side effect, and all modules share the same global scope. For this to work, the imported library needs to satisfy the following conditions: It should declare the global as a property on window (or self ), not via var Foo or this . In modules top-level variables are local to the module scope, and this is undefined , so the last two ways would not work. Its code should not violate strict mode The URL is either same-origin or CORS-enabled. While can run cross-origin resources, import sadly cannot. Basically, you are running a library as a module that was never written with the intention to be run as a module. Many are written in a way that also works in a module context, but not all. ExploringJS has an excellent summary of the differences between the two . For example, here is a trivial codepen loading jQuery via this method. Libraries using CJS without dependencies I dealt with this today, and it’s what prompted this post. I was trying to play around with Rework CSS , a CSS parser used by the HTTPArchive for analyzing CSS in the wild. However, all its code and documentation assumes Node.js. If I could avoid it, I’d really rather not have to make a Node.js app to try this out, or have to dive in module loaders to be able to require CJS modules in the browser. Was there anything I could do to just run this in a codepen, no strings attached? After a little googling, I found this issue . So there was a JS file I could import and get all the parser functionality. Except …there was one little problem. When you look at the source , it uses module.exports . If you just import that file, you predictably get an error that module is not defined, not to mention there are no ESM exports. My first thought was to stub module as a global variable, import this as a module, and then read module.exports and give it a proper name: window.module = {}; import &quot;https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js&quot;; console.log(module.exports); However, I was still getting the error that module was not defined. How was that possible?! They all share the same global context!! *pulls hair out* After some debugging, it dawned on me: static import statements are hoisted ; the “module” was getting executed before the code that imports it and stubs module . Dynamic imports to the rescue! import() is executed exactly where it’s called, and returns a promise. So this actually works: window.module = {}; import(&quot;https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js&quot;).then(_ =&gt; { console.log(module.exports); }); We could even turn it into a wee function, which I cheekily called require() : async function require(path) { let _module = window.module; window.module = {}; await import(path); let exports = module.exports; window.module = _module; // restore global return exports; } (async () =&gt; { // top-level await cannot come soon enough… let parse = await require(&quot;https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js&quot;); console.log(parse(&quot;body { color: red }&quot;)); })(); You can fiddle with this code in a live pen here . Do note that this technique will only work if the module you’re importing doesn’t import other CJS modules. If it does, you’d need a more elaborate require() function, which is left as an exercise for the reader. Also, just like the previous technique, the code needs to comply with strict mode and not be cross-origin. A similar technique can be used to load AMD modules via import() , just stub define() and you’re good to go. So, with this technique I was able to quickly whip up a ReworkCSS playground . You just edit the CSS in CodePen and see the resulting AST , and you can even fork it to share a specific AST with others! :) https://codepen.io/leaverou/pen/qBbQdGG Update: CJS with static imports After this article was posted, a clever hack was pointed out to me on Twitter : https://twitter.com/justinfagnani/status/1285325206811107329 While this works great if you can have multiple separate files, it doesn’t work when you’re e.g. quickly trying out a pen. Data URIs to the rescue! Turns out you can import a module from a data URI ! So let’s adapt our Rework example to use this : https://codepen.io/leaverou/pen/xxZmWvx Addendum: ESM gripes Since I was bound to get questions about what my gripes are with ESM, I figured I should mention them pre-emptively. First off, a little context. Nearly all of the JS I write is for libraries. I write libraries as a hobby , I write libraries as my job , and sometimes I write libraries to help me do my job . My job is usability (HCI) research (and specifically making programming easier), so I’m very sensitive to developer experience issues. I want my libraries to be usable not just by seasoned developers, but by novices too. ESM has not been designed with novices in mind. It evolved from the CJS/UMD/AMD ecosystem, in which most voices are seasoned developers. My main gripe with them, is how they expect full adoption, and settle for nothing less. There is no way to create a bundle of a library that can be used both traditionally, with a global, or as an ES module. There is also no standard way to import older libraries, or libraries using other module patterns (yes, this very post is about doing that, but essentially these are hacks, and there should be a better way). I understand the benefits of static analysis for imports and exports, but I wish there was a dynamic alternative to export , analogous to the dynamic import() . In terms of migrating to ESM, I also dislike how opinionated they are: strict mode is great, but forcing it doesn’t help people trying to migrate older codebases. Restricting them to cross-origin is also a pain, using s from other domains made it possible to quickly experiment with various libraries, and I would love for that to be true for modules too. But overall, I’m excited that JS now natively supports a module mechanism, and I expect any library I release in the future to utilize it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Import non-ESM libraries in ES Modules, with client-side vanilla JS</title>
  <link>https://lea.verou.me/2020/07/import-non-esm-libraries-in-es-modules-with-client-side-vanilla-js/</link>
  <pubDate>Sun, 17 May 2026 03:05:13 +0200</pubDate>
  <description>In case you havent heard, ECMAScript modules (ESM) are now supported everywhere ! While I do have some gripes with them , its too late for any of these things to change, so Im embracing the good parts and have cautiously started using them in new projects. I do quite like that I can just use import statements and dynamic import() for dependencies with URLs right from my JS, without module loaders, extra tags in my HTML, or hacks with dynamic tags and load events (in fact, Bliss has had a helper for this very thing that Ive used extensively in older projects). I love that I dont need any libraries for this, and I can use it client-side, anywhere, even in my codepens. Once you start using ESM, you realize that most libraries out there are not written in ESM, nor do they include ESM builds. Many are still using globals, and those that target Node.js use CommonJS (CJS). What can we do in that case? Unfortunately, ES Modules are not really designed with any import (pun intended) mechanism for these syntaxes, but, there are some strategies we could employ. Libraries using globals Technically, a JS file can be parsed as a module even with no imports or exports. Therefore, almost any library that uses globals can be fair game, it can just be imported as a module with no exports! How do we do that? While you may not see this syntax a lot, you dont actually need to name anything in the import statement. There is a syntax to import a module entirely for its side effects: import &quot;url/to/library.js&quot;; This syntax works fine for libraries that use globals, since declaring a global is essentially a side effect, and all modules share the same global scope. For this to work, the imported library needs to satisfy the following conditions: It should declare the global as a property on window (or self ), not via var Foo or this . In modules top-level variables are local to the module scope, and this is undefined , so the last two ways would not work. Its code should not violate strict mode The URL is either same-origin or CORS-enabled. While can run cross-origin resources, import sadly cannot. Basically, you are running a library as a module that was never written with the intention to be run as a module. Many are written in a way that also works in a module context, but not all. ExploringJS has an excellent summary of the differences between the two . For example, here is a trivial codepen loading jQuery via this method. Libraries using CJS without dependencies I dealt with this today, and its what prompted this post. I was trying to play around with Rework CSS , a CSS parser used by the HTTPArchive for analyzing CSS in the wild. However, all its code and documentation assumes Node.js. If I could avoid it, Id really rather not have to make a Node.js app to try this out, or have to dive in module loaders to be able to require CJS modules in the browser. Was there anything I could do to just run this in a codepen, no strings attached? After a little googling, I found this issue . So there was a JS file I could import and get all the parser functionality. Except there was one little problem. When you look at the source , it uses module.exports . If you just import that file, you predictably get an error that module is not defined, not to mention there are no ESM exports. My first thought was to stub module as a global variable, import this as a module, and then read module.exports and give it a proper name: window.module = {}; import &quot;https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js&quot;; console.log(module.exports); However, I was still getting the error that module was not defined. How was that possible?! They all share the same global context!! *pulls hair out* After some debugging, it dawned on me: static import statements are hoisted ; the module was getting executed before the code that imports it and stubs module . Dynamic imports to the rescue! import() is executed exactly where its called, and returns a promise. So this actually works: window.module = {}; import(&quot;https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js&quot;).then(_ =&gt; { console.log(module.exports); }); We could even turn it into a wee function, which I cheekily called require() : async function require(path) { let _module = window.module; window.module = {}; await import(path); let exports = module.exports; window.module = _module; // restore global return exports; } (async () =&gt; { // top-level await cannot come soon enough let parse = await require(&quot;https://cdn.jsdelivr.net/gh/reworkcss/css@latest/lib/parse/index.js&quot;); console.log(parse(&quot;body { color: red }&quot;)); })(); You can fiddle with this code in a live pen here . Do note that this technique will only work if the module youre importing doesnt import other CJS modules. If it does, youd need a more elaborate require() function, which is left as an exercise for the reader. Also, just like the previous technique, the code needs to comply with strict mode and not be cross-origin. A similar technique can be used to load AMD modules via import() , just stub define() and youre good to go. So, with this technique I was able to quickly whip up a ReworkCSS playground . You just edit the CSS in CodePen and see the resulting AST , and you can even fork it to share a specific AST with others! :) https://codepen.io/leaverou/pen/qBbQdGG Update: CJS with static imports After this article was posted, a clever hack was pointed out to me on Twitter : https://twitter.com/justinfagnani/status/1285325206811107329 While this works great if you can have multiple separate files, it doesnt work when youre e.g. quickly trying out a pen. Data URIs to the rescue! Turns out you can import a module from a data URI ! So lets adapt our Rework example to use this : https://codepen.io/leaverou/pen/xxZmWvx Addendum: ESM gripes Since I was bound to get questions about what my gripes are with ESM, I figured I should mention them pre-emptively. First off, a little context. Nearly all of the JS I write is for libraries. I write libraries as a hobby , I write libraries as my job , and sometimes I write libraries to help me do my job . My job is usability (HCI) research (and specifically making programming easier), so Im very sensitive to developer experience issues. I want my libraries to be usable not just by seasoned developers, but by novices too. ESM has not been designed with novices in mind. It evolved from the CJS/UMD/AMD ecosystem, in which most voices are seasoned developers. My main gripe with them, is how they expect full adoption, and settle for nothing less. There is no way to create a bundle of a library that can be used both traditionally, with a global, or as an ES module. There is also no standard way to import older libraries, or libraries using other module patterns (yes, this very post is about doing that, but essentially these are hacks, and there should be a better way). I understand the benefits of static analysis for imports and exports, but I wish there was a dynamic alternative to export , analogous to the dynamic import() . In terms of migrating to ESM, I also dislike how opinionated they are: strict mode is great, but forcing it doesnt help people trying to migrate older codebases. Restricting them to cross-origin is also a pain, using s from other domains made it possible to quickly experiment with various libraries, and I would love for that to be true for modules too. But overall, Im excited that JS now natively supports a module mechanism, and I expect any library I release in the future to utilize it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Releasing MaVoice: A free app to vote on repo issues</title>
  <link>https://lea.verou.me/2020/07/releasing-mavoice-a-free-app-to-vote-on-repo-issues/</link>
  <pubDate>Sun, 17 May 2026 03:05:12 +0200</pubDate>
  <description>First off, some news: I agreed to be this year’s CSS content lead for the Web Almanac ! One of the first things to do is to flesh out what statistics we should study to answer the question “What is the state of CSS in 2020?” . You can see last year’s chapter to get an idea of what kind of statistics could help answer that question. Of course, my first thought was “We should involve the community! People might have great ideas of statistics we could study!” . But what should we use to vote on ideas and make them rise to the top? I wanted to use a repo to manage all this, since I like all the conveniences for managing issues. However, there is not much on Github for voting. You can add 👍 reactions, but not sort by them, and voting itself is tedious: you need to open the comment, click on the reaction, then go back to the list of issues, rinse and repeat. Ideally, I wanted something like UserVoice™️, which lets you vote with one click, and sorts proposals by votes. And then it dawned on me: I’ll just build a Mavo app on top of the repo issues, that displays them as proposals to be voted on and sorts by 👍 reactions, UserVoice™️-style but without the UserVoice™️ price tag. 😎 In fact, I had started such a Mavo app a couple years ago, and never finished or released it. So, I just dug it up and resurrected it from its ashes! It’s — quite fittingly I think — called MaVoice . You can set it to any repo via the repo URL parameter, and any label via the labels URL param (defaults to enhancement ) to create a customized URL for any repo you want in seconds! For example, here’s the URL for the css-almanac repo, which only displays issues with the label “proposed stat”: https://projects.verou.me/mavoice/?repo=leaverou/css-almanac&amp;labels=proposed%20stat While this did need some custom JS, unlike other Mavo apps which need none, I’m still pretty happy I could spin up this kind of app with :) Yes, it’s still rough around the edges, and I’m sure you can find many things that could be improved, but it does the job for now, and PRs are always welcome 🤷🏽‍♀️ The main caveat if you decide to use this for your own repo : Because (to my knowledge) Github API still does not provide a way to sort issues by 👍 reactions, or even reactions in general (in either the v3 REST API, or the GraphQL API), issues are instead requested sorted by comment count, and are sorted by 👍 reactions client-side, right before render . Due to API limitations, this API call can only fetch the top 100 results . This means that if you have more than 100 issues to display (i.e. more than 100 open issues with the given label), it could potentially be inaccurate, especially if you have issues with many reactions and few comments. Another caveat is that because this is basically reactions on Github issues, there is no limit on how many issues someone can vote on . In theory, if they’re a bad actor (or just overexcited), they can just vote on everything. But I suppose that’s an intrinsic problem with using reactions to vote for things, having a UI for it just reveals the existing issue, it doesn’t create it. Hope you enjoy, and don’t forget to vote on which CSS stats we should study !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Releasing MaVoice: A free app to vote on repo issues</title>
  <link>https://lea.verou.me/2020/07/releasing-mavoice-a-free-app-to-vote-on-repo-issues/</link>
  <pubDate>Sun, 17 May 2026 03:05:12 +0200</pubDate>
  <description>First off, some news: I agreed to be this years CSS content lead for the Web Almanac ! One of the first things to do is to flesh out what statistics we should study to answer the question What is the state of CSS in 2020? . You can see last years chapter to get an idea of what kind of statistics could help answer that question. Of course, my first thought was We should involve the community! People might have great ideas of statistics we could study! . But what should we use to vote on ideas and make them rise to the top? I wanted to use a repo to manage all this, since I like all the conveniences for managing issues. However, there is not much on Github for voting. You can add reactions, but not sort by them, and voting itself is tedious: you need to open the comment, click on the reaction, then go back to the list of issues, rinse and repeat. Ideally, I wanted something like UserVoice, which lets you vote with one click, and sorts proposals by votes. And then it dawned on me: Ill just build a Mavo app on top of the repo issues, that displays them as proposals to be voted on and sorts by reactions, UserVoice-style but without the UserVoice price tag. In fact, I had started such a Mavo app a couple years ago, and never finished or released it. So, I just dug it up and resurrected it from its ashes! Its quite fittingly I think called MaVoice . You can set it to any repo via the repo URL parameter, and any label via the labels URL param (defaults to enhancement ) to create a customized URL for any repo you want in seconds! For example, heres the URL for the css-almanac repo, which only displays issues with the label proposed stat: https://projects.verou.me/mavoice/?repo=leaverou/css-almanac&amp;labels=proposed%20stat While this did need some custom JS, unlike other Mavo apps which need none, Im still pretty happy I could spin up this kind of app with :) Yes, its still rough around the edges, and Im sure you can find many things that could be improved, but it does the job for now, and PRs are always welcome The main caveat if you decide to use this for your own repo : Because (to my knowledge) Github API still does not provide a way to sort issues by reactions, or even reactions in general (in either the v3 REST API, or the GraphQL API), issues are instead requested sorted by comment count, and are sorted by reactions client-side, right before render . Due to API limitations, this API call can only fetch the top 100 results . This means that if you have more than 100 issues to display (i.e. more than 100 open issues with the given label), it could potentially be inaccurate, especially if you have issues with many reactions and few comments. Another caveat is that because this is basically reactions on Github issues, there is no limit on how many issues someone can vote on . In theory, if theyre a bad actor (or just overexcited), they can just vote on everything. But I suppose thats an intrinsic problem with using reactions to vote for things, having a UI for it just reveals the existing issue, it doesnt create it. Hope you enjoy, and dont forget to vote on which CSS stats we should study !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The Cicada Principle, revisited with CSS variables</title>
  <link>https://lea.verou.me/2020/07/the-cicada-principle-revisited-with-css-variables/</link>
  <pubDate>Sun, 17 May 2026 03:05:11 +0200</pubDate>
  <description>Many of today’s web crafters were not writing CSS at the time Alex Walker’s landmark article The Cicada Principle and Why it Matters to Web Designers was published in 2011. Last I heard of it was in 2016, when it was used in conjunction with blend modes to pseudo-randomize backgrounds even further. So what is the Cicada Principle and how does it relate to web design in a nutshell? It boils down to: when using repeating elements (tiled backgrounds, different effects on multiple elements etc), using prime numbers for the size of the repeating unit maximizes the appearance of organic randomness. Note that this only works when the parameters you set are independent. When I recently redesigned my blog , I ended up using a variation of the Cicada principle to pseudo-randomize the angles of code snippets. I didn’t think much of it until I saw this tweet : https://twitter.com/StuRobson/status/1273904521132072960 This made me think: hey, maybe I should actually write a blog post about the technique. After all, the technique itself is useful for way more than angles on code snippets. The main idea is simple: You write your main rule using CSS variables, and then use :nth-of-*() rules to set these variables to something different every N items. If you use enough variables, and choose your Ns for them to be prime numbers, you reach a good appearance of pseudo-randomness with relatively small Ns. In the case of code samples, I only have two different top cuts (going up or going down) and two different bottom cuts (same), which produce 2*2 = 4 different shapes. Since I only had four shapes, I wanted to maximize the pseudo-randomness of their order. A first attempt looks like this: pre { clip-path: polygon(var(--clip-top), var(--clip-bottom)); --clip-top: 0 0, 100% 2em; --clip-bottom: 100% calc(100% - 1.5em), 0 100%; } pre:nth-of-type(odd) { --clip-top: 0 2em, 100% 0; } pre:nth-of-type(3n + 1) { --clip-bottom: 100% 100%, 0 calc(100% - 1.5em); } This way, the exact sequence of shapes repeats every 2 * 3 = 6 code snippets. Also, the alternative --clip-bottom doesn’t really get the same visibility as the others, being present only 33.333% of the time. However, if we just add one more selector: pre { clip-path: polygon(var(--clip-top), var(--clip-bottom)); --clip-top: 0 0, 100% 2em; --clip-bottom: 100% calc(100% - 1.5em), 0 100%; } pre:nth-of-type(odd) { --clip-top: 0 2em, 100% 0; } pre:nth-of-type(3n + 1), pre:nth-of-type(5n + 1) { --clip-bottom: 100% 100%, 0 calc(100% - 1.5em); } Now the exact same sequence of shapes repeats every 2 * 3 * 5 = 30 code snippets, probably way more than I will have in any article. And it’s more fair to the alternate --clip-bottom , which now gets 1/3 + 1/5 - 1/15 = 46.67%, which is almost as much as the alternate --clip-top gets! You can explore this effect in this codepen : https://codepen.io/leaverou/pen/8541bfd3a42551f8845d668f29596ef9?editors=1100 Or, to better explore how different CSS creates different pseudo-randomness, you can use this content-less version with three variations: https://codepen.io/leaverou/pen/NWxaPVx Of course, the illusion of randomness is much better with more shapes, e.g. if we introduce a third type of edge we get 3 * 3 = 9 possible shapes: https://codepen.io/leaverou/pen/dyGmbJJ?editors=1100 I also used primes 7 and 11, so that the sequence repeats every 77 items. In general, the larger primes you use, the better the illusion of randomness, but you need to include more selectors, which can get tedious. Other examples So this got me thinking: What else would this technique be cool on? Especially if we include more values as well, we can pseudo-randomize the result itself better, and not just the order of only 4 different results. So I did a few experiments. Pseudo-randomized color swatches https://codepen.io/leaverou/pen/NWxXQKX Pseudo-randomized color swatches, with variables for hue, saturation, and lightness. And an alternative version : https://codepen.io/leaverou/pen/RwrLPer Which one looks more random? Why do you think that is? Pseudo-randomized border-radius Admittedly, this one can be done with just longhands, but since I realized this after I had already made it, I figured eh, I may as well include it 🤷🏽‍♀️ https://codepen.io/leaverou/pen/ZEQXOrd It is also really cool when combined with pseudo-random colors (just hue this time): https://codepen.io/leaverou/pen/oNbGzeE Pseudo-randomized snowfall Lots of things here: Using translate and transform together to animate them separately without resorting to CSS.registerPropery() Pseudo-randomized horizontal offset, animation-delay, font-size Technically we don’t need CSS variables to pseudo-randomize font-size , we can just set the property itself. However, variables enable us to pseudo-randomize it via a multiplier, in order to decouple the base font size from the pseudo-randomness, so we can edit them independently. And then we can use the same multiplier in animation-duration to make smaller snowflakes fall slower! https://codepen.io/leaverou/pen/YzwrWvV?editors=1100 Conclusions In general, the larger the primes you use, the better the illusion of randomness. With smaller primes, you will get more variation, but less appearance of randomness. There are two main ways to use primes to create the illusion of randomness with :nth-child() selectors: The first way is to set each trait on :nth-child(pn + b) where p is a prime that increases with each value and b is constant for each trait, like so: :nth-child(3n + 1) { property1: value11; } :nth-child(5n + 1) { property1: value12; } :nth-child(7n + 1) { property1: value13; } :nth-child(11n + 1) { property1: value14; } ... :nth-child(3n + 2) { property2: value21; } :nth-child(5n + 2) { property2: value22; } :nth-child(7n + 2) { property2: value23; } :nth-child(11n + 2) { property2: value24; } ... The benefit of this approach is that you can have as few or as many values as you like. The drawback is that because primes are sparse, and become sparser as we go, you will have a lot of “holes” where your base value is applied. The second way (which is more on par with the original Cicada principle) is to set each trait on :nth-child(pn + b) where p is constant per trait, and b increases with each value: :nth-child(5n + 1) { property1: value11; } :nth-child(5n + 2) { property1: value12; } :nth-child(5n + 3) { property1: value13; } :nth-child(5n + 4) { property1: value14; } ... :nth-child(7n + 1) { property2: value21; } :nth-child(7n + 2) { property2: value22; } :nth-child(7n + 3) { property2: value23; } :nth-child(7n + 4) { property2: value24; } ... This creates a better overall impression of randomness (especially if you order the values in a pseudo-random way too) without “holes”, but is more tedious, as you need as many values as the prime you’re using. What other cool examples can you think of?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The Cicada Principle, revisited with CSS variables</title>
  <link>https://lea.verou.me/2020/07/the-cicada-principle-revisited-with-css-variables/</link>
  <pubDate>Sun, 17 May 2026 03:05:11 +0200</pubDate>
  <description>Many of todays web crafters were not writing CSS at the time Alex Walkers landmark article The Cicada Principle and Why it Matters to Web Designers was published in 2011. Last I heard of it was in 2016, when it was used in conjunction with blend modes to pseudo-randomize backgrounds even further. So what is the Cicada Principle and how does it relate to web design in a nutshell? It boils down to: when using repeating elements (tiled backgrounds, different effects on multiple elements etc), using prime numbers for the size of the repeating unit maximizes the appearance of organic randomness. Note that this only works when the parameters you set are independent. When I recently redesigned my blog , I ended up using a variation of the Cicada principle to pseudo-randomize the angles of code snippets. I didnt think much of it until I saw this tweet : https://twitter.com/StuRobson/status/1273904521132072960 This made me think: hey, maybe I should actually write a blog post about the technique. After all, the technique itself is useful for way more than angles on code snippets. The main idea is simple: You write your main rule using CSS variables, and then use :nth-of-*() rules to set these variables to something different every N items. If you use enough variables, and choose your Ns for them to be prime numbers, you reach a good appearance of pseudo-randomness with relatively small Ns. In the case of code samples, I only have two different top cuts (going up or going down) and two different bottom cuts (same), which produce 2*2 = 4 different shapes. Since I only had four shapes, I wanted to maximize the pseudo-randomness of their order. A first attempt looks like this: pre { clip-path: polygon(var(--clip-top), var(--clip-bottom)); --clip-top: 0 0, 100% 2em; --clip-bottom: 100% calc(100% - 1.5em), 0 100%; } pre:nth-of-type(odd) { --clip-top: 0 2em, 100% 0; } pre:nth-of-type(3n + 1) { --clip-bottom: 100% 100%, 0 calc(100% - 1.5em); } This way, the exact sequence of shapes repeats every 2 * 3 = 6 code snippets. Also, the alternative --clip-bottom doesnt really get the same visibility as the others, being present only 33.333% of the time. However, if we just add one more selector: pre { clip-path: polygon(var(--clip-top), var(--clip-bottom)); --clip-top: 0 0, 100% 2em; --clip-bottom: 100% calc(100% - 1.5em), 0 100%; } pre:nth-of-type(odd) { --clip-top: 0 2em, 100% 0; } pre:nth-of-type(3n + 1), pre:nth-of-type(5n + 1) { --clip-bottom: 100% 100%, 0 calc(100% - 1.5em); } Now the exact same sequence of shapes repeats every 2 * 3 * 5 = 30 code snippets, probably way more than I will have in any article. And its more fair to the alternate --clip-bottom , which now gets 1/3 + 1/5 - 1/15 = 46.67%, which is almost as much as the alternate --clip-top gets! You can explore this effect in this codepen : https://codepen.io/leaverou/pen/8541bfd3a42551f8845d668f29596ef9?editors=1100 Or, to better explore how different CSS creates different pseudo-randomness, you can use this content-less version with three variations: https://codepen.io/leaverou/pen/NWxaPVx Of course, the illusion of randomness is much better with more shapes, e.g. if we introduce a third type of edge we get 3 * 3 = 9 possible shapes: https://codepen.io/leaverou/pen/dyGmbJJ?editors=1100 I also used primes 7 and 11, so that the sequence repeats every 77 items. In general, the larger primes you use, the better the illusion of randomness, but you need to include more selectors, which can get tedious. Other examples So this got me thinking: What else would this technique be cool on? Especially if we include more values as well, we can pseudo-randomize the result itself better, and not just the order of only 4 different results. So I did a few experiments. Pseudo-randomized color swatches https://codepen.io/leaverou/pen/NWxXQKX Pseudo-randomized color swatches, with variables for hue, saturation, and lightness. And an alternative version : https://codepen.io/leaverou/pen/RwrLPer Which one looks more random? Why do you think that is? Pseudo-randomized border-radius Admittedly, this one can be done with just longhands, but since I realized this after I had already made it, I figured eh, I may as well include it https://codepen.io/leaverou/pen/ZEQXOrd It is also really cool when combined with pseudo-random colors (just hue this time): https://codepen.io/leaverou/pen/oNbGzeE Pseudo-randomized snowfall Lots of things here: Using translate and transform together to animate them separately without resorting to CSS.registerPropery() Pseudo-randomized horizontal offset, animation-delay, font-size Technically we dont need CSS variables to pseudo-randomize font-size , we can just set the property itself. However, variables enable us to pseudo-randomize it via a multiplier, in order to decouple the base font size from the pseudo-randomness, so we can edit them independently. And then we can use the same multiplier in animation-duration to make smaller snowflakes fall slower! https://codepen.io/leaverou/pen/YzwrWvV?editors=1100 Conclusions In general, the larger the primes you use, the better the illusion of randomness. With smaller primes, you will get more variation, but less appearance of randomness. There are two main ways to use primes to create the illusion of randomness with :nth-child() selectors: The first way is to set each trait on :nth-child(pn + b) where p is a prime that increases with each value and b is constant for each trait, like so: :nth-child(3n + 1) { property1: value11; } :nth-child(5n + 1) { property1: value12; } :nth-child(7n + 1) { property1: value13; } :nth-child(11n + 1) { property1: value14; } ... :nth-child(3n + 2) { property2: value21; } :nth-child(5n + 2) { property2: value22; } :nth-child(7n + 2) { property2: value23; } :nth-child(11n + 2) { property2: value24; } ... The benefit of this approach is that you can have as few or as many values as you like. The drawback is that because primes are sparse, and become sparser as we go, you will have a lot of holes where your base value is applied. The second way (which is more on par with the original Cicada principle) is to set each trait on :nth-child(pn + b) where p is constant per trait, and b increases with each value: :nth-child(5n + 1) { property1: value11; } :nth-child(5n + 2) { property1: value12; } :nth-child(5n + 3) { property1: value13; } :nth-child(5n + 4) { property1: value14; } ... :nth-child(7n + 1) { property2: value21; } :nth-child(7n + 2) { property2: value22; } :nth-child(7n + 3) { property2: value23; } :nth-child(7n + 4) { property2: value24; } ... This creates a better overall impression of randomness (especially if you order the values in a pseudo-random way too) without holes, but is more tedious, as you need as many values as the prime youre using. What other cool examples can you think of?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Refactoring optional chaining into a large codebase: lessons learned</title>
  <link>https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/</link>
  <pubDate>Sun, 17 May 2026 03:05:10 +0200</pubDate>
  <description>Chinese translation by Coink Wang Now that optional chaining is supported across the board , I decided to finally refactor Mavo to use it (yes, yes, we do provide a transpiled version as well for older browsers, settle down). This is a moment I have been waiting for a long time, as I think optional chaining is the single most substantial JS syntax improvement since arrow functions and template strings. Yes, I think it’s more significant than async/await, just because of the mere frequency of code it improves. Property access is literally everywhere . First off, what is optional chaining , in case you haven’t heard of it before? You know how you can’t just do foo.bar.baz() without checking if foo exists, and then if foo.bar exists, and then if foo.bar.baz exists because you’ll get an error? So you have to do something awkward like: if (foo &amp;&amp; foo.bar &amp;&amp; foo.bar.baz) { foo.bar.baz(); } Or even: foo &amp;&amp; foo.bar &amp;&amp; foo.bar.baz &amp;&amp; foo.bar.baz(); Some even contort object destructuring to help with this . With optional chaining, you can just do this: foo?.bar?.baz?.() It supports normal property access, brackets ( foo?.[bar] ), and even function invocation ( foo?.() ). Sweet, right?? Yes, mostly. Indeed, there is SO MUCH code that can be simplified with it, it’s incredible. But there are a few caveats. Patterns to search for Suppose you decided to go ahead and refactor your code as well. What to look for? There is of course the obvious foo &amp;&amp; foo.bar that becomes foo?.bar . There is also the conditional version of it, that we described in the beginning of this article, which uses if() for some or all of the checks in the chain. There are also a few more patterns. Ternary foo? foo.bar : defaultValue Which can now be written as: foo?.bar || defaultValue or, using the other awesome new operator, the nullish coalescing operator : foo?.bar ?? defaultValue Array checking if (foo.length &gt; 3) { foo[2] } which now becomes: foo?.[2] Note that this is no substitute for a real array check, like the one done by Array.isArray(foo) . Do not go about replacing proper array checking with duck typing because it’s shorter. We stopped doing that over a decade ago . Regex match Forget about things like this: let match = &quot;#C0FFEE&quot;.match(/#([A-Z]+)/i); let hex = match &amp;&amp; match[1]; Or even things like that: let hex = (&quot;#C0FFEE&quot;.match(/#([A-Z]+)/i) || [,])[1]; Now it’s just: let hex = &quot;#C0FFEE&quot;.match(/#([A-Z]+)/i)?.[1]; In our case, I was able to even remove two utility functions and replace their invocations with this. Feature detection In simple cases, feature detection can be replaced by ?. . For example: if (element.prepend) element.prepend(otherElement); becomes: element.prepend?.(otherElement); Don’t overdo it While it may be tempting to convert code like this: if (foo) { something(foo.bar); somethingElse(foo.baz); andOneLastThing(foo.yolo); } to this: something(foo?.bar); somethingElse(foo?.baz); andOneLastThing(foo?.yolo); Don’t . You’re essentially having the JS runtime check foo three times instead of one. You may argue these things don’t matter much anymore performance-wise, but it’s the same repetition for the human reading your code: they have to mentally process the check for foo three times instead of one. And if they need to add another statement using property access on foo , they need to add yet another check, instead of just using the conditional that’s already there. Caveats You still need to check before assignment You may be tempted to convert things like: if (foo &amp;&amp; foo.bar) { foo.bar.baz = someValue; } to: foo?.bar?.baz = someValue; Unfortunately, that’s not possible and will error. This was an actual snippet from our codebase: if (this.bar &amp;&amp; this.bar.edit) { this.bar.edit.textContent = this._(&quot;edit&quot;); } Which I happily refactored to: if (this.bar?.edit) { this.bar.edit.textContent = this._(&quot;edit&quot;); } All good so far, this works nicely. But then I thought, wait a second… do I need the conditional at all? Maybe I can just do this: this.bar?.edit?.textContent = this._(&quot;edit&quot;); Nope. Uncaught SyntaxError: Invalid left-hand side in assignment . Can’t do that. You still need the conditional. I literally kept doing this, and I’m glad I had ESLint in my editor to warn me about it without having to actually run the code. It’s very easy to put the ?. in the wrong place or forget some ?. Note that if you’re refactoring a long chain with optional chaining, you often need to insert multiple ?. after the first one, for every member access that may or may not exist, otherwise you will get errors once the optional chaining returns undefined. Or, sometimes you may think you do, because you put the ?. in the wrong place. Take the following real example. I originally refactored this: this.children[index]? this.children[index].element : this.marker into this: this.children?.[index].element ?? this.marker then got a TypeError: Cannot read property &#39;element&#39; of undefined . Oops! Then I fixed it by adding an additional ?. : this.children?.[index]?.element ?? this.marker This works, but is superfluous, as pointed out in the comments. I just needed to move the ?. : this.children.[index]?.element ?? this.marker Note that as pointed out in the comments be careful about replacing array length checks with optional access to the index. This might be bad for performance, because out-of-bounds access on an array is de-optimizing the code in V8 (as it has to check the prototype chain for such a property too, not only decide that there is no such index in the array). It can introduce bugs if you’re not careful If, like me, you go on a refactoring spree, it’s easy after a certain point to just introduce optional chaining in places where it actually ends up changing what your code does and introducing subtle bugs. null vs undefined Possibly the most common pattern is replacing foo &amp;&amp; foo.bar with foo?.bar . While in most cases these work equivalently, this is not true for every case. When foo is null , the former returns null , whereas the latter returns undefined . This can cause bugs to creep up in cases where the distinction matters and is probably the most common way to introduce bugs with this type of refactoring. Equality checks Be careful about converting code like this: if (foo &amp;&amp; bar &amp;&amp; foo.prop1 === bar.prop2) { /* ... */ } to code like this: if (foo?.prop1 === bar?.prop2) { /* ... */ } In the first case, the condition will not be true, unless both foo and bar are truthy. However, in the second case, if both foo and bar are nullish, the conditional will be true, because both operands will return undefined ! The same bug can creep in even if the second operand doesn’t include any optional chaining, as long as it could be undefined you can get unintended matches. Operator precedence slips One thing to look out for is that optional chaining has higher precedence than &amp;&amp; . This becomes particularly significant when you replace an expression using &amp;&amp; that also involves equality checks, since the (in)equality operators are sandwiched between ?. and &amp;&amp; , having lower precedence than the former and higher than the latter. if (foo &amp;&amp; foo.bar === baz) { /* ... */ } What is compared with baz here? foo.bar or foo &amp;&amp; foo.bar ? Since &amp;&amp; has lower precedence than === , it’s as if we had written: if (foo &amp;&amp; (foo.bar === baz)) { /* ... */ } Note that the conditional cannot ever be executed if foo is falsy. However, once we refactor it to use optional chaining, it is now as if we were comparing ( foo &amp;&amp; foo.bar ) to baz : if (foo?.bar === baz) { /* ... */ } An obvious case where the different semantics affect execution is when baz is undefined . In that case, we can enter the conditional when foo is nullish, since then optional chaining will return undefined , which is basically the case we described above. In most other cases this doesn’t make a big difference. It can however be pretty bad when instead of an equality operator, you have an inequality operator, which still has the same precedence. Compare this: if (foo &amp;&amp; foo.bar !== baz) { /* ... */ } with this: if (foo?.bar !== baz) { /* ... */ } Now, we are going to enter the conditional every time foo is nullish, as long as baz is not undefined ! The difference is not noticeable in an edge case anymore, but in the average case! 😱 Return statements Rather obvious after you think about it, but it’s easy to forget return statements in the heat of the moment. You cannot replace things like this: if (foo &amp;&amp; foo.bar) { return foo.bar(); } with: return foo?.bar?.(); In the first case, you return conditionally, whereas in the second case you return always. This will not introduce any issues if the conditional is the last statement in your function, but it will change the control flow if it’s not. Sometimes, it can fix bugs too! Take a look at this code I encountered during my refactoring: /** * Get the current value of a CSS property on an element */ getStyle: (element, property) =&gt; { if (element) { var value = getComputedStyle(element).getPropertyValue(property); if (value) { return value.trim(); } } }, Can you spot the bug? If value is an empty string (and given the context, it could very well be), the function will return undefined , because an empty string is falsy! Rewriting it to use optional chaining fixes this: if (element) { var value = getComputedStyle(element).getPropertyValue(property); return value?.trim(); } Now, if value is the empty string, it will still return an empty string and it will only return undefined when value is nullish. Finding usages becomes trickier This was pointed out by Razvan Caliman on Twitter: https://twitter.com/razvancaliman/status/1273638529399230464 Bottom line In the end, this refactor made Mavo about 2KB lighter and saved 37 lines of code. It did however make the transpiled version 79 lines and 9KB (!) heavier. Here is the relevant commit , for your perusal. I tried my best to exercise restraint and not introduce any unrelated refactoring in this commit, so that the diff is chock-full of optional chaining examples. It has 104 additions and 141 deletions, so I’d wager it has about 100 examples of optional chaining in practice. Hope it’s helpful!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Refactoring optional chaining into a large codebase: lessons learned</title>
  <link>https://lea.verou.me/2020/06/refactoring-optional-chaining-into-a-large-codebase-lessons-learned/</link>
  <pubDate>Sun, 17 May 2026 03:05:10 +0200</pubDate>
  <description>Chinese translation by Coink Wang Now that optional chaining is supported across the board , I decided to finally refactor Mavo to use it (yes, yes, we do provide a transpiled version as well for older browsers, settle down). This is a moment I have been waiting for a long time, as I think optional chaining is the single most substantial JS syntax improvement since arrow functions and template strings. Yes, I think its more significant than async/await, just because of the mere frequency of code it improves. Property access is literally everywhere . First off, what is optional chaining , in case you havent heard of it before? You know how you cant just do foo.bar.baz() without checking if foo exists, and then if foo.bar exists, and then if foo.bar.baz exists because youll get an error? So you have to do something awkward like: if (foo &amp;&amp; foo.bar &amp;&amp; foo.bar.baz) { foo.bar.baz(); } Or even: foo &amp;&amp; foo.bar &amp;&amp; foo.bar.baz &amp;&amp; foo.bar.baz(); Some even contort object destructuring to help with this . With optional chaining, you can just do this: foo?.bar?.baz?.() It supports normal property access, brackets ( foo?.[bar] ), and even function invocation ( foo?.() ). Sweet, right?? Yes, mostly. Indeed, there is SO MUCH code that can be simplified with it, its incredible. But there are a few caveats. Patterns to search for Suppose you decided to go ahead and refactor your code as well. What to look for? There is of course the obvious foo &amp;&amp; foo.bar that becomes foo?.bar . There is also the conditional version of it, that we described in the beginning of this article, which uses if() for some or all of the checks in the chain. There are also a few more patterns. Ternary foo? foo.bar : defaultValue Which can now be written as: foo?.bar || defaultValue or, using the other awesome new operator, the nullish coalescing operator : foo?.bar ?? defaultValue Array checking if (foo.length &gt; 3) { foo[2] } which now becomes: foo?.[2] Note that this is no substitute for a real array check, like the one done by Array.isArray(foo) . Do not go about replacing proper array checking with duck typing because its shorter. We stopped doing that over a decade ago . Regex match Forget about things like this: let match = &quot;#C0FFEE&quot;.match(/#([A-Z]+)/i); let hex = match &amp;&amp; match[1]; Or even things like that: let hex = (&quot;#C0FFEE&quot;.match(/#([A-Z]+)/i) || [,])[1]; Now its just: let hex = &quot;#C0FFEE&quot;.match(/#([A-Z]+)/i)?.[1]; In our case, I was able to even remove two utility functions and replace their invocations with this. Feature detection In simple cases, feature detection can be replaced by ?. . For example: if (element.prepend) element.prepend(otherElement); becomes: element.prepend?.(otherElement); Dont overdo it While it may be tempting to convert code like this: if (foo) { something(foo.bar); somethingElse(foo.baz); andOneLastThing(foo.yolo); } to this: something(foo?.bar); somethingElse(foo?.baz); andOneLastThing(foo?.yolo); Dont . Youre essentially having the JS runtime check foo three times instead of one. You may argue these things dont matter much anymore performance-wise, but its the same repetition for the human reading your code: they have to mentally process the check for foo three times instead of one. And if they need to add another statement using property access on foo , they need to add yet another check, instead of just using the conditional thats already there. Caveats You still need to check before assignment You may be tempted to convert things like: if (foo &amp;&amp; foo.bar) { foo.bar.baz = someValue; } to: foo?.bar?.baz = someValue; Unfortunately, thats not possible and will error. This was an actual snippet from our codebase: if (this.bar &amp;&amp; this.bar.edit) { this.bar.edit.textContent = this._(&quot;edit&quot;); } Which I happily refactored to: if (this.bar?.edit) { this.bar.edit.textContent = this._(&quot;edit&quot;); } All good so far, this works nicely. But then I thought, wait a second do I need the conditional at all? Maybe I can just do this: this.bar?.edit?.textContent = this._(&quot;edit&quot;); Nope. Uncaught SyntaxError: Invalid left-hand side in assignment . Cant do that. You still need the conditional. I literally kept doing this, and Im glad I had ESLint in my editor to warn me about it without having to actually run the code. Its very easy to put the ?. in the wrong place or forget some ?. Note that if youre refactoring a long chain with optional chaining, you often need to insert multiple ?. after the first one, for every member access that may or may not exist, otherwise you will get errors once the optional chaining returns undefined. Or, sometimes you may think you do, because you put the ?. in the wrong place. Take the following real example. I originally refactored this: this.children[index]? this.children[index].element : this.marker into this: this.children?.[index].element ?? this.marker then got a TypeError: Cannot read property &#39;element&#39; of undefined . Oops! Then I fixed it by adding an additional ?. : this.children?.[index]?.element ?? this.marker This works, but is superfluous, as pointed out in the comments. I just needed to move the ?. : this.children.[index]?.element ?? this.marker Note that as pointed out in the comments be careful about replacing array length checks with optional access to the index. This might be bad for performance, because out-of-bounds access on an array is de-optimizing the code in V8 (as it has to check the prototype chain for such a property too, not only decide that there is no such index in the array). It can introduce bugs if youre not careful If, like me, you go on a refactoring spree, its easy after a certain point to just introduce optional chaining in places where it actually ends up changing what your code does and introducing subtle bugs. null vs undefined Possibly the most common pattern is replacing foo &amp;&amp; foo.bar with foo?.bar . While in most cases these work equivalently, this is not true for every case. When foo is null , the former returns null , whereas the latter returns undefined . This can cause bugs to creep up in cases where the distinction matters and is probably the most common way to introduce bugs with this type of refactoring. Equality checks Be careful about converting code like this: if (foo &amp;&amp; bar &amp;&amp; foo.prop1 === bar.prop2) { /* ... */ } to code like this: if (foo?.prop1 === bar?.prop2) { /* ... */ } In the first case, the condition will not be true, unless both foo and bar are truthy. However, in the second case, if both foo and bar are nullish, the conditional will be true, because both operands will return undefined ! The same bug can creep in even if the second operand doesnt include any optional chaining, as long as it could be undefined you can get unintended matches. Operator precedence slips One thing to look out for is that optional chaining has higher precedence than &amp;&amp; . This becomes particularly significant when you replace an expression using &amp;&amp; that also involves equality checks, since the (in)equality operators are sandwiched between ?. and &amp;&amp; , having lower precedence than the former and higher than the latter. if (foo &amp;&amp; foo.bar === baz) { /* ... */ } What is compared with baz here? foo.bar or foo &amp;&amp; foo.bar ? Since &amp;&amp; has lower precedence than === , its as if we had written: if (foo &amp;&amp; (foo.bar === baz)) { /* ... */ } Note that the conditional cannot ever be executed if foo is falsy. However, once we refactor it to use optional chaining, it is now as if we were comparing ( foo &amp;&amp; foo.bar ) to baz : if (foo?.bar === baz) { /* ... */ } An obvious case where the different semantics affect execution is when baz is undefined . In that case, we can enter the conditional when foo is nullish, since then optional chaining will return undefined , which is basically the case we described above. In most other cases this doesnt make a big difference. It can however be pretty bad when instead of an equality operator, you have an inequality operator, which still has the same precedence. Compare this: if (foo &amp;&amp; foo.bar !== baz) { /* ... */ } with this: if (foo?.bar !== baz) { /* ... */ } Now, we are going to enter the conditional every time foo is nullish, as long as baz is not undefined ! The difference is not noticeable in an edge case anymore, but in the average case! Return statements Rather obvious after you think about it, but its easy to forget return statements in the heat of the moment. You cannot replace things like this: if (foo &amp;&amp; foo.bar) { return foo.bar(); } with: return foo?.bar?.(); In the first case, you return conditionally, whereas in the second case you return always. This will not introduce any issues if the conditional is the last statement in your function, but it will change the control flow if its not. Sometimes, it can fix bugs too! Take a look at this code I encountered during my refactoring: /** * Get the current value of a CSS property on an element */ getStyle: (element, property) =&gt; { if (element) { var value = getComputedStyle(element).getPropertyValue(property); if (value) { return value.trim(); } } }, Can you spot the bug? If value is an empty string (and given the context, it could very well be), the function will return undefined , because an empty string is falsy! Rewriting it to use optional chaining fixes this: if (element) { var value = getComputedStyle(element).getPropertyValue(property); return value?.trim(); } Now, if value is the empty string, it will still return an empty string and it will only return undefined when value is nullish. Finding usages becomes trickier This was pointed out by Razvan Caliman on Twitter: https://twitter.com/razvancaliman/status/1273638529399230464 Bottom line In the end, this refactor made Mavo about 2KB lighter and saved 37 lines of code. It did however make the transpiled version 79 lines and 9KB (!) heavier. Here is the relevant commit , for your perusal. I tried my best to exercise restraint and not introduce any unrelated refactoring in this commit, so that the diff is chock-full of optional chaining examples. It has 104 additions and 141 deletions, so Id wager it has about 100 examples of optional chaining in practice. Hope its helpful!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Hybrid positioning with CSS variables and max()</title>
  <link>https://lea.verou.me/2020/06/hybrid-positioning-with-css-variables-and-max/</link>
  <pubDate>Sun, 17 May 2026 03:05:09 +0200</pubDate>
  <description>Notice how the navigation on the left behaves wrt scrolling: It’s like absolute at first that becomes fixed once the header scrolls out of the viewport. One of my side projects these days is a color space agnostic color conversion &amp; manipulation library, which I’m developing together with my husband, Chris Lilley (you can see a sneak peek of its docs above). He brings his color science expertise to the table, and I bring my JS &amp; API design experience, so it’s a great match and I’m really excited about it! ( if you’re serious about color and you’re building a tool or demo that would benefit from it contact me, we need as much early feedback on the API as we can get! ) For the documentation, I wanted to have the page navigation on the side (when there is enough space), right under the header when scrolled all the way to the top, but I wanted it to scroll with the page (as if it was absolutely positioned) until the header is out of view, and then stay at the top for the rest of the scrolling (as if it used fixed positioning). It sounds very much like a case for position: sticky , doesn’t it? However, an element with position: sticky behaves like it’s relatively positioned when it’s in view and like it’s using position: fixed when its scrolled out of view but its container is still in view. What I wanted here was different. I basically wanted position: absolute while the header was in view and position: fixed after. Yes, there are ways I could have contorted position: sticky to do what I wanted, but was there another solution? In the past, we’d just go straight to JS, slap position: absolute on our element, calculate the offset in a scroll event listener and set a top CSS property on our element. However, this is flimsy and violates separation of concerns, as we now need to modify Javascript to change styling. Pass! What if instead we had access to the scroll offset in CSS? Would that be sufficient to solve our use case? Let’s find out! As I pointed out in my Increment article about CSS Variables last month, and in my CSS Variables series of talks a few years ago , we can use JS to set &amp; update CSS variables on the root that describe pure data (mouse position, input values, scroll offset etc), and then use them as-needed throughout our CSS, reaching near-perfect separation of concerns for many common cases. In this case, we write 3 lines of JS to set a --scrolltop variable: let root = document.documentElement; document.addEventListener(&quot;scroll&quot;, evt =&gt; { root.style.setProperty(&quot;--scrolltop&quot;, root.scrollTop); }); Then, we can position our navigation absolutely, and subtract var(--scrolltop) to offset any scroll ( 11rem is our header height): #toc { position: fixed; top: calc(11rem - var(--scrolltop) * 1px); } This works up to a certain point, but once scrolltop exceeds the height of the header, top becomes negative and our navigation starts drifting off screen: Just subtracting --scrolltop essentially implements absolute positioning with position: fixed . We’ve basically re-implemented absolute positioning with position: fixed , which is not very useful! What we really want is to cap the result of the calculation to 0 so that our navigation always remains visible. Wouldn’t it be great if there was a max-top attribute, just like max-width so that we could do this? One thought might be to change the JS and use Math.max() to cap --scrolltop to a specific number that corresponds to our header height. However, while this would work for this particular case, it means that --scrolltop cannot be used generically anymore, because it’s tailored to our specific use case and does not correspond to the actual scroll offset. Also, this encodes more about styling in the JS than is ideal, since the clamping we need is presentation-related — if our style was different, we may not need it anymore. But how can we do this without resorting to JS? Thankfully, we recently got implementations for probably the one feature I was pining for the most in CSS, for years: min() , max() and clamp() functions, which bring the power of min/max constraints to any CSS property! And even for width and height , they are strictly more powerful than min/max-* because you can have any number of minimums and maximums, whereas the min/max-* properties limit you to only one. While brower compatibility is actually pretty good , we can’t just use it with no fallback, since this is one of the features where lack of support can be destructive. We will provide a fallback in our base style and use @supports to conditonally override it: #toc { position: fixed; top: 11em; } @supports (top: max(1em, 1px)) { #toc { top: max(0em, 11rem - var(--scrolltop) * 1px); } } Aaand that was it, this gives us the result we wanted! And because --scrolltop is sufficiently generic, we can re-use it anywhere in our CSS where we need access to the scroll offset. I’ve actually used exactly the scame --scrolltop setting JS code in my blog, to keep the gradient centerpoint on my logo while maintaining a fixed background attachment, so that various elements can use the same background and having it appear continuous, i.e. not affected by their own background positioning area: The website header and the post header are actually different element. The background appears continuous because it’s using background-attachment: fixed , and the scrolltop variable is used to emulate background-attachment: scroll while still using the viewport as the background positioning area for both backgrounds. Appendix: Why didn’t we just use the cascade? You might wonder, why do we even need @supports ? Why not use the cascade, like we’ve always done to provide fallbacks for values without sufficiently universal support? I.e., why not just do this: #toc { position: fixed; top: 11em; top: max(0em, 11rem - var(--scrolltop) * 1px); } The reason is that when you use CSS variables, this does not work as expected. The browser doesn’t know if your property value is valid until the variable is resolved, and by then it has already processed the cascade and has thrown away any potential fallbacks. So, what would happen if we went this route and max() was not supported? Once the browser realizes that the second value is invalid due to using an unknown function, it will make the property invalid at computed value time , which essentially equates to the initial keyword, and for the top property, the initial value is 0 . This would mean your navigation would overlap the header when scrolled close to the top, which is terrible !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Hybrid positioning with CSS variables and max()</title>
  <link>https://lea.verou.me/2020/06/hybrid-positioning-with-css-variables-and-max/</link>
  <pubDate>Sun, 17 May 2026 03:05:09 +0200</pubDate>
  <description>Notice how the navigation on the left behaves wrt scrolling: Its like absolute at first that becomes fixed once the header scrolls out of the viewport. One of my side projects these days is a color space agnostic color conversion &amp; manipulation library, which Im developing together with my husband, Chris Lilley (you can see a sneak peek of its docs above). He brings his color science expertise to the table, and I bring my JS &amp; API design experience, so its a great match and Im really excited about it! ( if youre serious about color and youre building a tool or demo that would benefit from it contact me, we need as much early feedback on the API as we can get! ) For the documentation, I wanted to have the page navigation on the side (when there is enough space), right under the header when scrolled all the way to the top, but I wanted it to scroll with the page (as if it was absolutely positioned) until the header is out of view, and then stay at the top for the rest of the scrolling (as if it used fixed positioning). It sounds very much like a case for position: sticky , doesnt it? However, an element with position: sticky behaves like its relatively positioned when its in view and like its using position: fixed when its scrolled out of view but its container is still in view. What I wanted here was different. I basically wanted position: absolute while the header was in view and position: fixed after. Yes, there are ways I could have contorted position: sticky to do what I wanted, but was there another solution? In the past, wed just go straight to JS, slap position: absolute on our element, calculate the offset in a scroll event listener and set a top CSS property on our element. However, this is flimsy and violates separation of concerns, as we now need to modify Javascript to change styling. Pass! What if instead we had access to the scroll offset in CSS? Would that be sufficient to solve our use case? Lets find out! As I pointed out in my Increment article about CSS Variables last month, and in my CSS Variables series of talks a few years ago , we can use JS to set &amp; update CSS variables on the root that describe pure data (mouse position, input values, scroll offset etc), and then use them as-needed throughout our CSS, reaching near-perfect separation of concerns for many common cases. In this case, we write 3 lines of JS to set a --scrolltop variable: let root = document.documentElement; document.addEventListener(&quot;scroll&quot;, evt =&gt; { root.style.setProperty(&quot;--scrolltop&quot;, root.scrollTop); }); Then, we can position our navigation absolutely, and subtract var(--scrolltop) to offset any scroll ( 11rem is our header height): #toc { position: fixed; top: calc(11rem - var(--scrolltop) * 1px); } This works up to a certain point, but once scrolltop exceeds the height of the header, top becomes negative and our navigation starts drifting off screen: Just subtracting --scrolltop essentially implements absolute positioning with position: fixed . Weve basically re-implemented absolute positioning with position: fixed , which is not very useful! What we really want is to cap the result of the calculation to 0 so that our navigation always remains visible. Wouldnt it be great if there was a max-top attribute, just like max-width so that we could do this? One thought might be to change the JS and use Math.max() to cap --scrolltop to a specific number that corresponds to our header height. However, while this would work for this particular case, it means that --scrolltop cannot be used generically anymore, because its tailored to our specific use case and does not correspond to the actual scroll offset. Also, this encodes more about styling in the JS than is ideal, since the clamping we need is presentation-related if our style was different, we may not need it anymore. But how can we do this without resorting to JS? Thankfully, we recently got implementations for probably the one feature I was pining for the most in CSS, for years: min() , max() and clamp() functions, which bring the power of min/max constraints to any CSS property! And even for width and height , they are strictly more powerful than min/max-* because you can have any number of minimums and maximums, whereas the min/max-* properties limit you to only one. While brower compatibility is actually pretty good , we cant just use it with no fallback, since this is one of the features where lack of support can be destructive. We will provide a fallback in our base style and use @supports to conditonally override it: #toc { position: fixed; top: 11em; } @supports (top: max(1em, 1px)) { #toc { top: max(0em, 11rem - var(--scrolltop) * 1px); } } Aaand that was it, this gives us the result we wanted! And because --scrolltop is sufficiently generic, we can re-use it anywhere in our CSS where we need access to the scroll offset. Ive actually used exactly the scame --scrolltop setting JS code in my blog, to keep the gradient centerpoint on my logo while maintaining a fixed background attachment, so that various elements can use the same background and having it appear continuous, i.e. not affected by their own background positioning area: The website header and the post header are actually different element. The background appears continuous because its using background-attachment: fixed , and the scrolltop variable is used to emulate background-attachment: scroll while still using the viewport as the background positioning area for both backgrounds. Appendix: Why didnt we just use the cascade? You might wonder, why do we even need @supports ? Why not use the cascade, like weve always done to provide fallbacks for values without sufficiently universal support? I.e., why not just do this: #toc { position: fixed; top: 11em; top: max(0em, 11rem - var(--scrolltop) * 1px); } The reason is that when you use CSS variables, this does not work as expected. The browser doesnt know if your property value is valid until the variable is resolved, and by then it has already processed the cascade and has thrown away any potential fallbacks. So, what would happen if we went this route and max() was not supported? Once the browser realizes that the second value is invalid due to using an unknown function, it will make the property invalid at computed value time , which essentially equates to the initial keyword, and for the top property, the initial value is 0 . This would mean your navigation would overlap the header when scrolled close to the top, which is terrible !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>New decade, new theme</title>
  <link>https://lea.verou.me/2020/06/new-decade-new-theme/</link>
  <pubDate>Sun, 17 May 2026 03:05:08 +0200</pubDate>
  <description>It has been almost a decade since this blog last saw a redesign . This blog’s theme 2011 - 2020. RIP! In these 9 years, my life changed dramatically. I joined and left W3C , joined the CSS WG , went to MIT for a PhD , published a book , got married , had a baby , among other things. I designed dozens of websites for dozens of projects, but this theme remained constant, with probably a hasty tweak here and there but nothing more than that. Even its mobile version was a few quick media queries to make it palatable on mobile. To put this into perspective, when I designed that theme: CSS gradients were still cutting edge We were still using browser prefixes all over the place RSS was still a thing that websites advertised Skeuomorphism was all the rage Websites were desktop first, and often desktop-only. Opera was a browser we tested in. IE8 was the latest IE version. It didn’t support SVG, gradients, border-radius, shadows, web fonts (except .eot), transforms, , , We were still hacking layout with floats, clearfix and overflow: hidden Over the course of these years, I kept saying “I need to update my website’s theme”, but never got around to it, there was always something more high priority. The stroke that broke the camel’s back was this Monday. I came up with a nice CSS tip on another website I was working on, and realized I was hesitating to blog about it because I was embarrassed at how my website looked. This is it, I thought. If it has gotten so bad that I avoid blogging because I don’t want people to be reminded of how old my website looks, I need to get my shit together and fix this, I told myself. My plan was to design something entirely from scratch, like I had done the previous time (the previous theme used a blank HTML5 starter theme as its only starting point). However, when I previewed the new Wordpress default ( Twenty Twenty ), I fell in love, especially with its typography: it used a very Helvetica-esque variable font as its heading typeface, and Hoefler Text for body text. 😍 It would surely be very convenient to be able to adapt an existing theme, but on the other hand, isn’t it embarrassing to be known for CSS and use the default theme or something close to it? In the end, I kept the things I liked about it and it certainly still looks a lot like Twenty Twenty, but I think I’ve made enough tweaks that it’s also very Lea . And of course there are animated conic gradients in it, because duh. 😂 Do keep in mind that this is just a day’s work, so it will be rough around the edges and still very much a work in progress. Let me know about any issues you find in the comments! PS: Yes, yes, I will eventually get around to enforcing https:// !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>New decade, new theme</title>
  <link>https://lea.verou.me/2020/06/new-decade-new-theme/</link>
  <pubDate>Sun, 17 May 2026 03:05:08 +0200</pubDate>
  <description>It has been almost a decade since this blog last saw a redesign . This blogs theme 2011 - 2020. RIP! In these 9 years, my life changed dramatically. I joined and left W3C , joined the CSS WG , went to MIT for a PhD , published a book , got married , had a baby , among other things. I designed dozens of websites for dozens of projects, but this theme remained constant, with probably a hasty tweak here and there but nothing more than that. Even its mobile version was a few quick media queries to make it palatable on mobile. To put this into perspective, when I designed that theme: CSS gradients were still cutting edge We were still using browser prefixes all over the place RSS was still a thing that websites advertised Skeuomorphism was all the rage Websites were desktop first, and often desktop-only. Opera was a browser we tested in. IE8 was the latest IE version. It didnt support SVG, gradients, border-radius, shadows, web fonts (except .eot), transforms, , , We were still hacking layout with floats, clearfix and overflow: hidden Over the course of these years, I kept saying I need to update my websites theme, but never got around to it, there was always something more high priority. The stroke that broke the camels back was this Monday. I came up with a nice CSS tip on another website I was working on, and realized I was hesitating to blog about it because I was embarrassed at how my website looked. This is it, I thought. If it has gotten so bad that I avoid blogging because I dont want people to be reminded of how old my website looks, I need to get my shit together and fix this, I told myself. My plan was to design something entirely from scratch, like I had done the previous time (the previous theme used a blank HTML5 starter theme as its only starting point). However, when I previewed the new Wordpress default ( Twenty Twenty ), I fell in love, especially with its typography: it used a very Helvetica-esque variable font as its heading typeface, and Hoefler Text for body text. It would surely be very convenient to be able to adapt an existing theme, but on the other hand, isnt it embarrassing to be known for CSS and use the default theme or something close to it? In the end, I kept the things I liked about it and it certainly still looks a lot like Twenty Twenty, but I think Ive made enough tweaks that its also very Lea . And of course there are animated conic gradients in it, because duh. Do keep in mind that this is just a days work, so it will be rough around the edges and still very much a work in progress. Let me know about any issues you find in the comments! PS: Yes, yes, I will eventually get around to enforcing https:// !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Today&#39;s Javascript, from an outsider&#39;s perspective</title>
  <link>https://lea.verou.me/2020/05/todays-javascript-from-an-outsiders-perspective/</link>
  <pubDate>Sun, 17 May 2026 03:05:06 +0200</pubDate>
  <description>Today I tried to help a friend who is a great computer scientist, but not a JS person use a JS module he found on Github. Since for the past 6 years my day job is doing usability research &amp; teaching at MIT, I couldn’t help but cringe at the slog that this was. Lo and behold, a pile of unnecessary error conditions, cryptic errors, and lack of proper feedback. And I don’t feel I did a good job communicating the frustration he went through in the one hour or so until he gave up. It went a bit like this… Note: N_ames of packages and people have been changed to protect their identity. I’ve also omitted a few issues he faced that were too specific to the package at hand. Some of the errors are reconstructed from memory, so let me know if I got anything wrong!_ John: Hey, I want to try out this algorithm I found on Github, it says to use import functionName from packageName and then call functionName(arguments) . Seems simple enough! I don’t really need a UI, so I’m gonna use Node! Lea: Sure, Node seems appropriate for this! John runs npm install packageName --save as recommended by the package’s README John runs node index.js Node: Warning: To load an ES module, set “type”: “module” in the package.json or use the .mjs extension. SyntaxError: Cannot use import statement outside a module John: But I don’t have a package.json… Lea: Run npm init , it will generate it for you! John runs npm init , goes through the wizard, adds type: &quot;module&quot; manually to the generated package.json. John runs node index.js Node: SyntaxError: Cannot use import statement outside a module Oddly, the error was thrown from an internal module of the project this time. WAT?! Lea: Ok, screw this, just run it in a browser, it’s an ES6 module and it’s just a pure JS algorithm that doesn’t use any Node APIs, it should work. John makes a simple index.html with a John loads index.html in a browser Nothing in the console. Nada. Crickets. 🦗 Lea: Oh, you need to adjust your module path to import packageName. Node does special stuff to resolve based on node_modules , now you’re in a browser you need to specify an explicit path yourself. John looks, at his filesystem, but there was no node_modules directory. Lea: Oh, you ran npm install before you had a package.json , that’s probably it! Try it again! John runs npm install packageName --save again John: Oh yeah, there is a node_modules now! John desperately looks in node_modules to find the entry point John edits his index.js accordingly, reloads index.html Firefox: Incorrect MIME type: text/html Lea: Oh, you’re in file:// ! Dude, what are you doing these days without a localhost? Javascript is severely restricted in file:// today. John: But why do I… ok fine, I’m going to start a localhost. John starts localhost , visits his index.html under http://localhost:80 Firefox: Incorrect MIME type: text/html John: Sigh. Do I need to configure my localhost to serve JS files with a text/javascript MIME type? Lea: What? No! It knows this. Um… look at the Networks tab, I suspect it can’t find your module, so it’s returning an HTML page for the 404, then it complains because the MIME type of the error page is not text/javascript . Looks at node_modules again, corrects path. Turns out VS Code collapses folders with only 1 subfolder, which is why we hadn’t noticed . FWIW I do think this is a good usability improvement on VS Code’s behalf, it improves efficiency, but they need to make it more visible that this is what has happened. Firefox: SyntaxError: missing ) after formal parameters Lea: What? That’s coming from the package source, it’s not your fault. I don’t understand… can we look at this line? John clicks at line throwing the error Lea: Oh my goodness. This is not Javascript, it’s Typescript!! With a .js extension!! John: I just wanted to run one line of code to test this algorithm… 😭😭😭 John gives up . Concludes never to touch Node, npm, or ES6 modules with a barge pole. The End. Note that John is a computer scientist that knows a fair bit about the Web: He had Node &amp; npm installed, he knew what MIME types are, he could start a localhost when needed. What hope do actual novices have?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Today&#39;s Javascript, from an outsider&#39;s perspective</title>
  <link>https://lea.verou.me/2020/05/todays-javascript-from-an-outsiders-perspective/</link>
  <pubDate>Sun, 17 May 2026 03:05:06 +0200</pubDate>
  <description>Today I tried to help a friend who is a great computer scientist, but not a JS person use a JS module he found on Github. Since for the past 6 years my day job is doing usability research &amp; teaching at MIT, I couldnt help but cringe at the slog that this was. Lo and behold, a pile of unnecessary error conditions, cryptic errors, and lack of proper feedback. And I dont feel I did a good job communicating the frustration he went through in the one hour or so until he gave up. It went a bit like this Note: N_ames of packages and people have been changed to protect their identity. Ive also omitted a few issues he faced that were too specific to the package at hand. Some of the errors are reconstructed from memory, so let me know if I got anything wrong!_ John: Hey, I want to try out this algorithm I found on Github, it says to use import functionName from packageName and then call functionName(arguments) . Seems simple enough! I dont really need a UI, so Im gonna use Node! Lea: Sure, Node seems appropriate for this! John runs npm install packageName --save as recommended by the packages README John runs node index.js Node: Warning: To load an ES module, set type: module in the package.json or use the .mjs extension. SyntaxError: Cannot use import statement outside a module John: But I dont have a package.json Lea: Run npm init , it will generate it for you! John runs npm init , goes through the wizard, adds type: &quot;module&quot; manually to the generated package.json. John runs node index.js Node: SyntaxError: Cannot use import statement outside a module Oddly, the error was thrown from an internal module of the project this time. WAT?! Lea: Ok, screw this, just run it in a browser, its an ES6 module and its just a pure JS algorithm that doesnt use any Node APIs, it should work. John makes a simple index.html with a John loads index.html in a browser Nothing in the console. Nada. Crickets. Lea: Oh, you need to adjust your module path to import packageName. Node does special stuff to resolve based on node_modules , now youre in a browser you need to specify an explicit path yourself. John looks, at his filesystem, but there was no node_modules directory. Lea: Oh, you ran npm install before you had a package.json , thats probably it! Try it again! John runs npm install packageName --save again John: Oh yeah, there is a node_modules now! John desperately looks in node_modules to find the entry point John edits his index.js accordingly, reloads index.html Firefox: Incorrect MIME type: text/html Lea: Oh, youre in file:// ! Dude, what are you doing these days without a localhost? Javascript is severely restricted in file:// today. John: But why do I ok fine, Im going to start a localhost. John starts localhost , visits his index.html under http://localhost:80 Firefox: Incorrect MIME type: text/html John: Sigh. Do I need to configure my localhost to serve JS files with a text/javascript MIME type? Lea: What? No! It knows this. Um look at the Networks tab, I suspect it cant find your module, so its returning an HTML page for the 404, then it complains because the MIME type of the error page is not text/javascript . Looks at node_modules again, corrects path. Turns out VS Code collapses folders with only 1 subfolder, which is why we hadnt noticed . FWIW I do think this is a good usability improvement on VS Codes behalf, it improves efficiency, but they need to make it more visible that this is what has happened. Firefox: SyntaxError: missing ) after formal parameters Lea: What? Thats coming from the package source, its not your fault. I dont understand can we look at this line? John clicks at line throwing the error Lea: Oh my goodness. This is not Javascript, its Typescript!! With a .js extension!! John: I just wanted to run one line of code to test this algorithm John gives up . Concludes never to touch Node, npm, or ES6 modules with a barge pole. The End. Note that John is a computer scientist that knows a fair bit about the Web: He had Node &amp; npm installed, he knew what MIME types are, he could start a localhost when needed. What hope do actual novices have?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>LCH colors in CSS: what, why, and how?</title>
  <link>https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/</link>
  <pubDate>Sun, 17 May 2026 03:05:05 +0200</pubDate>
  <description>I was always interested in color science. In 2014, I gave a talk about CSS Color 4 at various conferences around the world called “The Chroma Zone” . Even before that, in 2009, I wrote a color picker that used a hidden Java applet to support ICC color profiles to do CMYK properly, a first on the Web at the time (to my knowledge). I never released it, but it sparked this angry rant . Color is also how I originally met my now husband, Chris Lilley: In my first CSS WG meeting in 2012, he approached me to ask a question about CSS and Greek, and once he introduced himself I said “You’re Chris Lilley, the color expert?!? I have questions for you!” . I later discovered that he had done even more cool things (he was a co-author of PNG and started SVG 🤯), but at the time, I only knew of him as “the W3C color expert”, that’s how much into color I was (I got my color questions answered much later, in 2015 that we actually got together) . My interest in color science was renewed in 2019, after I became co-editor of CSS Color 5 , with the goal of fleshing out my color modification proposal , which aims to allow arbitrary tweaking of color channels to create color variations, and combine it with Una’s color modification proposal . LCH colors in CSS is something I’m very excited about, and I strongly believe designers would be outraged we don’t have them yet if they knew more about them. What is LCH? CSS Color 4 defines lch() colors , among other things, and as of recently, all major browsers have started implementing them or are seriously considering it: Safari is already implementing , Chrome is about to , and Firefox is discussing it . LCH is a color space that has several advantages over the RGB/HSL colors we’re familiar with in CSS. In fact, I’d go as far as to call it a game-changer , and here’s why. 1. We actually get access to about 50% more colors. This is huge. Currently, every CSS color we can specify, is defined to be in the sRGB color space . This was more than sufficient a few years ago, since all but professional monitors had gamuts smaller than sRGB. However, that’s not true any more. Today, the gamut (range of possible colors displayed) of most monitors is closer to P3 , which has a 50% larger volume than sRGB . CSS right now cannot access these colors at all . Let me repeat: We have no access to one third of the colors in most modern monitors. And these are not just any colors, but the most vivid colors the screen can display . Our websites are washed out because monitor hardware evolved faster than CSS specs and browser implementations. Gamut volume of sRGB vs P3 2. LCH (and Lab) is perceptually uniform In LCH, the same numerical change in coordinates produces the same perceptual color difference. This property of a color space is called “perceptual uniformity”. RGB or HSL are not perceptually uniform. A very illustrative example is the following [ example source ]: Both the colors in the first row, as well as the colors in the second row, only differ by 20 degrees in hue. Is the perceptual difference between them equal? 3. LCH lightness actually means something In HSL, lightness is meaningless. Colors can have the same lightness value, with wildly different perceptual lightness. My favorite examples are yellow and blue. Believe it or not, both have the same HSL lightness! Both of these colors have a lightness of 50%, but they are most certainly not equally light. What does HSL lightness actually mean then? You might argue that at least lightness means something for constant hue and saturation, i.e. for adjustments within the same color. It is true that we do get a lighter color if we increase the HSL lightness and a darker one if we decrease it, but it’s not necessarily the same color: Both of these have the same hue and saturation, but do they really look like darker and lighter variants of the same color? With LCH, any colors with the same lightness are equally perceptually light, and any colors with the same chroma are equally perceptually saturated. How does LCH work? LCH stands for “Lightness Chroma Hue”. The parameters loosely correspond to HSL’s, however there are a few crucial differences: The hue angles don’t fully correspond to HSL’s hues. E.g. 0 is not red, but more of a magenta and 180 is not turquoise but more of a bluish green, and is exactly complementary. Note how these colors, while wildly different in hue, perceptually have the same lightness. In HSL, saturation is a neat 0-100 percentage, since it’s a simple transformation of RGB into polar coordinates. In LCH however, Chroma is theoretically unbounded . LCH (like Lab) is designed to be able to represent the entire spectrum of human vision, and not all of these colors can be displayed by a screen, even a P3 screen. Not only is the maximum chroma different depending on screen gamut, it’s actually different per color. This may be better understood with an example. For simplicity, assume you have a screen whose gamut exactly matches the sRGB color space (for comparison, the screen of a 2013 MacBook Air was about 60% of sRGB, although most modern screens are about 150% of sRGB, as discussed above). For L=50 H=180 (the cyan above), the maximum Chroma is only 35! For L=50 H=0 (the magenta above), Chroma can go up to 77 without exceeding the boundaries of sRGB. For L=50 H=320 (the purple above), it can go up to 108! While the lack of boundaries can be somewhat unsettling (in people and in color spaces) , don’t worry: if you specify a color that is not displayable in a given monitor, it will be scaled down so that it becomes visible while preserving its essence. After all, that’s not new: before monitors got gamuts wider than sRGB, this is what was happening with regular CSS colors when they were displayed in monitors with gamuts smaller than sRGB. An LCH color picker Hopefully, you are now somewhat excited about LCH, but how to visualize it? I actually made this a while ago, primarily to help me, Chris , Adam , and Una in wrapping our heads around LCH sufficiently to edit CSS Color 5 . It’s different to know the theory, and it’s different to be able to play with sliders and see the result. I even bought a domain, css.land , to host similar demos eventually. We used it a fair bit, and Chris got me to add a few features too, but I never really posted about it, so it was only accessible to us, and anybody that noticed its Github repo . Why not just use an existing LCH color picker? The conversion code for this is written by Chris, and he was confident the math is at least intended to be correct (i.e. if it’s wrong it’s a bug in the code, not a gap in understanding) The Chroma is not 0-100 like in some color pickers we found We wanted to allow inputting arbitrary CSS colors (the “Import…” button above) We wanted to allow inputting decimals (the sliders only do integers, but the black number inputs allow any number) I wanted to be able to store colors, and see how they interpolate. We wanted to be able to see whether the LCH color was within sRGB, P3, (or Rec.2020, an even larger color space). We wanted alpha And lastly, because it’s fun! Especially since it’s implemented with Mavo (and a little bit of JS, this is not a pure Mavo HTML demo). Recently, Chris posted it in a whatwg/html issue thread and many people discovered it, so it nudged me to post about it, so, here it is: css.land/lch FAQ Based on the questions I got after I posted this article, I should clarify a few common misconceptions. “You said that these colors are not implemented yet, but I see them in your article” All of the colors displayed in this article are within the sRGB gamut, exactly because we can’t display those outside it yet. sRGB is a color space, not a syntax. E.g. rgb(255 0 0) and lch(54.292% 106.839 40.853) specify the same color. “How does the LCH picker display colors outside sRGB?” It doesn’t. Neither does any other on the Web (to my knowledge). The color picker is implemented with web technologies, and therefore suffers from the same issues. It has to scale them down to display something similar, that is within sRGB (it used to just clip the RGB components to 0-100%, but thanks to this PR from Tab it now uses a far superior algorithm: it just reduces the Chroma until the color is within sRGB). This is why increasing the Chroma doesn’t produce a brighter color beyond a certain point: because that color cannot be displayed with CSS right now. “I’ve noticed that Firefox displays more vivid colors than Chrome and Safari, is that related?” Firefox does not implement the spec that restricts CSS colors to sRGB. Instead, it just throws the raw RGB coordinates on the screen, so e.g. rgb(100% 0% 0%) is the brightest red your screen can display. While this may seem like a superior solution, it’s incredibly inconsistent: specifying a color is approximate at best, since every screen displays it differently. By restricting CSS colors to a known color space (sRGB) we gained device independence. LCH and Lab are also device independent as they are based on actual measured color. What about color(display-p3 r g b)? Safari supports that since 2017! I was notified of this after I posted this article. I was aware Safari was implementing this syntax a while ago, but somehow missed that they shipped it. In fact, WebKit published an article about this syntax last month! How exciting! color(colorspaceid params) is another syntax added by CSS Color 4 and is the swiss army knife of color management in CSS: in its full glory it allows specifying an ICC color profile and colors from it (e.g. you want real CMYK colors on a webpage? You want Pantone? With color profiles, you can do that too!). It also supports some predefined color spaces, of which display-p3 is one. So, for example, color(display-p3 0 1 0) gives us the brightest green in the P3 color space. You can use this test case to test support: you’ll see red if color() is not supported and bright green if it is. Exciting as it may be (and I should tweak the color picker to use it when available!), do note that it only addresses the first issue I mentioned: getting to all gamut colors. However, since it’s RGB-based, it still suffers from the other issues of RGB. It is not perceptually uniform, and is difficult to create variants (lighter or darker, more or less vivid etc) by tweaking its parameters. Furthermore, it’s a short-term solution. It works now, because screens that can display a wider gamut than P3 are rare. Once hardware advances again, color(display-p3 ...) will have the same problem as sRGB colors have today. LCH and Lab are device independent, and can represent the entire gamut of human vision so they will work regardless of how hardware advances. How does LCH relate to the Lab color space that I know from Photoshop and other applications? LCH is the same color space as Lab, just viewed differently! Take a look at the following diagram that I made for my students: The L in Lab and LCH is exactly the same (perceptual Lightness). For a given lightness L, in Lab, a color has cartesian coordinates (L, a, b) and polar coordinates (L, C, H). Chroma is just the length of the line from 0 to point (a, b) and Hue is the angle of that ray. Therefore, the formulae to convert Lab to LCH are trivial one liners: C is sqrt(a² + b²) and H is atan(b/a) (with different handling if a = 0). atan() is just the reverse of tan(), i.e. tan(H) = b/a.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>LCH colors in CSS: what, why, and how?</title>
  <link>https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/</link>
  <pubDate>Sun, 17 May 2026 03:05:05 +0200</pubDate>
  <description>I was always interested in color science. In 2014, I gave a talk about CSS Color 4 at various conferences around the world called The Chroma Zone . Even before that, in 2009, I wrote a color picker that used a hidden Java applet to support ICC color profiles to do CMYK properly, a first on the Web at the time (to my knowledge). I never released it, but it sparked this angry rant . Color is also how I originally met my now husband, Chris Lilley: In my first CSS WG meeting in 2012, he approached me to ask a question about CSS and Greek, and once he introduced himself I said Youre Chris Lilley, the color expert?!? I have questions for you! . I later discovered that he had done even more cool things (he was a co-author of PNG and started SVG ), but at the time, I only knew of him as the W3C color expert, thats how much into color I was (I got my color questions answered much later, in 2015 that we actually got together) . My interest in color science was renewed in 2019, after I became co-editor of CSS Color 5 , with the goal of fleshing out my color modification proposal , which aims to allow arbitrary tweaking of color channels to create color variations, and combine it with Unas color modification proposal . LCH colors in CSS is something Im very excited about, and I strongly believe designers would be outraged we dont have them yet if they knew more about them. What is LCH? CSS Color 4 defines lch() colors , among other things, and as of recently, all major browsers have started implementing them or are seriously considering it: Safari is already implementing , Chrome is about to , and Firefox is discussing it . LCH is a color space that has several advantages over the RGB/HSL colors were familiar with in CSS. In fact, Id go as far as to call it a game-changer , and heres why. 1. We actually get access to about 50% more colors. This is huge. Currently, every CSS color we can specify, is defined to be in the sRGB color space . This was more than sufficient a few years ago, since all but professional monitors had gamuts smaller than sRGB. However, thats not true any more. Today, the gamut (range of possible colors displayed) of most monitors is closer to P3 , which has a 50% larger volume than sRGB . CSS right now cannot access these colors at all . Let me repeat: We have no access to one third of the colors in most modern monitors. And these are not just any colors, but the most vivid colors the screen can display . Our websites are washed out because monitor hardware evolved faster than CSS specs and browser implementations. Gamut volume of sRGB vs P3 2. LCH (and Lab) is perceptually uniform In LCH, the same numerical change in coordinates produces the same perceptual color difference. This property of a color space is called perceptual uniformity. RGB or HSL are not perceptually uniform. A very illustrative example is the following [ example source ]: Both the colors in the first row, as well as the colors in the second row, only differ by 20 degrees in hue. Is the perceptual difference between them equal? 3. LCH lightness actually means something In HSL, lightness is meaningless. Colors can have the same lightness value, with wildly different perceptual lightness. My favorite examples are yellow and blue. Believe it or not, both have the same HSL lightness! Both of these colors have a lightness of 50%, but they are most certainly not equally light. What does HSL lightness actually mean then? You might argue that at least lightness means something for constant hue and saturation, i.e. for adjustments within the same color. It is true that we do get a lighter color if we increase the HSL lightness and a darker one if we decrease it, but its not necessarily the same color: Both of these have the same hue and saturation, but do they really look like darker and lighter variants of the same color? With LCH, any colors with the same lightness are equally perceptually light, and any colors with the same chroma are equally perceptually saturated. How does LCH work? LCH stands for Lightness Chroma Hue. The parameters loosely correspond to HSLs, however there are a few crucial differences: The hue angles dont fully correspond to HSLs hues. E.g. 0 is not red, but more of a magenta and 180 is not turquoise but more of a bluish green, and is exactly complementary. Note how these colors, while wildly different in hue, perceptually have the same lightness. In HSL, saturation is a neat 0-100 percentage, since its a simple transformation of RGB into polar coordinates. In LCH however, Chroma is theoretically unbounded . LCH (like Lab) is designed to be able to represent the entire spectrum of human vision, and not all of these colors can be displayed by a screen, even a P3 screen. Not only is the maximum chroma different depending on screen gamut, its actually different per color. This may be better understood with an example. For simplicity, assume you have a screen whose gamut exactly matches the sRGB color space (for comparison, the screen of a 2013 MacBook Air was about 60% of sRGB, although most modern screens are about 150% of sRGB, as discussed above). For L=50 H=180 (the cyan above), the maximum Chroma is only 35! For L=50 H=0 (the magenta above), Chroma can go up to 77 without exceeding the boundaries of sRGB. For L=50 H=320 (the purple above), it can go up to 108! While the lack of boundaries can be somewhat unsettling (in people and in color spaces) , dont worry: if you specify a color that is not displayable in a given monitor, it will be scaled down so that it becomes visible while preserving its essence. After all, thats not new: before monitors got gamuts wider than sRGB, this is what was happening with regular CSS colors when they were displayed in monitors with gamuts smaller than sRGB. An LCH color picker Hopefully, you are now somewhat excited about LCH, but how to visualize it? I actually made this a while ago, primarily to help me, Chris , Adam , and Una in wrapping our heads around LCH sufficiently to edit CSS Color 5 . Its different to know the theory, and its different to be able to play with sliders and see the result. I even bought a domain, css.land , to host similar demos eventually. We used it a fair bit, and Chris got me to add a few features too, but I never really posted about it, so it was only accessible to us, and anybody that noticed its Github repo . Why not just use an existing LCH color picker? The conversion code for this is written by Chris, and he was confident the math is at least intended to be correct (i.e. if its wrong its a bug in the code, not a gap in understanding) The Chroma is not 0-100 like in some color pickers we found We wanted to allow inputting arbitrary CSS colors (the Import button above) We wanted to allow inputting decimals (the sliders only do integers, but the black number inputs allow any number) I wanted to be able to store colors, and see how they interpolate. We wanted to be able to see whether the LCH color was within sRGB, P3, (or Rec.2020, an even larger color space). We wanted alpha And lastly, because its fun! Especially since its implemented with Mavo (and a little bit of JS, this is not a pure Mavo HTML demo). Recently, Chris posted it in a whatwg/html issue thread and many people discovered it, so it nudged me to post about it, so, here it is: css.land/lch FAQ Based on the questions I got after I posted this article, I should clarify a few common misconceptions. You said that these colors are not implemented yet, but I see them in your article All of the colors displayed in this article are within the sRGB gamut, exactly because we cant display those outside it yet. sRGB is a color space, not a syntax. E.g. rgb(255 0 0) and lch(54.292% 106.839 40.853) specify the same color. How does the LCH picker display colors outside sRGB? It doesnt. Neither does any other on the Web (to my knowledge). The color picker is implemented with web technologies, and therefore suffers from the same issues. It has to scale them down to display something similar, that is within sRGB (it used to just clip the RGB components to 0-100%, but thanks to this PR from Tab it now uses a far superior algorithm: it just reduces the Chroma until the color is within sRGB). This is why increasing the Chroma doesnt produce a brighter color beyond a certain point: because that color cannot be displayed with CSS right now. Ive noticed that Firefox displays more vivid colors than Chrome and Safari, is that related? Firefox does not implement the spec that restricts CSS colors to sRGB. Instead, it just throws the raw RGB coordinates on the screen, so e.g. rgb(100% 0% 0%) is the brightest red your screen can display. While this may seem like a superior solution, its incredibly inconsistent: specifying a color is approximate at best, since every screen displays it differently. By restricting CSS colors to a known color space (sRGB) we gained device independence. LCH and Lab are also device independent as they are based on actual measured color. What about color(display-p3 r g b)? Safari supports that since 2017! I was notified of this after I posted this article. I was aware Safari was implementing this syntax a while ago, but somehow missed that they shipped it. In fact, WebKit published an article about this syntax last month! How exciting! color(colorspaceid params) is another syntax added by CSS Color 4 and is the swiss army knife of color management in CSS: in its full glory it allows specifying an ICC color profile and colors from it (e.g. you want real CMYK colors on a webpage? You want Pantone? With color profiles, you can do that too!). It also supports some predefined color spaces, of which display-p3 is one. So, for example, color(display-p3 0 1 0) gives us the brightest green in the P3 color space. You can use this test case to test support: youll see red if color() is not supported and bright green if it is. Exciting as it may be (and I should tweak the color picker to use it when available!), do note that it only addresses the first issue I mentioned: getting to all gamut colors. However, since its RGB-based, it still suffers from the other issues of RGB. It is not perceptually uniform, and is difficult to create variants (lighter or darker, more or less vivid etc) by tweaking its parameters. Furthermore, its a short-term solution. It works now, because screens that can display a wider gamut than P3 are rare. Once hardware advances again, color(display-p3 ...) will have the same problem as sRGB colors have today. LCH and Lab are device independent, and can represent the entire gamut of human vision so they will work regardless of how hardware advances. How does LCH relate to the Lab color space that I know from Photoshop and other applications? LCH is the same color space as Lab, just viewed differently! Take a look at the following diagram that I made for my students: The L in Lab and LCH is exactly the same (perceptual Lightness). For a given lightness L, in Lab, a color has cartesian coordinates (L, a, b) and polar coordinates (L, C, H). Chroma is just the length of the line from 0 to point (a, b) and Hue is the angle of that ray. Therefore, the formulae to convert Lab to LCH are trivial one liners: C is sqrt(a + b) and H is atan(b/a) (with different handling if a = 0). atan() is just the reverse of tan(), i.e. tan(H) = b/a.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Issue closing stats for any repo</title>
  <link>https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/</link>
  <pubDate>Sun, 17 May 2026 03:05:03 +0200</pubDate>
  <description>tl;dr: If you just want to quickly get stats for a repo, you can find the app here . The rest of this post explains how it’s built with Mavo HTML, CSS, and 0 lines of JS. Or, if you’d prefer, you can just View Source — it’s all there! The finished app we’re going to make, find it at https://projects.verou.me/issue-closing One of the cool things about Mavo is how it enables one to quickly build apps that utilize the Github API. At some point I wanted to compute stats about how quickly (or rather, slowly…) Github issues are closed in the Mavo repo . And what better way to build this than a Mavo app? It was fairly easy to build a prototype for that. Displaying a list of the last 100 closed issues and the time it took to close them To render the last 100 closed issues in the Mavo app, I first looked up the appropriate API call in Github’s API documentation , then used it in the mv-source attribute on the Mavo root , i.e. the element with mv-app that encompasses everything in my app: Then, I displayed a list of these issues with: #[number] took [closed_at - created_at] ms See the Pen Step 1 - Issue Closing App Tutorial by Lea Verou ( @leaverou ) on CodePen . This would work, but the way it displays results is not very user friendly (e.g. “#542 took 149627000 ms” ). We need to display the result in a more readable way. We can use the duration() function to display a readable duration such as “1 day”: #[number] took [duration(closed_at - created_at)] See the Pen Step 2 - Issue Closing App Tutorial by Lea Verou ( @leaverou ) on CodePen . Displaying aggregate statistics However, a list of issues is not very easy to process. What’s the overall picture? Does this repo close issues fast or not? Time for some statistics! We want to calculate average, median, minimum and maximum issue closing time. To calculate these statistics, we need to use the times we have displayed in the previous step. First, we need to give our calculation a name, so we can refer to its value in expressions: [duration(closed_at - created_at)] However, as it currently stands, the value of this property is text (e.g. “1 day”, “2 months” etc). We cannot compute averages and medians on text! We need the property value to be a number. We can hide the actual raw value in an attribute and use the nicely formatted value as the visible content of the element, like so (we use the content attribute here but you can use any, e.g. a data-* attribute would work just as well): [duration(timeToClose)] Note: There is a data formatting feature in the works which would simplify this kind of thing by allowing you to separate the raw value and its presentation without having to use separate attributes for them. We can also add a class to color it red, green, or black depending on whether the time is longer than a month, shorter than a day, or in-between respectively: month(), &#39;long&#39;, if (timeToClose [duration(timeToClose)] Now, on to calculate our statistics! We take advantage of the fact that timeToClose outside the issue collection gives us all the times, so we can compute aggregates on them. Therefore, the stats we want to calculate are simply average(timeToClose) , median(timeToClose) , min(timeToclose) , and max(timeToClose) . We put all these in a definition list: Median [duration(median(timeToClose))] Average [duration(average(timeToClose))] Slowest [duration(max(timeToClose))] Fastest [duration(min(timeToClose))] See the Pen Step 3 - Issue Closing App Tutorial by Lea Verou ( @leaverou ) on CodePen . Making repo a variable Now that all the functionality of my app was in place, I realized this could be useful for more repos as well. Why not make the repo a property that can be changed? So I added an input for specifying the repo: and then replaced mavoweb/mavo with [repo] everywhere else, i.e. mv-source became https://api.github.com/repos/[repo]/issues?state=closed&amp;sort=updated&amp;per_page=100 . Avoid reload on every keystroke This worked, but since Mavo properties are reactive, it kept trying to reload data with every single keystroke, which was annoying and wasteful. Therefore, I needed to do a bit more work so that there is a definite action that submits the change. Enter Mavo Actions ! I created two properties: repo for the actual repo and repoInput for the input. repoInput still changes on every keystroke, but it’s repo that is actually being used in the app. I wrapped the input with a and added an action on the form that does this ( mv-action=&quot;set(repo, repoInput)&quot; ). I also added a submit button. Since Mavo actions on forms are triggered when the form is submitted, it doesn’t matter if I press Enter on the input, or click the Submit button, both work. Setting the repo via a URL parameter Eventually I also wanted to be able to set the repo from the URL, so I also added a hidden repoDefault property: , and then changed the hardcoded mv-default=&quot;mavoweb/mavo&quot; to mv-default=&quot;[repoDefault]&quot; on both the repo and the repoInput properties. That way one can link to stats for a specific repo, e.g. https://projects.verou.me/issue-closing/?repo=prismjs/prism Why a repoDefault property and not just mv-default=&quot;[url(&#39;repo&#39;) or &#39;mavoweb/mavo&#39;] ? Just keeping things DRY and avoiding having to repeat the same expression twice. See the Pen Step 5 - Issue Closing App Tutorial by Lea Verou ( @leaverou ) on CodePen . Filtering by label At some point I wondered: What would the issue closing times be if we only counted bugs? What if we only counted enhancements? Surely these would be different: When looking at issue closing times for a repo, one primarily cares about how fast bugs are fixed, not how quickly every random feature suggestion is implemented. Wouldn’t it be cool to also have a label filter? For that, I added a series of radio buttons: Show: All Bugs only Enhancements only Then, I modified mv-source to also use this value in its API call: mv-source=&quot;https://api.github.com/repos/[repo]/issues?state=closed&amp;sort=updated&amp;labels=[labels]&amp;per_page=100&quot; . Note that when turning radio buttons into a Mavo property you only use the property attribute on the first one. This is important because Mavo has special handling when you use the property attribute with the same name multiple times in the same group, which we don’t want here. You can add the property attribute on any of the radio buttons, it doesn’t have to be the first. Just make sure it’s only one of them. Then I became greedy: Why not also allow filtering by custom labels too? So I added another radio with an input: Show: All Bugs only Enhancements only Label Note that since this is a text field, when the last value is selected, we’d have the same problem as we did with the repo input: Every keystroke would fire a new request. We can solve this in the same way as we solved it for the repo property, by having an intermediate property and only setting labels when the form is actually submitted: Show: All Bugs only Enhancements only Label Adding label autocomplete Since we now allow filtering by a custom label, wouldn’t it be cool to allow autocomplete too? HTML allows us to offer autocomplete in our forms via and we can use Mavo to populate the contents! First, we add a and link it with our custom label input, like so: Label Currently, our suggestion list is empty. How do we populate it with the labels that have actually been used in this repo? Looking at the API documentation , we see that each returned issue has a labels field with its labels as an object, and each of these objects has a name field with the textual label. This means that if we use issue.labels.name in Mavo outside of the issues collection, we get a list with all of these values, which we can then use to populate our by passing it on to mv-value which allows us to create dynamic collections: Label Note that we also used unique() to eliminate duplicates, since otherwise each label would appear as many times as it is used. See the Pen Issue Closing App - Tutorial Step 6 by Lea Verou ( @leaverou ) on CodePen . Adding a visual summary graphic Now that we got the functionality down, we can be a little playful and add some visual flourish. How about a bar chart that summarizes the proportion of long vs short vs normal closing times? We start by setting the CSS variables we are going to need for our graphic, i.e. the number of issues in each category: month())]; --total: [count(issue)];&quot;&gt; Based on [count(issue)] most recently updated issues Then, we draw our graphic: summary::before { content: &quot;&quot;; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1; height: 5px; background: linear-gradient(to right, var(--short-color) calc(var(--short, 0) / var(--total) * 100%), hsl(220, 10%, 75%) 0, hsl(220, 10%, 75%) calc(100% - var(--long, 0) / var(--total) * 100%), var(--long-color) 0) bottom / auto 100% no-repeat border-box; } Now, wouldn’t it be cool to also show a small pie chart next to the heading, if conic gradients are supported so we can draw it? The color stops would be the same, so we define a --summary-stops variable on summary , so we can reuse them across both gradients: summary { --summary-stops: var(--short-color) calc(var(--short, 0) / var(--total) * 100%), hsl(220, 10%, 75%) 0, hsl(220, 10%, 75%) calc(100% - var(--long, 0) / var(--total) * 100%), var(--long-color) 0; } summary::before { content: &quot;&quot;; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1; height: 5px; background: linear-gradient(to right, var(--summary-stops)) bottom / auto 100% no-repeat border-box; } @supports (background: conic-gradient(red, red)) { summary::after { content: &quot;&quot;; display: inline-block; vertical-align: middle; width: 1.2em; height: 1.2em; margin-left: .3em; border-radius: 50%; background: conic-gradient(var(--summary-stops)); } } See the Pen Issue Closing App - Tutorial Step 7 by Lea Verou ( @leaverou ) on CodePen .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Issue closing stats for any repo</title>
  <link>https://lea.verou.me/2019/12/issue-closing-stats-for-any-repo/</link>
  <pubDate>Sun, 17 May 2026 03:05:03 +0200</pubDate>
  <description>tl;dr: If you just want to quickly get stats for a repo, you can find the app here . The rest of this post explains how its built with Mavo HTML, CSS, and 0 lines of JS. Or, if youd prefer, you can just View Source its all there! The finished app were going to make, find it at https://projects.verou.me/issue-closing One of the cool things about Mavo is how it enables one to quickly build apps that utilize the Github API. At some point I wanted to compute stats about how quickly (or rather, slowly) Github issues are closed in the Mavo repo . And what better way to build this than a Mavo app? It was fairly easy to build a prototype for that. Displaying a list of the last 100 closed issues and the time it took to close them To render the last 100 closed issues in the Mavo app, I first looked up the appropriate API call in Githubs API documentation , then used it in the mv-source attribute on the Mavo root , i.e. the element with mv-app that encompasses everything in my app: Then, I displayed a list of these issues with: #[number] took [closed_at - created_at] ms See the Pen Step 1 - Issue Closing App Tutorial by Lea Verou ( @leaverou ) on CodePen . This would work, but the way it displays results is not very user friendly (e.g. #542 took 149627000 ms ). We need to display the result in a more readable way. We can use the duration() function to display a readable duration such as 1 day: #[number] took [duration(closed_at - created_at)] See the Pen Step 2 - Issue Closing App Tutorial by Lea Verou ( @leaverou ) on CodePen . Displaying aggregate statistics However, a list of issues is not very easy to process. Whats the overall picture? Does this repo close issues fast or not? Time for some statistics! We want to calculate average, median, minimum and maximum issue closing time. To calculate these statistics, we need to use the times we have displayed in the previous step. First, we need to give our calculation a name, so we can refer to its value in expressions: [duration(closed_at - created_at)] However, as it currently stands, the value of this property is text (e.g. 1 day, 2 months etc). We cannot compute averages and medians on text! We need the property value to be a number. We can hide the actual raw value in an attribute and use the nicely formatted value as the visible content of the element, like so (we use the content attribute here but you can use any, e.g. a data-* attribute would work just as well): [duration(timeToClose)] Note: There is a data formatting feature in the works which would simplify this kind of thing by allowing you to separate the raw value and its presentation without having to use separate attributes for them. We can also add a class to color it red, green, or black depending on whether the time is longer than a month, shorter than a day, or in-between respectively: month(), &#39;long&#39;, if (timeToClose [duration(timeToClose)] Now, on to calculate our statistics! We take advantage of the fact that timeToClose outside the issue collection gives us all the times, so we can compute aggregates on them. Therefore, the stats we want to calculate are simply average(timeToClose) , median(timeToClose) , min(timeToclose) , and max(timeToClose) . We put all these in a definition list: Median [duration(median(timeToClose))] Average [duration(average(timeToClose))] Slowest [duration(max(timeToClose))] Fastest [duration(min(timeToClose))] See the Pen Step 3 - Issue Closing App Tutorial by Lea Verou ( @leaverou ) on CodePen . Making repo a variable Now that all the functionality of my app was in place, I realized this could be useful for more repos as well. Why not make the repo a property that can be changed? So I added an input for specifying the repo: and then replaced mavoweb/mavo with [repo] everywhere else, i.e. mv-source became https://api.github.com/repos/[repo]/issues?state=closed&amp;sort=updated&amp;per_page=100 . Avoid reload on every keystroke This worked, but since Mavo properties are reactive, it kept trying to reload data with every single keystroke, which was annoying and wasteful. Therefore, I needed to do a bit more work so that there is a definite action that submits the change. Enter Mavo Actions ! I created two properties: repo for the actual repo and repoInput for the input. repoInput still changes on every keystroke, but its repo that is actually being used in the app. I wrapped the input with a and added an action on the form that does this ( mv-action=&quot;set(repo, repoInput)&quot; ). I also added a submit button. Since Mavo actions on forms are triggered when the form is submitted, it doesnt matter if I press Enter on the input, or click the Submit button, both work. Setting the repo via a URL parameter Eventually I also wanted to be able to set the repo from the URL, so I also added a hidden repoDefault property: , and then changed the hardcoded mv-default=&quot;mavoweb/mavo&quot; to mv-default=&quot;[repoDefault]&quot; on both the repo and the repoInput properties. That way one can link to stats for a specific repo, e.g. https://projects.verou.me/issue-closing/?repo=prismjs/prism Why a repoDefault property and not just mv-default=&quot;[url(&#39;repo&#39;) or &#39;mavoweb/mavo&#39;] ? Just keeping things DRY and avoiding having to repeat the same expression twice. See the Pen Step 5 - Issue Closing App Tutorial by Lea Verou ( @leaverou ) on CodePen . Filtering by label At some point I wondered: What would the issue closing times be if we only counted bugs? What if we only counted enhancements? Surely these would be different: When looking at issue closing times for a repo, one primarily cares about how fast bugs are fixed, not how quickly every random feature suggestion is implemented. Wouldnt it be cool to also have a label filter? For that, I added a series of radio buttons: Show: All Bugs only Enhancements only Then, I modified mv-source to also use this value in its API call: mv-source=&quot;https://api.github.com/repos/[repo]/issues?state=closed&amp;sort=updated&amp;labels=[labels]&amp;per_page=100&quot; . Note that when turning radio buttons into a Mavo property you only use the property attribute on the first one. This is important because Mavo has special handling when you use the property attribute with the same name multiple times in the same group, which we dont want here. You can add the property attribute on any of the radio buttons, it doesnt have to be the first. Just make sure its only one of them. Then I became greedy: Why not also allow filtering by custom labels too? So I added another radio with an input: Show: All Bugs only Enhancements only Label Note that since this is a text field, when the last value is selected, wed have the same problem as we did with the repo input: Every keystroke would fire a new request. We can solve this in the same way as we solved it for the repo property, by having an intermediate property and only setting labels when the form is actually submitted: Show: All Bugs only Enhancements only Label Adding label autocomplete Since we now allow filtering by a custom label, wouldnt it be cool to allow autocomplete too? HTML allows us to offer autocomplete in our forms via and we can use Mavo to populate the contents! First, we add a and link it with our custom label input, like so: Label Currently, our suggestion list is empty. How do we populate it with the labels that have actually been used in this repo? Looking at the API documentation , we see that each returned issue has a labels field with its labels as an object, and each of these objects has a name field with the textual label. This means that if we use issue.labels.name in Mavo outside of the issues collection, we get a list with all of these values, which we can then use to populate our by passing it on to mv-value which allows us to create dynamic collections: Label Note that we also used unique() to eliminate duplicates, since otherwise each label would appear as many times as it is used. See the Pen Issue Closing App - Tutorial Step 6 by Lea Verou ( @leaverou ) on CodePen . Adding a visual summary graphic Now that we got the functionality down, we can be a little playful and add some visual flourish. How about a bar chart that summarizes the proportion of long vs short vs normal closing times? We start by setting the CSS variables we are going to need for our graphic, i.e. the number of issues in each category: month())]; --total: [count(issue)];&quot;&gt; Based on [count(issue)] most recently updated issues Then, we draw our graphic: summary::before { content: &quot;&quot;; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1; height: 5px; background: linear-gradient(to right, var(--short-color) calc(var(--short, 0) / var(--total) * 100%), hsl(220, 10%, 75%) 0, hsl(220, 10%, 75%) calc(100% - var(--long, 0) / var(--total) * 100%), var(--long-color) 0) bottom / auto 100% no-repeat border-box; } Now, wouldnt it be cool to also show a small pie chart next to the heading, if conic gradients are supported so we can draw it? The color stops would be the same, so we define a --summary-stops variable on summary , so we can reuse them across both gradients: summary { --summary-stops: var(--short-color) calc(var(--short, 0) / var(--total) * 100%), hsl(220, 10%, 75%) 0, hsl(220, 10%, 75%) calc(100% - var(--long, 0) / var(--total) * 100%), var(--long-color) 0; } summary::before { content: &quot;&quot;; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1; height: 5px; background: linear-gradient(to right, var(--summary-stops)) bottom / auto 100% no-repeat border-box; } @supports (background: conic-gradient(red, red)) { summary::after { content: &quot;&quot;; display: inline-block; vertical-align: middle; width: 1.2em; height: 1.2em; margin-left: .3em; border-radius: 50%; background: conic-gradient(var(--summary-stops)); } } See the Pen Issue Closing App - Tutorial Step 7 by Lea Verou ( @leaverou ) on CodePen .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Utility: Convert SVG path to all-relative or all-absolute commands</title>
  <link>https://lea.verou.me/2019/05/utility-convert-svg-path-to-all-relative-or-all-absolute-commands/</link>
  <pubDate>Sun, 17 May 2026 02:45:21 +0200</pubDate>
  <description>I like hand-editing my SVGs. Often I will create an initial version in Illustrator, and then export and continue with hand editing. Not only is it a bit of a meditative experience and it satisfies my obsessive-compulsive tendencies to clean up the code, it has actual practical benefits when you need to make certain changes or introduce animation. Some things are easier to do in a GUI, and others are easier to do in code, and I like having the flexibility to pick which one fits my use case best. However, there was always a thing that was a PITA: modifying paths. Usually if I need anything more complicated than just moving them, I’d do it in Illustrator, but even moving them can be painful if they are not all relative (and no, I don’t like introducing pointless transforms for things that should really be in the d attribute). For example, this was today’s result of trying to move an exported “a” glyph from Raleway Bold by modifying its first M command: Trying to move a path by changing its first M command when not all of its commands are relative. This happened because even though most commands were exported as relative, several were not and I had not noticed. I have no idea why some commands were exported as absolute, it seems kind of random. When all commands are relative, moving a path is as simple as manipulating its initial M command and the rest just adapts, because that’s the whole point of relative commands . Same with manipulating every other part of the path, the rest of it just adapts. It’s beautiful. I honestly have no idea why anybody would favor absolute commands. And yet, googling “convert SVG path to relative” yields one result , whereas there are plenty of results about converting paths to absolute. No idea why that’s even desirable, ever (?). I remembered I had come across that result before. Thankfully, there’s also a fiddle to go with it, which I had used in the past to convert my path. I love it, it uses this library called Snap.svg which supports converting paths to relative as a just-add-water utility method . However, that fiddle is a quick demo to answer a StackOverflow question, so the UI is not super pleasant to use (there is no UI: you just manipulate the path in the SVG and wait for the fiddle to run). This time around, I needed to convert multiple paths, so I needed a more efficient UI. So I created this demo which is also based on Snap.svg, but has a slightly more efficient UI. You just paste your path in a textarea and it both displays it and instantly converts it to all-relative and all-absolute paths (also using Snap.svg). It also displays both your original path and the two converted ones, so you can make sure they still look the same. It even follows a pending-delete pattern so you can just focus on the output textarea and hit Cmd-C in one fell swoop. I wasn’t sure about posting this or just tweeting it (it literally took less than 30 minutes — including this blog post — and I tend to only post small things like that on my twitter ), but I thought it might be useful to others googling the same thing, so I may as well post it here for posterity. Enjoy!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Utility: Convert SVG path to all-relative or all-absolute commands</title>
  <link>https://lea.verou.me/2019/05/utility-convert-svg-path-to-all-relative-or-all-absolute-commands/</link>
  <pubDate>Sun, 17 May 2026 02:45:21 +0200</pubDate>
  <description>I like hand-editing my SVGs. Often I will create an initial version in Illustrator, and then export and continue with hand editing. Not only is it a bit of a meditative experience and it satisfies my obsessive-compulsive tendencies to clean up the code, it has actual practical benefits when you need to make certain changes or introduce animation. Some things are easier to do in a GUI, and others are easier to do in code, and I like having the flexibility to pick which one fits my use case best. However, there was always a thing that was a PITA: modifying paths. Usually if I need anything more complicated than just moving them, Id do it in Illustrator, but even moving them can be painful if they are not all relative (and no, I dont like introducing pointless transforms for things that should really be in the d attribute). For example, this was todays result of trying to move an exported a glyph from Raleway Bold by modifying its first M command: Trying to move a path by changing its first M command when not all of its commands are relative. This happened because even though most commands were exported as relative, several were not and I had not noticed. I have no idea why some commands were exported as absolute, it seems kind of random. When all commands are relative, moving a path is as simple as manipulating its initial M command and the rest just adapts, because thats the whole point of relative commands . Same with manipulating every other part of the path, the rest of it just adapts. Its beautiful. I honestly have no idea why anybody would favor absolute commands. And yet, googling convert SVG path to relative yields one result , whereas there are plenty of results about converting paths to absolute. No idea why thats even desirable, ever (?). I remembered I had come across that result before. Thankfully, theres also a fiddle to go with it, which I had used in the past to convert my path. I love it, it uses this library called Snap.svg which supports converting paths to relative as a just-add-water utility method . However, that fiddle is a quick demo to answer a StackOverflow question, so the UI is not super pleasant to use (there is no UI: you just manipulate the path in the SVG and wait for the fiddle to run). This time around, I needed to convert multiple paths, so I needed a more efficient UI. So I created this demo which is also based on Snap.svg, but has a slightly more efficient UI. You just paste your path in a textarea and it both displays it and instantly converts it to all-relative and all-absolute paths (also using Snap.svg). It also displays both your original path and the two converted ones, so you can make sure they still look the same. It even follows a pending-delete pattern so you can just focus on the output textarea and hit Cmd-C in one fell swoop. I wasnt sure about posting this or just tweeting it (it literally took less than 30 minutes including this blog post and I tend to only post small things like that on my twitter ), but I thought it might be useful to others googling the same thing, so I may as well post it here for posterity. Enjoy!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>ReferenceError: x is not defined?</title>
  <link>https://lea.verou.me/2018/12/referenceerror-x-is-not-defined/</link>
  <pubDate>Sun, 17 May 2026 02:45:20 +0200</pubDate>
  <description>Today for a bit of code I was writing, I needed to be able to distinguish “x is not defined” ReferenceError s from any other error within a try...catch block and handle them differently. Now I know what you’re thinking. Trying to figure out exactly what kind of error you have programmatically is a well-known fool’s errand. If you express a desire to engage in such a risky endeavor, any JS veteran in sight will shake their head in remembrance of their early days, but have the wisdom to refrain from trying to convince you otherwise; they know that failing will teach you what it taught them when they were young and foolish enough to attempt such a thing. Despite writing JS for 13 years, today I was feeling adventurous. “But what if, just this once, I could get it to work? It’s a pretty standard error message! What if I tested in so many browsers that I would be confident I’ve covered all cases?” I made a simple page on my server that just prints out the error message written in a way that would maximize older browser coverage. Armed with that, I started visiting every browser in my BrowserStack account. Here are my findings for anyone interested: Chrome (all versions, including mobile): x is not defined Firefox (all versions, including mobile): x is not defined Safari 4-12 : Can&#39;t find variable: x Edge (16 - 18): &#39;x&#39; is not defined Edge 15: &#39;x&#39; is undefined IE6-11 and Windows Phone IE: &#39;x&#39; is undefined UC Browser (all versions): x is not defined Samsung browser (all versions): x is not defined Opera Mini and Pre-Chromium Opera: Undefined variable: x Even if you, dear reader, are wise enough to never try and detect this error, I thought you may find the variety (or lack thereof) above interesting. I also did a little bit of testing with a different UI language (I picked Greek), but it didn’t seem to localize the error messages. If you’re using a different UI language, please open the page above and if the message is not in English, let me know! In the end, I decided to go ahead with it, and time will tell if it was foolish to do so. For anyone wishing to also dabble in such dangerous waters, this was my checking code: if (e instanceof ReferenceError &amp;&amp; /is (not |un)defined$|^(Can&#39;t find|Undefined) variable/.test(e.message)) { // do stuff } Found any cases I missed? Or perhaps you found a different ReferenceError that would erroneously match the regex above? Let me know in the comments! One thing that’s important to note is that even if the code above is bulletproof for today’s browser landscape, the more developers that do things like this, the harder it is for browser makers to improve these error messages . However, until there’s a better way to do this, pointing fingers at developers for wanting to do perfectly reasonable things, is not the solution. This is why HTTP has status codes , so we don’t have to string match on the text. Imagine having to string match “Not Found” to figure out if a request was found or not! Similarly, many other technologies have error codes, so that different types of errors can be distinguished without resulting to flimsy string matching. I’m hoping that one day JS will also have a better way to distinguish errors more precisely than the general error categories of today, and we’ll look back to posts like this with a nostalgic smile, being so glad we don’t have to do crap like this ever again.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Refresh CSS Bookmarklet v2</title>
  <link>https://lea.verou.me/2018/09/refresh-css-bookmarklet-v2/</link>
  <pubDate>Sun, 17 May 2026 02:45:20 +0200</pubDate>
  <description>Almost 11 years ago, Paul Irish posted this brilliant bookmarklet to refresh all stylesheets on the current page. Despite the amount of tools, plugins, servers to live reload that have been released over the years, I’ve always kept coming back to it. It’s incredibly elegant in its simplicity. It works everywhere: locally or remotely, on any domain and protocol. No need to set up anything, no need to alter my process in any way, no need to use a specific local server or tool. It quietly just accepts your preferences and workflow instead of trying to change them. Sure, it doesn’t automatically detect changes and reload, but in most cases, I don’t want it to. I’ve been using this almost daily for a decade and there’s always been one thing that bothered me: It doesn’t work with iframes. If the stylesheet you’re editing is inside an iframe, tough luck. If you can open the frame in a new tab, that works, but often that’s nontrivial (e.g. the frame is dynamically generated). After dealing with this issue today once more, I thought “this is just a few lines of JS, why not fix it?”. The first step was to get Paul’s code in a readable format, since the bookmarklet is heavily minified: (function() { var links = document.getElementsByTagName(&#39;link&#39;); for (var i = 0; i Once I did that, it became obvious to me that this could be shortened a lot; the last 10 years have been wonderful for JS evolution! (()=&gt;{ for (let link of Array.from(document.querySelectorAll(&quot;link[rel=stylesheet][href]&quot;))) { var href = new URL(link.href, location); href.searchParams.set(&quot;forceReload&quot;, Date.now()); link.href = href; } })() Sure, this reduces browser support a bit (most notably it excludes IE11), but since this is a local development tool, that’s not such a big problem. Now, let’s extend this to support iframes as well: { let $$ = (selector, root = document) =&gt; Array.from(root.querySelectorAll(selector)); let refresh = (document) =&gt; { for (let link of $$(&quot;link[rel=stylesheet][href]&quot;, document)) { let href = new URL(link.href); href.searchParams.set(&quot;forceReload&quot;, Date.now()); link.href = href; } for (let iframe of $$(&quot;iframe&quot;, document)) { iframe.contentDocument &amp;&amp; refresh(iframe.contentDocument); } } refresh(); } That’s it! Do keep in mind that this will not work with cross-origin iframes, but then again, you probably don’t expect it to in that case. Now all we need to do to turn it into a bookmarklet is to prepend it with javascript: and minify the code. Here you go: Array.from(t.querySelectorAll(e)),t=r=&gt;{for(let t of e(&#39;link[rel=stylesheet][href]&#39;,r)){let e=new URL(t.href);e.searchParams.set(&#39;forceReload&#39;,Date.now()),t.href=e}for(let o of e(&#39;iframe&#39;,r))o.contentDocument&amp;&amp;t(o.contentDocument)};t()}&quot;&gt;🔄 CSS v2 Hope this is useful to someone else as well :) Any improvements are always welcome! Credits Paul Irish, for the original bookmarklet Maurício Kishi, for making the iframe traversal recursive ( comment )</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>ReferenceError: x is not defined?</title>
  <link>https://lea.verou.me/2018/12/referenceerror-x-is-not-defined/</link>
  <pubDate>Sun, 17 May 2026 02:45:20 +0200</pubDate>
  <description>Today for a bit of code I was writing, I needed to be able to distinguish x is not defined ReferenceError s from any other error within a try...catch block and handle them differently. Now I know what youre thinking. Trying to figure out exactly what kind of error you have programmatically is a well-known fools errand. If you express a desire to engage in such a risky endeavor, any JS veteran in sight will shake their head in remembrance of their early days, but have the wisdom to refrain from trying to convince you otherwise; they know that failing will teach you what it taught them when they were young and foolish enough to attempt such a thing. Despite writing JS for 13 years, today I was feeling adventurous. But what if, just this once, I could get it to work? Its a pretty standard error message! What if I tested in so many browsers that I would be confident Ive covered all cases? I made a simple page on my server that just prints out the error message written in a way that would maximize older browser coverage. Armed with that, I started visiting every browser in my BrowserStack account. Here are my findings for anyone interested: Chrome (all versions, including mobile): x is not defined Firefox (all versions, including mobile): x is not defined Safari 4-12 : Can&#39;t find variable: x Edge (16 - 18): &#39;x&#39; is not defined Edge 15: &#39;x&#39; is undefined IE6-11 and Windows Phone IE: &#39;x&#39; is undefined UC Browser (all versions): x is not defined Samsung browser (all versions): x is not defined Opera Mini and Pre-Chromium Opera: Undefined variable: x Even if you, dear reader, are wise enough to never try and detect this error, I thought you may find the variety (or lack thereof) above interesting. I also did a little bit of testing with a different UI language (I picked Greek), but it didnt seem to localize the error messages. If youre using a different UI language, please open the page above and if the message is not in English, let me know! In the end, I decided to go ahead with it, and time will tell if it was foolish to do so. For anyone wishing to also dabble in such dangerous waters, this was my checking code: if (e instanceof ReferenceError &amp;&amp; /is (not |un)defined$|^(Can&#39;t find|Undefined) variable/.test(e.message)) { // do stuff } Found any cases I missed? Or perhaps you found a different ReferenceError that would erroneously match the regex above? Let me know in the comments! One thing thats important to note is that even if the code above is bulletproof for todays browser landscape, the more developers that do things like this, the harder it is for browser makers to improve these error messages . However, until theres a better way to do this, pointing fingers at developers for wanting to do perfectly reasonable things, is not the solution. This is why HTTP has status codes , so we dont have to string match on the text. Imagine having to string match Not Found to figure out if a request was found or not! Similarly, many other technologies have error codes, so that different types of errors can be distinguished without resulting to flimsy string matching. Im hoping that one day JS will also have a better way to distinguish errors more precisely than the general error categories of today, and well look back to posts like this with a nostalgic smile, being so glad we dont have to do crap like this ever again.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Refresh CSS Bookmarklet v2</title>
  <link>https://lea.verou.me/2018/09/refresh-css-bookmarklet-v2/</link>
  <pubDate>Sun, 17 May 2026 02:45:20 +0200</pubDate>
  <description>Almost 11 years ago, Paul Irish posted this brilliant bookmarklet to refresh all stylesheets on the current page. Despite the amount of tools, plugins, servers to live reload that have been released over the years, Ive always kept coming back to it. Its incredibly elegant in its simplicity. It works everywhere: locally or remotely, on any domain and protocol. No need to set up anything, no need to alter my process in any way, no need to use a specific local server or tool. It quietly just accepts your preferences and workflow instead of trying to change them. Sure, it doesnt automatically detect changes and reload, but in most cases, I dont want it to. Ive been using this almost daily for a decade and theres always been one thing that bothered me: It doesnt work with iframes. If the stylesheet youre editing is inside an iframe, tough luck. If you can open the frame in a new tab, that works, but often thats nontrivial (e.g. the frame is dynamically generated). After dealing with this issue today once more, I thought this is just a few lines of JS, why not fix it?. The first step was to get Pauls code in a readable format, since the bookmarklet is heavily minified: (function() { var links = document.getElementsByTagName(&#39;link&#39;); for (var i = 0; i Once I did that, it became obvious to me that this could be shortened a lot; the last 10 years have been wonderful for JS evolution! (()=&gt;{ for (let link of Array.from(document.querySelectorAll(&quot;link[rel=stylesheet][href]&quot;))) { var href = new URL(link.href, location); href.searchParams.set(&quot;forceReload&quot;, Date.now()); link.href = href; } })() Sure, this reduces browser support a bit (most notably it excludes IE11), but since this is a local development tool, thats not such a big problem. Now, lets extend this to support iframes as well: { let $$ = (selector, root = document) =&gt; Array.from(root.querySelectorAll(selector)); let refresh = (document) =&gt; { for (let link of $$(&quot;link[rel=stylesheet][href]&quot;, document)) { let href = new URL(link.href); href.searchParams.set(&quot;forceReload&quot;, Date.now()); link.href = href; } for (let iframe of $$(&quot;iframe&quot;, document)) { iframe.contentDocument &amp;&amp; refresh(iframe.contentDocument); } } refresh(); } Thats it! Do keep in mind that this will not work with cross-origin iframes, but then again, you probably dont expect it to in that case. Now all we need to do to turn it into a bookmarklet is to prepend it with javascript: and minify the code. Here you go: Array.from(t.querySelectorAll(e)),t=r=&gt;{for(let t of e(&#39;link[rel=stylesheet][href]&#39;,r)){let e=new URL(t.href);e.searchParams.set(&#39;forceReload&#39;,Date.now()),t.href=e}for(let o of e(&#39;iframe&#39;,r))o.contentDocument&amp;&amp;t(o.contentDocument)};t()}&quot;&gt; CSS v2 Hope this is useful to someone else as well :) Any improvements are always welcome! Credits Paul Irish, for the original bookmarklet Maurício Kishi, for making the iframe traversal recursive ( comment )</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Easy Dynamic Regular Expressions with Tagged Template Literals and Proxies</title>
  <link>https://lea.verou.me/2018/06/easy-dynamic-regular-expressions-with-tagged-template-literals-and-proxies/</link>
  <pubDate>Sun, 17 May 2026 02:45:19 +0200</pubDate>
  <description>If you use regular expressions a lot, you probably also create them from existing strings that you first need to escape in case they contain special characters that need to be matched literally, like $ or + . Usually, a helper function is defined (hopefully this will soon change as RegExp.escape() is coming!) that basically looks like this: var escapeRegExp = s =&gt; s.replace(/[-\/\\^$*+?.()|[\]{}]/g, &quot;\\$&amp;&quot;); and then regexps are created by escaping the static strings and concatenating them with the rest of the regex like this: var regex = RegExp(escapeRegExp(start) + &#39;([\\S\\s]+?)&#39; + escapeRegExp(end), &quot;gi&quot;) or, with ES6 template literals, like this: var regex = RegExp(`${escapeRegExp(start)}([\\S\\s]+?)${escapeRegExp(end)}`, &quot;gi&quot;) (In case you were wondering, this regex is taken directly from the Mavo source code ) Isn’t this horribly verbose? What if we could define a regex with just a template literal ( `${start}([\\S\\s]+?)${end}` for the regex above) and it just worked? Well, it turns out we can! If you haven’t seen tagged template literals before, I suggest you click that MDN link and read up. Basically, you can prepend an ES6 template literal with a reference to a function and the function accepts the static parts of the string and the dynamic parts separately, allowing you to operate on them! So, what if we defined such a function that returns a RegExp object and escapes the dynamic parts? Let’s try to do that: var regexp = (strings, ...values) =&gt; { return RegExp(strings[0] + values.map((v, i) =&gt; escapeRegExp(v) + strings[i+1]).join(&quot;&quot;)) }; And now we can try it in the console: &gt; regexp`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; Won’t somebody, please, think of the flags?! This is all fine and dandy, but how do we specify flags? Note that the original regexp had flags (“gi”). The tagged template syntax doesn’t really allow us to pass in any additional parameters. However, thanks to functions being first-class objects in JS, we can have a function that takes the flags in as parameters and returns a function that generates regexps with the right flags: var regexp = flags =&gt; { return (strings, ...values) =&gt; { var pattern = strings[0] + values.map((v, i) =&gt; escapeRegExp(v) + strings[i+1]).join(&quot;&quot;) return RegExp(pattern, flags); } }; And now we can try it in the console: &gt; regexp(&quot;gi&quot;)`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; This works nice, but now even if we don’t want any flags, we can’t use the nice simple syntax we had earlier, we need to include a pair of empty parens: &gt; regexp()`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; Can we have our cake and eat it too? Can we have the short parenthesis-less syntax when we have no flags, and still be able to specify flags? Of course! We can check the arguments we have and either return a function, or call the function. If our function is used as a tag, the first argument will be an array ( thanks Roman! ). If we’re expecting it to return a function, the first argument would be a string: the flags. So, let’s try this approach! var regexp = (...args) =&gt; { var ret = (flags, strings, ...values) =&gt; { var pattern = strings[0] + values.map((v, i) =&gt; escapeRegExp(v) + strings[i+1]).join(&quot;&quot;); return RegExp(pattern, flags); }; if (Array.isArray(args[0])) { // Used as a template tag return ret(&quot;&quot;, ...args); } return ret.bind(undefined, args[0]); }; And now we can try it in the console and verify that both syntaxes work: &gt; regexp(&quot;gi&quot;)`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; Even nicer syntax, with proxies! Is there a better way? If this is not super critical for performance, we could use proxies to return the right function with a template tag like regexp.gi , no parentheses or quotes needed and the code is actually shorter too: var _regexp = (flags, strings, ...values) =&gt; { var pattern = strings[0] + values.map((v, i) =&gt; escapeRegExp(v) + strings[i+1]).join(&quot;&quot;); return RegExp(pattern, flags); }; var regexp = new Proxy(_regexp.bind(undefined, &quot;&quot;), { get: (t, property) =&gt; _regexp.bind(undefined, property) }); And now we can try it in the console, both with and without flags! &gt; regexp.gi`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; regexp`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; That’s some beauty right there! ? PS: If you liked this, take a look at this mini-library by Dr. Axel Rauschmayer that uses a similar idea and turns it into a library that does more than just escaping strings (different syntax for flags though, they become part of the template string, like in PHP)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Easy Dynamic Regular Expressions with Tagged Template Literals and Proxies</title>
  <link>https://lea.verou.me/2018/06/easy-dynamic-regular-expressions-with-tagged-template-literals-and-proxies/</link>
  <pubDate>Sun, 17 May 2026 02:45:19 +0200</pubDate>
  <description>If you use regular expressions a lot, you probably also create them from existing strings that you first need to escape in case they contain special characters that need to be matched literally, like $ or + . Usually, a helper function is defined (hopefully this will soon change as RegExp.escape() is coming!) that basically looks like this: var escapeRegExp = s =&gt; s.replace(/[-\/\\^$*+?.()|[\]{}]/g, &quot;\\$&amp;&quot;); and then regexps are created by escaping the static strings and concatenating them with the rest of the regex like this: var regex = RegExp(escapeRegExp(start) + &#39;([\\S\\s]+?)&#39; + escapeRegExp(end), &quot;gi&quot;) or, with ES6 template literals, like this: var regex = RegExp(`${escapeRegExp(start)}([\\S\\s]+?)${escapeRegExp(end)}`, &quot;gi&quot;) (In case you were wondering, this regex is taken directly from the Mavo source code ) Isnt this horribly verbose? What if we could define a regex with just a template literal ( `${start}([\\S\\s]+?)${end}` for the regex above) and it just worked? Well, it turns out we can! If you havent seen tagged template literals before, I suggest you click that MDN link and read up. Basically, you can prepend an ES6 template literal with a reference to a function and the function accepts the static parts of the string and the dynamic parts separately, allowing you to operate on them! So, what if we defined such a function that returns a RegExp object and escapes the dynamic parts? Lets try to do that: var regexp = (strings, ...values) =&gt; { return RegExp(strings[0] + values.map((v, i) =&gt; escapeRegExp(v) + strings[i+1]).join(&quot;&quot;)) }; And now we can try it in the console: &gt; regexp`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; Wont somebody, please, think of the flags?! This is all fine and dandy, but how do we specify flags? Note that the original regexp had flags (gi). The tagged template syntax doesnt really allow us to pass in any additional parameters. However, thanks to functions being first-class objects in JS, we can have a function that takes the flags in as parameters and returns a function that generates regexps with the right flags: var regexp = flags =&gt; { return (strings, ...values) =&gt; { var pattern = strings[0] + values.map((v, i) =&gt; escapeRegExp(v) + strings[i+1]).join(&quot;&quot;) return RegExp(pattern, flags); } }; And now we can try it in the console: &gt; regexp(&quot;gi&quot;)`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; This works nice, but now even if we dont want any flags, we cant use the nice simple syntax we had earlier, we need to include a pair of empty parens: &gt; regexp()`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; Can we have our cake and eat it too? Can we have the short parenthesis-less syntax when we have no flags, and still be able to specify flags? Of course! We can check the arguments we have and either return a function, or call the function. If our function is used as a tag, the first argument will be an array ( thanks Roman! ). If were expecting it to return a function, the first argument would be a string: the flags. So, lets try this approach! var regexp = (...args) =&gt; { var ret = (flags, strings, ...values) =&gt; { var pattern = strings[0] + values.map((v, i) =&gt; escapeRegExp(v) + strings[i+1]).join(&quot;&quot;); return RegExp(pattern, flags); }; if (Array.isArray(args[0])) { // Used as a template tag return ret(&quot;&quot;, ...args); } return ret.bind(undefined, args[0]); }; And now we can try it in the console and verify that both syntaxes work: &gt; regexp(&quot;gi&quot;)`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; Even nicer syntax, with proxies! Is there a better way? If this is not super critical for performance, we could use proxies to return the right function with a template tag like regexp.gi , no parentheses or quotes needed and the code is actually shorter too: var _regexp = (flags, strings, ...values) =&gt; { var pattern = strings[0] + values.map((v, i) =&gt; escapeRegExp(v) + strings[i+1]).join(&quot;&quot;); return RegExp(pattern, flags); }; var regexp = new Proxy(_regexp.bind(undefined, &quot;&quot;), { get: (t, property) =&gt; _regexp.bind(undefined, property) }); And now we can try it in the console, both with and without flags! &gt; regexp.gi`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; regexp`^${&#39;/*&#39;}([\\S\\s]+?)${&#39;*/&#39;}`; Thats some beauty right there! ? PS: If you liked this, take a look at this mini-library by Dr. Axel Rauschmayer that uses a similar idea and turns it into a library that does more than just escaping strings (different syntax for flags though, they become part of the template string, like in PHP)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Never forget type=&quot;button&quot; on generated buttons!</title>
  <link>https://lea.verou.me/2018/05/never-forget-typebutton-on-generated-buttons/</link>
  <pubDate>Sun, 17 May 2026 02:45:18 +0200</pubDate>
  <description>I just dealt with one of the weirdest bugs and thought you may find it amusing too. In one of my slides for my upcoming talk “Even More CSS Secrets”, I had a Mavo app on a , and the app included a collection to quickly create a UI to manage pairs of values for something I wanted to calculate in one of my live demos. A Mavo collection is a repeatable HTML element with affordances to add items, delete items, move items etc. Many of these affordances are implemented via elements generated by Mavo. Normally, hitting Enter inside a text field within a collection adds a new item, as one would expect. However, I noticed that when I hit Enter inside any item, not only no item was added, but an item was being deleted , with the usual “Item deleted [Undo]” UI and everything! At first I thought it was a bug with the part of Mavo code that adds items on Enter and deletes empty items on backspace, so I commented that out. Nope, still happening. I was already very puzzled, since I couldn’t remember any other part of the codebase that deletes items in response to keyboard events. So, I added breakpoints on the delete(item) method of Mavo.Collection to inspect the call stack and see how execution got there. Turned out, it got there via a normal … click event on the actual delete button! What fresh hell was this? I never clicked any delete button! And then it dawned on me: elements with no type attribute set are submit buttons by default ! Quote from spec: The missing value default and invalid value default are the Submit Button state. . This makes no difference in most cases, UNLESS you’re inside a form. The delete button of the first item had been turned into the de facto default submit button just because it was the first button in that form and it had no type! I also remembered that regardless of how you submit a form (e.g. by hitting Enter on a single-line text field) it also fires a click event on the default submit button , because people often listen to that instead of the form’s submit event. Ironically, I was cancelling the form’s submit event in my code, but it still generated that fake click event, making it even harder to track down as no form submission was actually happening. The solution was of course to go through every part of the Mavo code that generates buttons and add type=“button” to them . I would recommend this to everyone who is writing libraries that will operate in unfamiliar HTML code. Most of the time a type-less will work just fine, but when it doesn’t, things get really weird .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Never forget type=&quot;button&quot; on generated buttons!</title>
  <link>https://lea.verou.me/2018/05/never-forget-typebutton-on-generated-buttons/</link>
  <pubDate>Sun, 17 May 2026 02:45:18 +0200</pubDate>
  <description>I just dealt with one of the weirdest bugs and thought you may find it amusing too. In one of my slides for my upcoming talk Even More CSS Secrets, I had a Mavo app on a , and the app included a collection to quickly create a UI to manage pairs of values for something I wanted to calculate in one of my live demos. A Mavo collection is a repeatable HTML element with affordances to add items, delete items, move items etc. Many of these affordances are implemented via elements generated by Mavo. Normally, hitting Enter inside a text field within a collection adds a new item, as one would expect. However, I noticed that when I hit Enter inside any item, not only no item was added, but an item was being deleted , with the usual Item deleted [Undo] UI and everything! At first I thought it was a bug with the part of Mavo code that adds items on Enter and deletes empty items on backspace, so I commented that out. Nope, still happening. I was already very puzzled, since I couldnt remember any other part of the codebase that deletes items in response to keyboard events. So, I added breakpoints on the delete(item) method of Mavo.Collection to inspect the call stack and see how execution got there. Turned out, it got there via a normal click event on the actual delete button! What fresh hell was this? I never clicked any delete button! And then it dawned on me: elements with no type attribute set are submit buttons by default ! Quote from spec: The missing value default and invalid value default are the Submit Button state. . This makes no difference in most cases, UNLESS youre inside a form. The delete button of the first item had been turned into the de facto default submit button just because it was the first button in that form and it had no type! I also remembered that regardless of how you submit a form (e.g. by hitting Enter on a single-line text field) it also fires a click event on the default submit button , because people often listen to that instead of the forms submit event. Ironically, I was cancelling the forms submit event in my code, but it still generated that fake click event, making it even harder to track down as no form submission was actually happening. The solution was of course to go through every part of the Mavo code that generates buttons and add type=button to them . I would recommend this to everyone who is writing libraries that will operate in unfamiliar HTML code. Most of the time a type-less will work just fine, but when it doesnt, things get really weird .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Responsive tables, revisited</title>
  <link>https://lea.verou.me/2018/05/responsive-tables-revisited/</link>
  <pubDate>Sun, 17 May 2026 02:45:17 +0200</pubDate>
  <description>Many people have explored responsive tables . The usual idea is turning the table into key-value pairs so that cells become rows and there are only 2 columns total, which fit in any screen. However, this means table headers need to now be repeated for every row. The current ways to do that are: Duplicating content in CSS or via a data-* attribute, using generated content to insert it before every row. Using a definition list which naturally has duplicated s, displaying it as a table in larger screens. A few techniques that go in an entirely different direction are: Hiding non-essential columns in smaller screens Showing a thumbnail of the table instead, and display the full table on click Displaying a graph in smaller screens (e.g. a pie chart) I think the key-value display is probably best because it works for any kind of table, and provides the same information. So I wondered, is there any way to create it without duplicating content either in the markup or in the CSS? After a bit of thinking, I came up with two ways, each with their own pros and cons. Both techniques are very similar: They set table elements to display: block; so that they behave like normal elements and duplicate the contents in two different ways: Using text-shadow and creating one shadow for each row Using the element() function to duplicate the entire thead, styles and all. Each method has its own pros and cons, but the following pros and cons apply to both: Pros: Works with normal table markup Cons: All but the first set of headers are unselectable (since neither shadows nor element()-generated images are real text). However, keep in mind that the techniques based on generated content also have this problem — and for all rows. Also, that the markup screen readers see is the same as a normal table. However, it’s still a pretty serious flaw and makes this a hack. I’m looking forward to seeing more viable solutions. Only works if none of the table cells wrap, since it depends on table cells being aligned with their headers. Using text-shadow to copy text to other rows Additional Pros: Works in every browser Additional Cons: Max Number of rows needs to be hardcoded in the CSS, since each row needs another text shadow on . However, you can specify more shadows than needed, since overflow: hidden on the table prevents extra ones from showing up. Also, number of columns needs to be specified in the CSS (the --cols variable). Demo Using element() to copy the entire to other rows Additional Cons: element() is currently only supported in Firefox :( Demo</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Responsive tables, revisited</title>
  <link>https://lea.verou.me/2018/05/responsive-tables-revisited/</link>
  <pubDate>Sun, 17 May 2026 02:45:17 +0200</pubDate>
  <description>Many people have explored responsive tables . The usual idea is turning the table into key-value pairs so that cells become rows and there are only 2 columns total, which fit in any screen. However, this means table headers need to now be repeated for every row. The current ways to do that are: Duplicating content in CSS or via a data-* attribute, using generated content to insert it before every row. Using a definition list which naturally has duplicated s, displaying it as a table in larger screens. A few techniques that go in an entirely different direction are: Hiding non-essential columns in smaller screens Showing a thumbnail of the table instead, and display the full table on click Displaying a graph in smaller screens (e.g. a pie chart) I think the key-value display is probably best because it works for any kind of table, and provides the same information. So I wondered, is there any way to create it without duplicating content either in the markup or in the CSS? After a bit of thinking, I came up with two ways, each with their own pros and cons. Both techniques are very similar: They set table elements to display: block; so that they behave like normal elements and duplicate the contents in two different ways: Using text-shadow and creating one shadow for each row Using the element() function to duplicate the entire thead, styles and all. Each method has its own pros and cons, but the following pros and cons apply to both: Pros: Works with normal table markup Cons: All but the first set of headers are unselectable (since neither shadows nor element()-generated images are real text). However, keep in mind that the techniques based on generated content also have this problem and for all rows. Also, that the markup screen readers see is the same as a normal table. However, its still a pretty serious flaw and makes this a hack. Im looking forward to seeing more viable solutions. Only works if none of the table cells wrap, since it depends on table cells being aligned with their headers. Using text-shadow to copy text to other rows Additional Pros: Works in every browser Additional Cons: Max Number of rows needs to be hardcoded in the CSS, since each row needs another text shadow on . However, you can specify more shadows than needed, since overflow: hidden on the table prevents extra ones from showing up. Also, number of columns needs to be specified in the CSS (the --cols variable). Demo Using element() to copy the entire to other rows Additional Cons: element() is currently only supported in Firefox :( Demo</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Quicker Storify export</title>
  <link>https://lea.verou.me/2018/04/quicker-storify-export/</link>
  <pubDate>Sun, 17 May 2026 02:45:16 +0200</pubDate>
  <description>If you’ve used Storify , you probably know by now it’s closing down soon. They have an FAQ up to help people with the transition which explains that to export your content you need to… Log in to Storify at www.storify.com. Mouse over the story that contains content you would like to export and select “View.” Click on the ellipses icon and select “Export.” Choose your preferred format for download. To save your content and linked assets in HTML, select - File &gt; Save as &gt; Web Page, Complete. To export your content to PDF, select Export to HTML &gt; File &gt; Print &gt; Save as PDF. Repeat the process for each story whose content you would like to preserve. So I started doing that. I wasn’t sure if JSON or HTML would be more useful to me, so I was exporting both. It was painful. Each export required 3 page loads, and they were slow. After 5 stories, I started wondering if there’s a quicker way. I’m a programmer after all, my job is to automate things. However, I also didn’t want to spend too long on that, since I only had 40 stories, so the effort should definitely not be longer than it would have taken to manually export the remaining 35 stories. I noticed that the HTML and JSON URLs for each story could actually be recreated by using the slug of the Story URL: https://storify.com/LeaVerou/**css-variables-var-subtitle-cssconf-asia**.html https://api.storify.com/v1/stories/LeaVerou/**css-variables-var-subtitle-cssconf-asia ** The bold part is the only thing that changes. I tried that with a different slug and it worked just fine. Bingo! So I could write a quick console script to get all these URLs and open them in separate tabs and then all I have to do is go through each tab and hit Cmd + S to save. It’s not perfect, but it took minutes to write and saved A LOT of time. Following is the script I wrote. Go to your profile page, click “Show more” and scroll until all your stories are visible, then paste it into the console. You will probably need to do it twice: once to disable popup blocking because the browser rightfully freaks out when you try to open this many tabs from script, and once to actually open all of them. var slugs = [... new Set($$(&quot;.story-tile&quot;).map(e =&gt; e.dataset.path))] slugs.forEach(s =&gt; { open(`https://api.storify.com/v1/stories/${s}`); open(`https://storify.com/${s}.html`) }) This gets a list of all unique (hence the [...new Set(array)] ) slugs and opens both the JSON and HTML export URLs in new tabs. Then you can go through each tab and save. You will notice that the browser becomes REALLY SLOW when you open this many tabs (in my case 41 stories × 2 tabs each = 82 tabs!) so you may want to do it in steps, by using array.slice(). Also, if you don’t want to save the HTML version, the whole process becomes much faster, the HTML pages took AGES to load and kept freezing the browser. Hope this helps! PS: If you’re content with your data being held hostage by a different company, you could also use this tool by Wakelet . I’ve done that too, but I also wanted to own my data as well.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Free Intro to Web Development slides (with demos)</title>
  <link>https://lea.verou.me/2018/02/free-intro-to-web-development-slides-with-demos/</link>
  <pubDate>Sun, 17 May 2026 02:45:16 +0200</pubDate>
  <description>This semester I’m teaching 6.813 User Interface Design and Implementation at MIT , as an instructor. Many of the assignments of this course include Web development and the course included two 2-hour labs to introduce students to these technologies. Since I’m involved this year, I decided to make new labs from scratch and increase the number of labs from 2 to 3. Even so, trying to decide what to include and what not to from the entirety of web development in only 6 hours was really hard, and I still feel I failed to include important bits. Since many people asked me for the slides on Twitter, I decided to share them. You will find my slides here and an outline of what is covered is here . These slides were also the supporting material the students had on their own laptops and often they had to do exercises in them. The audience for these slides is beginners in Web development but technical otherwise — people who understand OOP, trees, data structures and have experience in at least one C-like programming language. Some demos will not make sense as they were live coded , but I included notes (top right or bottom left corner) about what was explained in each part. Use the arrow keys to navigate. It is also quite big, so do not open this on a phone or on a data plan. If the “Open in new Tab” button opens a tab which then closes immediately, disable Adblock . From some quick testing, they seem to work in Firefox and Safari, but in class we were using an updated version of Chrome (since we were talking about developer tools, we needed to all have the same UI), so that’s the browser I’d recommend since they were tested much more there. I’m sharing them as-is in case someone else finds them useful. Please do not bug me if they don’t work in your setup , or if you do not find them useful or whatever**.** If they don’t tickle your fancy, move on. I cannot provide any support or fixes. If you want to help fix the issue, you can submit a pull request , but be warned: most of the code was written under extreme time pressure (I had to produce this 6 times as fast as I usually need to make talks), so is not my finest moment. If you want to use them to teach other people that’s fine as long as it’s a non-profit event. [gallery columns=“2” size=“medium” ids=“2756,2757,2755,2754”]</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Quicker Storify export</title>
  <link>https://lea.verou.me/2018/04/quicker-storify-export/</link>
  <pubDate>Sun, 17 May 2026 02:45:16 +0200</pubDate>
  <description>If youve used Storify , you probably know by now its closing down soon. They have an FAQ up to help people with the transition which explains that to export your content you need to Log in to Storify at www.storify.com. Mouse over the story that contains content you would like to export and select View. Click on the ellipses icon and select Export. Choose your preferred format for download. To save your content and linked assets in HTML, select - File &gt; Save as &gt; Web Page, Complete. To export your content to PDF, select Export to HTML &gt; File &gt; Print &gt; Save as PDF. Repeat the process for each story whose content you would like to preserve. So I started doing that. I wasnt sure if JSON or HTML would be more useful to me, so I was exporting both. It was painful. Each export required 3 page loads, and they were slow. After 5 stories, I started wondering if theres a quicker way. Im a programmer after all, my job is to automate things. However, I also didnt want to spend too long on that, since I only had 40 stories, so the effort should definitely not be longer than it would have taken to manually export the remaining 35 stories. I noticed that the HTML and JSON URLs for each story could actually be recreated by using the slug of the Story URL: https://storify.com/LeaVerou/**css-variables-var-subtitle-cssconf-asia**.html https://api.storify.com/v1/stories/LeaVerou/**css-variables-var-subtitle-cssconf-asia ** The bold part is the only thing that changes. I tried that with a different slug and it worked just fine. Bingo! So I could write a quick console script to get all these URLs and open them in separate tabs and then all I have to do is go through each tab and hit Cmd + S to save. Its not perfect, but it took minutes to write and saved A LOT of time. Following is the script I wrote. Go to your profile page, click Show more and scroll until all your stories are visible, then paste it into the console. You will probably need to do it twice: once to disable popup blocking because the browser rightfully freaks out when you try to open this many tabs from script, and once to actually open all of them. var slugs = [... new Set($$(&quot;.story-tile&quot;).map(e =&gt; e.dataset.path))] slugs.forEach(s =&gt; { open(`https://api.storify.com/v1/stories/${s}`); open(`https://storify.com/${s}.html`) }) This gets a list of all unique (hence the [...new Set(array)] ) slugs and opens both the JSON and HTML export URLs in new tabs. Then you can go through each tab and save. You will notice that the browser becomes REALLY SLOW when you open this many tabs (in my case 41 stories 2 tabs each = 82 tabs!) so you may want to do it in steps, by using array.slice(). Also, if you dont want to save the HTML version, the whole process becomes much faster, the HTML pages took AGES to load and kept freezing the browser. Hope this helps! PS: If youre content with your data being held hostage by a different company, you could also use this tool by Wakelet . Ive done that too, but I also wanted to own my data as well.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Free Intro to Web Development slides (with demos)</title>
  <link>https://lea.verou.me/2018/02/free-intro-to-web-development-slides-with-demos/</link>
  <pubDate>Sun, 17 May 2026 02:45:16 +0200</pubDate>
  <description>This semester Im teaching 6.813 User Interface Design and Implementation at MIT , as an instructor. Many of the assignments of this course include Web development and the course included two 2-hour labs to introduce students to these technologies. Since Im involved this year, I decided to make new labs from scratch and increase the number of labs from 2 to 3. Even so, trying to decide what to include and what not to from the entirety of web development in only 6 hours was really hard, and I still feel I failed to include important bits. Since many people asked me for the slides on Twitter, I decided to share them. You will find my slides here and an outline of what is covered is here . These slides were also the supporting material the students had on their own laptops and often they had to do exercises in them. The audience for these slides is beginners in Web development but technical otherwise people who understand OOP, trees, data structures and have experience in at least one C-like programming language. Some demos will not make sense as they were live coded , but I included notes (top right or bottom left corner) about what was explained in each part. Use the arrow keys to navigate. It is also quite big, so do not open this on a phone or on a data plan. If the Open in new Tab button opens a tab which then closes immediately, disable Adblock . From some quick testing, they seem to work in Firefox and Safari, but in class we were using an updated version of Chrome (since we were talking about developer tools, we needed to all have the same UI), so thats the browser Id recommend since they were tested much more there. Im sharing them as-is in case someone else finds them useful. Please do not bug me if they dont work in your setup , or if you do not find them useful or whatever**.** If they dont tickle your fancy, move on. I cannot provide any support or fixes. If you want to help fix the issue, you can submit a pull request , but be warned: most of the code was written under extreme time pressure (I had to produce this 6 times as fast as I usually need to make talks), so is not my finest moment. If you want to use them to teach other people thats fine as long as its a non-profit event. [gallery columns=2 size=medium ids=2756,2757,2755,2754]</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Different remote and local resource URLs, with Service Workers!</title>
  <link>https://lea.verou.me/2017/10/different-remote-and-local-resource-urls-with-service-workers/</link>
  <pubDate>Sun, 17 May 2026 02:45:15 +0200</pubDate>
  <description>I often run into this issue where I want a different URL remotely and a different one locally so I can test my local changes to a library. Sure, relative URLs work a lot of the time, but are often not an option. Developing Mavo is yet another example of this: since Mavo is in a separate repo from mavo.io (its website) as well as test.mavo.io (the testsuite), I can’t just have relative URLs to it that also work remotely. I’ve been encountering this problem way too frequently pretty much since I started in web development. In this post, will describe all solutions and workarounds I’ve used over time for this, including the one I’m currently using for Mavo: Service Workers! The manual one Probably the first solution everyone tries is doing it manually: every time you need to test, you just change the URL to a relative, local one and try to remember to change it back before committing. I still use this in some cases, since us developers are a lazy bunch. Usually I have both and use my editor’s (un)commenting shortcut for enabling one or the other: --&gt; However, as you might imagine, this approach has several problems, the worst of which is that more than once I forgot and committed with the active script being the local one, which resulted in the remote website totally breaking. Also, it’s clunky, especially when it’s two resources whose URLs you need to change. The JS one This idea uses a bit of JS to load the remote URL when the local one fails to load. This works, and doesn’t introduce any cognitive overhead for the developer, but the obvious drawback is that it slows things down for the server since a request needs to be sent and fail before the real resource can be loaded. Slowing things down for the local case might be acceptable, even though undesirable, but slowing things down on the remote website for the sake of debugging is completely unacceptable. Furthermore, this exposes the debugging URLs in the HTML source, which gives me a bit of a knee jerk reaction. A variation of this approach that doesn’t have the performance problem is: { let host = location.hostname == &quot;localhost&quot;? &#39;http://localhost:8000/dist&#39; : &#39;https://get.mavo.io&#39;; document.write(` `); } This works fine, but it’s very clunky, especially if you have to do this multiple times (e.g. on multiple testing files or demos). The build tools one The solution I was following up to a few months ago was to use gulp to copy over the files needed, and then link to my local copies via a relative URL. I would also have a gulp.watch() that monitors changes to the original files and copies them over again: gulp.task(&quot;copy&quot;, function() { gulp.src([&quot;../mavo/dist/**/*&quot;]) .pipe(gulp.dest(&quot;mavo&quot;)); }); gulp.task(&quot;watch&quot;, function() { gulp.watch([&quot;../mavo/dist/*&quot;], [&quot;copy&quot;]); }); This worked but I had to remember to run gulp watch every time I started working on each project. Often I forgot, which was a huge source of confusion as to why my changes had no effect. Also, it meant I had copies of Mavo lying around on every repo that uses it and had to manually update them by running gulp , which was suboptimal. The Service Worker one In April, after being fed up with having to deal with this problem for over a decade, I posted a tweet : https://twitter.com/LeaVerou/status/857030863292436480?ref_src=twsrc^tfw @MylesBorins replied (though his tweet seems to have disappeared) and suggested that perhaps Service Workers could help. In case you’ve been hiding under a rock for the past couple of years, Service Workers are a new(ish) API that allows you to intercept requests from your website to the network and do whatever you want with them. They are mostly promoted for creating good offline experiences, though they can do a lot more. I was looking for an excuse to dabble in Service Workers for a while, and this was a great one. Furthermore, browser support doesn’t really matter in this case because the Service Worker is only used locally. The code I ended up with looks like this in a small script called sitewide.js , which, as you may imagine, is used sitewide: (function() { if (location.hostname !== &quot;localhost&quot;) { return; } if (!self.document) { // We&#39;re in a service worker! Oh man, we’re living in the future! ?? self.addEventListener(&quot;fetch&quot;, function(evt) { var url = evt.request.url; if (url.indexOf(&quot;get.mavo.io/mavo.&quot;) &gt; -1 || url.indexOf(&quot;dev.mavo.io/dist/mavo.&quot;) &gt; -1) { var newURL = url.replace(/.+?(get|dev)\.mavo\.io\/(dist\/)?/, &quot;http://localhost:8000/dist/&quot;) + &quot;?&quot; + Date.now(); var response = fetch(new Request(newURL), evt.request) .then(r =&gt; r.status fetch(evt.request)); evt.respondWith(response); } }); return; } if (&quot;serviceWorker&quot; in navigator) { // Register this script as a service worker addEventListener(&quot;load&quot;, function() { navigator.serviceWorker.register(&quot;sitewide.js&quot;); }); } })(); So far, this has worked more nicely than any of the aforementioned solutions and allows me to just use the normal remote URLs in my HTML. However, it’s not without its own caveats: Service Workers are only activated on a cached pageload, so the first one uses the remote URL. This is almost never a problem locally anyway though, so I’m not concerned about it much. The same origin restriction that service workers have is fairly annoying. So, I have to copy the service worker script on every repo I want to use this on, I cannot just link to it. It needs to be explained to new contributors since most aren’t familiar with Service Workers and how they work at all. Currently the URLs for both local and remote are baked into the code, but it’s easy to imagine a mini-library that takes care of it as long as you include the local URL as a parameter (e.g. https://get.mavo.io/mavo.js?local=http://localhost:8000/dist/mavo.js ). Other solutions Solutions I didn’t test (but you may want to) include: .htaccess redirect based on domain, suggested by @codepo8 . I don’t use Apache locally, so that’s of no use to me. Symbolic links, suggested by @aleschmidx User scripts (e.g. Greasemonkey), suggested by @WebManWlkg Modifying the hosts file, suggested by @LukeBrowell (that works if you don’t need access to the remote URL at all) Is there any other solution? What do you do?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Different remote and local resource URLs, with Service Workers!</title>
  <link>https://lea.verou.me/2017/10/different-remote-and-local-resource-urls-with-service-workers/</link>
  <pubDate>Sun, 17 May 2026 02:45:15 +0200</pubDate>
  <description>I often run into this issue where I want a different URL remotely and a different one locally so I can test my local changes to a library. Sure, relative URLs work a lot of the time, but are often not an option. Developing Mavo is yet another example of this: since Mavo is in a separate repo from mavo.io (its website) as well as test.mavo.io (the testsuite), I cant just have relative URLs to it that also work remotely. Ive been encountering this problem way too frequently pretty much since I started in web development. In this post, will describe all solutions and workarounds Ive used over time for this, including the one Im currently using for Mavo: Service Workers! The manual one Probably the first solution everyone tries is doing it manually: every time you need to test, you just change the URL to a relative, local one and try to remember to change it back before committing. I still use this in some cases, since us developers are a lazy bunch. Usually I have both and use my editors (un)commenting shortcut for enabling one or the other: --&gt; However, as you might imagine, this approach has several problems, the worst of which is that more than once I forgot and committed with the active script being the local one, which resulted in the remote website totally breaking. Also, its clunky, especially when its two resources whose URLs you need to change. The JS one This idea uses a bit of JS to load the remote URL when the local one fails to load. This works, and doesnt introduce any cognitive overhead for the developer, but the obvious drawback is that it slows things down for the server since a request needs to be sent and fail before the real resource can be loaded. Slowing things down for the local case might be acceptable, even though undesirable, but slowing things down on the remote website for the sake of debugging is completely unacceptable. Furthermore, this exposes the debugging URLs in the HTML source, which gives me a bit of a knee jerk reaction. A variation of this approach that doesnt have the performance problem is: { let host = location.hostname == &quot;localhost&quot;? &#39;http://localhost:8000/dist&#39; : &#39;https://get.mavo.io&#39;; document.write(` `); } This works fine, but its very clunky, especially if you have to do this multiple times (e.g. on multiple testing files or demos). The build tools one The solution I was following up to a few months ago was to use gulp to copy over the files needed, and then link to my local copies via a relative URL. I would also have a gulp.watch() that monitors changes to the original files and copies them over again: gulp.task(&quot;copy&quot;, function() { gulp.src([&quot;../mavo/dist/**/*&quot;]) .pipe(gulp.dest(&quot;mavo&quot;)); }); gulp.task(&quot;watch&quot;, function() { gulp.watch([&quot;../mavo/dist/*&quot;], [&quot;copy&quot;]); }); This worked but I had to remember to run gulp watch every time I started working on each project. Often I forgot, which was a huge source of confusion as to why my changes had no effect. Also, it meant I had copies of Mavo lying around on every repo that uses it and had to manually update them by running gulp , which was suboptimal. The Service Worker one In April, after being fed up with having to deal with this problem for over a decade, I posted a tweet : https://twitter.com/LeaVerou/status/857030863292436480?ref_src=twsrc^tfw @MylesBorins replied (though his tweet seems to have disappeared) and suggested that perhaps Service Workers could help. In case youve been hiding under a rock for the past couple of years, Service Workers are a new(ish) API that allows you to intercept requests from your website to the network and do whatever you want with them. They are mostly promoted for creating good offline experiences, though they can do a lot more. I was looking for an excuse to dabble in Service Workers for a while, and this was a great one. Furthermore, browser support doesnt really matter in this case because the Service Worker is only used locally. The code I ended up with looks like this in a small script called sitewide.js , which, as you may imagine, is used sitewide: (function() { if (location.hostname !== &quot;localhost&quot;) { return; } if (!self.document) { // We&#39;re in a service worker! Oh man, were living in the future! ?? self.addEventListener(&quot;fetch&quot;, function(evt) { var url = evt.request.url; if (url.indexOf(&quot;get.mavo.io/mavo.&quot;) &gt; -1 || url.indexOf(&quot;dev.mavo.io/dist/mavo.&quot;) &gt; -1) { var newURL = url.replace(/.+?(get|dev)\.mavo\.io\/(dist\/)?/, &quot;http://localhost:8000/dist/&quot;) + &quot;?&quot; + Date.now(); var response = fetch(new Request(newURL), evt.request) .then(r =&gt; r.status fetch(evt.request)); evt.respondWith(response); } }); return; } if (&quot;serviceWorker&quot; in navigator) { // Register this script as a service worker addEventListener(&quot;load&quot;, function() { navigator.serviceWorker.register(&quot;sitewide.js&quot;); }); } })(); So far, this has worked more nicely than any of the aforementioned solutions and allows me to just use the normal remote URLs in my HTML. However, its not without its own caveats: Service Workers are only activated on a cached pageload, so the first one uses the remote URL. This is almost never a problem locally anyway though, so Im not concerned about it much. The same origin restriction that service workers have is fairly annoying. So, I have to copy the service worker script on every repo I want to use this on, I cannot just link to it. It needs to be explained to new contributors since most arent familiar with Service Workers and how they work at all. Currently the URLs for both local and remote are baked into the code, but its easy to imagine a mini-library that takes care of it as long as you include the local URL as a parameter (e.g. https://get.mavo.io/mavo.js?local=http://localhost:8000/dist/mavo.js ). Other solutions Solutions I didnt test (but you may want to) include: .htaccess redirect based on domain, suggested by @codepo8 . I dont use Apache locally, so thats of no use to me. Symbolic links, suggested by @aleschmidx User scripts (e.g. Greasemonkey), suggested by @WebManWlkg Modifying the hosts file, suggested by @LukeBrowell (that works if you dont need access to the remote URL at all) Is there any other solution? What do you do?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Mavo: Create web apps entirely by writing HTML!</title>
  <link>https://lea.verou.me/2017/05/introducing-mavo-create-web-apps-entirely-by-writing-html/</link>
  <pubDate>Sun, 17 May 2026 02:45:14 +0200</pubDate>
  <description>Today I finally released the project I’ve been working on for the last two years at MIT CSAIL : An HTML-based language for creating (many kinds of) web applications without programming or a server backend. It’s named Mavo after my late mother ( Ma ria V er o u), and is Open Source of course (yes, getting paid to work on open source is exactly as fun as it sounds). It was the scariest release of my life, and have been postponing it for months. I kept feeling Mavo was not quite there yet, maybe I should add this one feature first, oh and this other one, oh and we can’t release without this one, surely! Eventually I realized that what I was doing had more to do with postponing the anxiety and less to do with Mavo reaching a stage where it can be released. After all, “if you’re not at least a bit embarrassed by what you release, you waited too long”, right? So, there it is, I hope you find it useful. Read the post on Smashing Magazine or just head straight to mavo.io , read the docs , and play with the demos ! And do let me know what you make with it, no matter how small and trivial you may think it is, I would love to see it!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Mavo: Create web apps entirely by writing HTML!</title>
  <link>https://lea.verou.me/2017/05/introducing-mavo-create-web-apps-entirely-by-writing-html/</link>
  <pubDate>Sun, 17 May 2026 02:45:14 +0200</pubDate>
  <description>Today I finally released the project Ive been working on for the last two years at MIT CSAIL : An HTML-based language for creating (many kinds of) web applications without programming or a server backend. Its named Mavo after my late mother ( Ma ria V er o u), and is Open Source of course (yes, getting paid to work on open source is exactly as fun as it sounds). It was the scariest release of my life, and have been postponing it for months. I kept feeling Mavo was not quite there yet, maybe I should add this one feature first, oh and this other one, oh and we cant release without this one, surely! Eventually I realized that what I was doing had more to do with postponing the anxiety and less to do with Mavo reaching a stage where it can be released. After all, if youre not at least a bit embarrassed by what you release, you waited too long, right? So, there it is, I hope you find it useful. Read the post on Smashing Magazine or just head straight to mavo.io , read the docs , and play with the demos ! And do let me know what you make with it, no matter how small and trivial you may think it is, I would love to see it!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>HTML APIs: What they are and how to design a good one</title>
  <link>https://lea.verou.me/2017/02/html-apis-what-they-are-and-how-to-design-a-good-one/</link>
  <pubDate>Sun, 17 May 2026 02:45:13 +0200</pubDate>
  <description>I’m a strong believer in lowering the barrier of what it takes to create rich, interactive experiences and improving the user experience of programming. I wrote an article over at Smashing Magazine aimed at JavaScript library developers that want their libraries to be usable via HTML (i.e. without writing any JavaScript). Sounds interesting? Read it here .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>HTML APIs: What they are and how to design a good one</title>
  <link>https://lea.verou.me/2017/02/html-apis-what-they-are-and-how-to-design-a-good-one/</link>
  <pubDate>Sun, 17 May 2026 02:45:13 +0200</pubDate>
  <description>Im a strong believer in lowering the barrier of what it takes to create rich, interactive experiences and improving the user experience of programming. I wrote an article over at Smashing Magazine aimed at JavaScript library developers that want their libraries to be usable via HTML (i.e. without writing any JavaScript). Sounds interesting? Read it here .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Duoload: Simplest website load comparison tool, ever</title>
  <link>https://lea.verou.me/2017/02/duoload-simplest-website-load-comparison-tool-ever/</link>
  <pubDate>Sun, 17 May 2026 02:45:12 +0200</pubDate>
  <description>Today I needed a quick tool to compare the loading progression (not just loading time, but also incremental rendering) of two websites, one remote and one in my localhost. Just have them side by side and see how they load relative to each other. Maybe even record the result on video and study it afterwards. That’s all. No special features, no analysis, no stats. So I did what I always do when I need help finding a tool, I asked Twitter: https://twitter.com/LeaVerou/status/827327249305178113 Most suggested complicated tools, some non-free and most unlikely to work on local URLs. I thought damn, what I need is a very simple thing! I could code this in 5 minutes! So I did and here it is , in case someone else finds it useful! The (minuscule amount of) code is of course on Github . Duoload Of course it goes without saying that this is probably a bit inaccurate. Do not use it for mission-critical performance comparisons. Credits for the name Duoload to Chris Lilley who came up with it in the 1 minute deadline I gave him :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Duoload: Simplest website load comparison tool, ever</title>
  <link>https://lea.verou.me/2017/02/duoload-simplest-website-load-comparison-tool-ever/</link>
  <pubDate>Sun, 17 May 2026 02:45:12 +0200</pubDate>
  <description>Today I needed a quick tool to compare the loading progression (not just loading time, but also incremental rendering) of two websites, one remote and one in my localhost. Just have them side by side and see how they load relative to each other. Maybe even record the result on video and study it afterwards. Thats all. No special features, no analysis, no stats. So I did what I always do when I need help finding a tool, I asked Twitter: https://twitter.com/LeaVerou/status/827327249305178113 Most suggested complicated tools, some non-free and most unlikely to work on local URLs. I thought damn, what I need is a very simple thing! I could code this in 5 minutes! So I did and here it is , in case someone else finds it useful! The (minuscule amount of) code is of course on Github . Duoload Of course it goes without saying that this is probably a bit inaccurate. Do not use it for mission-critical performance comparisons. Credits for the name Duoload to Chris Lilley who came up with it in the 1 minute deadline I gave him :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Resolve Promises externally with this one weird trick</title>
  <link>https://lea.verou.me/2016/12/resolve-promises-externally-with-this-one-weird-trick/</link>
  <pubDate>Sun, 17 May 2026 02:45:11 +0200</pubDate>
  <description>Those of us who use promises heavily, have often wished there was a Promise.prototype.resolve() method, that would force an existing Promise to resolve. However, for architectural reasons (throw safety), there is no such thing and probably never will be. Therefore, a Promise can only resolve or reject by calling the respective methods in its constructor: var promise = new Promise((resolve, reject) =&gt; { if (something) { resolve(); } else { reject(); } }); However, often it is not desirable to put your entire code inside a Promise constructor so you could resolve or reject it at any point. In my latest case today, I wanted a Promise that resolved when a tree was created, so that third-party components could defer code execution until the tree was ready. However, given that plugins could be running on any hook, that meant wrapping a ton of code with the Promise constructor, which was obviously a no-go. I had come across this problem before and usually gave up and created a Promise around all the necessary code. However, this time my aversion to what this would produce got me to think even harder. What could I do to call resolve() asynchronously from outside the Promise? A custom event? Nah, too slow for my purposes, why involve the DOM when it’s not needed? Another Promise? Nah, that just transfers the problem. An setInterval to repeatedly check if the tree is created? OMG, I can’t believe you just thought that Lea, ewwww, gross! Getters and setters? Hmmm, maybe that could work! If the setter is inside the Promise constructor, then I can resolve the Promise by just setting a property! My first iteration looked like this: this.treeBuilt = new Promise((resolve, reject) =&gt; { Object.defineProperty(this, &quot;_treeBuilt&quot;, { set: value =&gt; { if (value) { resolve(); } } }); }); // Many, many lines below… this._treeBuilt = true; However, it really bothered me that I had to define 2 properties when I only needed one. I could of course do some cleanup and delete them after the promise is resolved, but the fact that at some point in time these useless properties existed will still haunt me, and I’m sure the more OCD-prone of you know exactly what I mean. Can I do it with just one property? Turns out I can! The main idea is realizing that the getter and the setter could be doing completely unrelated tasks. In this case, setting the property would resolve the promise and reading its value would return the promise: var setter; var promise = new Promise((resolve, reject) =&gt; { setter = value =&gt; { if (value) { resolve(); } }; }); Object.defineProperty(this, &quot;treeBuilt&quot;, { set: setter, get: () =&gt; promise }); // Many, many lines below… this.treeBuilt = true; For better performance, once the promise is resolved you could even delete the dynamic property and replace it with a normal property that just points to the promise, but be careful because in that case, any future attempts to resolve the promise by setting the property will make you lose your reference to it! I still think the code looks a bit ugly, so if you can think a more elegant solution, I’m all ears (well, eyes really)! Update: Joseph Silber gave an interesting solution on twitter : function defer() { var deferred = { promise: null, resolve: null, reject: null }; deferred.promise = new Promise((resolve, reject) =&gt; { deferred.resolve = resolve; deferred.reject = reject; }); return deferred; } this.treeBuilt = defer(); // Many, many lines below… this.treeBuilt.resolve(); I love that this is reusable, and calling resolve() makes a lot more sense than setting something to true . However, I didn’t like that it involved a separate object ( deferred ) and that people using the treeBuilt property would not be able to call .then() directly on it, so I simplified it a bit to only use one Promise object: function defer() { var res, rej; var promise = new Promise((resolve, reject) =&gt; { res = resolve; rej = reject; }); promise.resolve = res; promise.reject = rej; return promise; } this.treeBuilt = defer(); // Many, many lines below… this.treeBuilt.resolve(); Finally, something I like!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Resolve Promises externally with this one weird trick</title>
  <link>https://lea.verou.me/2016/12/resolve-promises-externally-with-this-one-weird-trick/</link>
  <pubDate>Sun, 17 May 2026 02:45:11 +0200</pubDate>
  <description>Those of us who use promises heavily, have often wished there was a Promise.prototype.resolve() method, that would force an existing Promise to resolve. However, for architectural reasons (throw safety), there is no such thing and probably never will be. Therefore, a Promise can only resolve or reject by calling the respective methods in its constructor: var promise = new Promise((resolve, reject) =&gt; { if (something) { resolve(); } else { reject(); } }); However, often it is not desirable to put your entire code inside a Promise constructor so you could resolve or reject it at any point. In my latest case today, I wanted a Promise that resolved when a tree was created, so that third-party components could defer code execution until the tree was ready. However, given that plugins could be running on any hook, that meant wrapping a ton of code with the Promise constructor, which was obviously a no-go. I had come across this problem before and usually gave up and created a Promise around all the necessary code. However, this time my aversion to what this would produce got me to think even harder. What could I do to call resolve() asynchronously from outside the Promise? A custom event? Nah, too slow for my purposes, why involve the DOM when its not needed? Another Promise? Nah, that just transfers the problem. An setInterval to repeatedly check if the tree is created? OMG, I cant believe you just thought that Lea, ewwww, gross! Getters and setters? Hmmm, maybe that could work! If the setter is inside the Promise constructor, then I can resolve the Promise by just setting a property! My first iteration looked like this: this.treeBuilt = new Promise((resolve, reject) =&gt; { Object.defineProperty(this, &quot;_treeBuilt&quot;, { set: value =&gt; { if (value) { resolve(); } } }); }); // Many, many lines below this._treeBuilt = true; However, it really bothered me that I had to define 2 properties when I only needed one. I could of course do some cleanup and delete them after the promise is resolved, but the fact that at some point in time these useless properties existed will still haunt me, and Im sure the more OCD-prone of you know exactly what I mean. Can I do it with just one property? Turns out I can! The main idea is realizing that the getter and the setter could be doing completely unrelated tasks. In this case, setting the property would resolve the promise and reading its value would return the promise: var setter; var promise = new Promise((resolve, reject) =&gt; { setter = value =&gt; { if (value) { resolve(); } }; }); Object.defineProperty(this, &quot;treeBuilt&quot;, { set: setter, get: () =&gt; promise }); // Many, many lines below this.treeBuilt = true; For better performance, once the promise is resolved you could even delete the dynamic property and replace it with a normal property that just points to the promise, but be careful because in that case, any future attempts to resolve the promise by setting the property will make you lose your reference to it! I still think the code looks a bit ugly, so if you can think a more elegant solution, Im all ears (well, eyes really)! Update: Joseph Silber gave an interesting solution on twitter : function defer() { var deferred = { promise: null, resolve: null, reject: null }; deferred.promise = new Promise((resolve, reject) =&gt; { deferred.resolve = resolve; deferred.reject = reject; }); return deferred; } this.treeBuilt = defer(); // Many, many lines below this.treeBuilt.resolve(); I love that this is reusable, and calling resolve() makes a lot more sense than setting something to true . However, I didnt like that it involved a separate object ( deferred ) and that people using the treeBuilt property would not be able to call .then() directly on it, so I simplified it a bit to only use one Promise object: function defer() { var res, rej; var promise = new Promise((resolve, reject) =&gt; { res = resolve; rej = reject; }); promise.resolve = res; promise.reject = rej; return promise; } this.treeBuilt = defer(); // Many, many lines below this.treeBuilt.resolve(); Finally, something I like!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>URL rewriting with Github Pages</title>
  <link>https://lea.verou.me/2016/11/url-rewriting-with-github-pages/</link>
  <pubDate>Sun, 17 May 2026 02:45:10 +0200</pubDate>
  <description>I adore Github Pages . I use them for everything I can, and try to avoid server-side code like the plague, exactly so that I can use them. The convenience of pushing to a repo and having the changes immediately reflected on the website with no commit hooks or any additional setup, is awesome. The free price tag is even more awesome. So, when the time came to publish my book , naturally, I wanted the companion website to be on Github Pages. There was only one small problem: I wanted nice URLs, like http://play.csssecrets.io/pie-animated , which would redirect to demos on dabblet.com . Any sane person would have likely bitten the bullet and used some kind of server-side language. However, I’m not a particularly sane person :D Turns out Github uses some URL rewriting of its own on Github Pages : If you provide a 404.html, any URL that doesn’t exist will be handled by that. Wait a second, is that basically how we do nice URLs on the server anyway? We can do the same in Github Pages, by just running JS inside 404.html! So, I created a JSON file with all demo ids and their dabblet URLs, a 404.html that shows either a redirection or an error (JS decides which one) and a tiny bit of Vanilla JS that reads the current URL, fetches the JSON file, and redirects to the right dabblet. Here it is, without the helpers: (function(){ document.body.className = &#39;redirecting&#39;; var slug = location.pathname.slice(1); xhr({ src: &#39;secrets.json&#39;, onsuccess: function () { var slugs = JSON.parse(this.responseText); var hash = slugs[slug]; if (hash) { // Redirect var url = hash.indexOf(&#39;http&#39;) == 0? hash : &#39;https://dabblet.com/gist/&#39; + hash; $(&#39;section.redirecting &gt; p&#39;).innerHTML = &#39;Redirecting to &#39; + url + &#39; …&#39;; location.href = url; } else { document.body.className = &#39;error not-found&#39;; } }, onerror: function () { document.body.className = &#39;error json&#39;; } }); })(); That’s all! You can imagine using the same trick to redirect to other HTML pages in the same Github Pages site, have proper URLs for a single page site, and all sorts of things! Is it a hack? Of course. But when did that ever stop us? :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>URL rewriting with Github Pages</title>
  <link>https://lea.verou.me/2016/11/url-rewriting-with-github-pages/</link>
  <pubDate>Sun, 17 May 2026 02:45:10 +0200</pubDate>
  <description>I adore Github Pages . I use them for everything I can, and try to avoid server-side code like the plague, exactly so that I can use them. The convenience of pushing to a repo and having the changes immediately reflected on the website with no commit hooks or any additional setup, is awesome. The free price tag is even more awesome. So, when the time came to publish my book , naturally, I wanted the companion website to be on Github Pages. There was only one small problem: I wanted nice URLs, like http://play.csssecrets.io/pie-animated , which would redirect to demos on dabblet.com . Any sane person would have likely bitten the bullet and used some kind of server-side language. However, Im not a particularly sane person :D Turns out Github uses some URL rewriting of its own on Github Pages : If you provide a 404.html, any URL that doesnt exist will be handled by that. Wait a second, is that basically how we do nice URLs on the server anyway? We can do the same in Github Pages, by just running JS inside 404.html! So, I created a JSON file with all demo ids and their dabblet URLs, a 404.html that shows either a redirection or an error (JS decides which one) and a tiny bit of Vanilla JS that reads the current URL, fetches the JSON file, and redirects to the right dabblet. Here it is, without the helpers: (function(){ document.body.className = &#39;redirecting&#39;; var slug = location.pathname.slice(1); xhr({ src: &#39;secrets.json&#39;, onsuccess: function () { var slugs = JSON.parse(this.responseText); var hash = slugs[slug]; if (hash) { // Redirect var url = hash.indexOf(&#39;http&#39;) == 0? hash : &#39;https://dabblet.com/gist/&#39; + hash; $(&#39;section.redirecting &gt; p&#39;).innerHTML = &#39;Redirecting to &#39; + url + &#39; &#39;; location.href = url; } else { document.body.className = &#39;error not-found&#39;; } }, onerror: function () { document.body.className = &#39;error json&#39;; } }); })(); Thats all! You can imagine using the same trick to redirect to other HTML pages in the same Github Pages site, have proper URLs for a single page site, and all sorts of things! Is it a hack? Of course. But when did that ever stop us? :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Autoprefixing, with CSS variables!</title>
  <link>https://lea.verou.me/2016/09/autoprefixing-with-css-variables/</link>
  <pubDate>Sun, 17 May 2026 02:45:09 +0200</pubDate>
  <description>Recently, when I was making the minisite for markapp.io , I realized a neat trick one can do with CSS variables, precisely due to their dynamic nature. Let’s say you want to use a property that has multiple versions: an unprefixed one and one or more prefixed ones. In this example we are going to use clip-path , which currently needs both an unprefixed version and a -webkit- prefixed one, however the technique works for any property and any number of prefixes or different property names, as long as the value is the same across all variations of the property name. The first part is to define a --clip-path property on every element with a value of initial. This prevents the property from being inherited every time it’s used, and since the * has zero specificity, any declaration that uses --clip-path can override it. Then you define all variations of the property name with var(--clip-path) as their value: * { --clip-path: initial; -webkit-clip-path: var(--clip-path); clip-path: var(--clip-path); } Then, every time we need clip-path, we use --clip-path instead and it just works: header { --clip-path: polygon(0% 0%, 100% 0%, 100% calc(100% - 2.5em), 0% 100%); } Even !important should work, because it affects the cascading of CSS variables . Furthermore, if for some reason you want to explicitly set -webkit-clip-path , you can do that too, again because * has zero specificity. The main downside to this is that it limits browser support to the intersection of the support for the feature you are using and support for CSS Variables. However, all browsers except Edge support CSS variables , and Edge is working on it . I can’t see any other downsides to it (except having to use a different property name obvs), but if you do, let me know in the comments! I think there’s still a lot to be discovered about cool uses of CSS variables. I wonder if there exists a variation of this technique to produce custom longhands, e.g. breaking box-shadow into --box-shadow-x , --box-shadow-y etc, but I can’t think of anything yet. Can you? ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Autoprefixing, with CSS variables!</title>
  <link>https://lea.verou.me/2016/09/autoprefixing-with-css-variables/</link>
  <pubDate>Sun, 17 May 2026 02:45:09 +0200</pubDate>
  <description>Recently, when I was making the minisite for markapp.io , I realized a neat trick one can do with CSS variables, precisely due to their dynamic nature. Lets say you want to use a property that has multiple versions: an unprefixed one and one or more prefixed ones. In this example we are going to use clip-path , which currently needs both an unprefixed version and a -webkit- prefixed one, however the technique works for any property and any number of prefixes or different property names, as long as the value is the same across all variations of the property name. The first part is to define a --clip-path property on every element with a value of initial. This prevents the property from being inherited every time its used, and since the * has zero specificity, any declaration that uses --clip-path can override it. Then you define all variations of the property name with var(--clip-path) as their value: * { --clip-path: initial; -webkit-clip-path: var(--clip-path); clip-path: var(--clip-path); } Then, every time we need clip-path, we use --clip-path instead and it just works: header { --clip-path: polygon(0% 0%, 100% 0%, 100% calc(100% - 2.5em), 0% 100%); } Even !important should work, because it affects the cascading of CSS variables . Furthermore, if for some reason you want to explicitly set -webkit-clip-path , you can do that too, again because * has zero specificity. The main downside to this is that it limits browser support to the intersection of the support for the feature you are using and support for CSS Variables. However, all browsers except Edge support CSS variables , and Edge is working on it . I cant see any other downsides to it (except having to use a different property name obvs), but if you do, let me know in the comments! I think theres still a lot to be discovered about cool uses of CSS variables. I wonder if there exists a variation of this technique to produce custom longhands, e.g. breaking box-shadow into --box-shadow-x , --box-shadow-y etc, but I cant think of anything yet. Can you? ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Markapp: A list of HTML libraries</title>
  <link>https://lea.verou.me/2016/08/markapp-a-list-of-html-libraries/</link>
  <pubDate>Sun, 17 May 2026 02:45:08 +0200</pubDate>
  <description>I have often lamented how many JavaScript developers don’t realize that a large percentage of HTML &amp; CSS authors are not comfortable writing JS, and struggle to use their libraries. To encourage libraries with HTML APIs, i.e. libraries that can be used without writing a line of JS, I made a website to list and promote them: MarkApp . The list is currently quite short, so I’m counting on you to expand it . Seen any libraries with good HTML APIs? Add them!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Markapp: A list of HTML libraries</title>
  <link>https://lea.verou.me/2016/08/markapp-a-list-of-html-libraries/</link>
  <pubDate>Sun, 17 May 2026 02:45:08 +0200</pubDate>
  <description>I have often lamented how many JavaScript developers dont realize that a large percentage of HTML &amp; CSS authors are not comfortable writing JS, and struggle to use their libraries. To encourage libraries with HTML APIs, i.e. libraries that can be used without writing a line of JS, I made a website to list and promote them: MarkApp . The list is currently quite short, so Im counting on you to expand it . Seen any libraries with good HTML APIs? Add them!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Multirange: A tiny polyfill for HTML5.1 two-handle sliders</title>
  <link>https://lea.verou.me/2016/05/introducing-multirange-a-tiny-polyfill-for-html5-two-handle-sliders/</link>
  <pubDate>Sun, 17 May 2026 02:45:07 +0200</pubDate>
  <description>As part of my preparation for my talk at CSSDay HTML Special , I was perusing the most recent HTML specs ( WHATWG Living Standard , W3C HTML 5.1 ) to see what undiscovered gems lay there. It turns out that HTML sliders have a lot of cool features specced that aren’t very well implemented: Ticks that snap via the list attribute and the element. This is fairly decently implemented, except labelled ticks, which is not supported anywhere. Vertical sliders when height &gt; width, implemented nowhere (instead, browsers employ proprietary ways for making sliders vertical: An orient=vertical attribute in Gecko, -webkit-appearance: slider-vertical; in WebKit/Blink and writing-mode: bt-lr; in IE/Edge). Good ol’ rotate transforms work too, but have the usual problems, such as layout not being affected by the transform. Two-handle sliders for ranges, via the multiple attribute. I made a quick testcase for all three, and to my disappointment (but not to my surprise), support was extremely poor. I was most excited about the last one, since I’ve been wanting range sliders in HTML for a long time. Sadly, there are no implementations. But hey, what if I could create a polyfill by cleverly overlaying two sliders? Would it be possible? I started experimenting in JSBin last night, just for the lolz, then soon realized this could actually work and started a GitHub repo . Since CSS variables are now supported almost everywhere, I’ve had a lot of fun using them. Sure, I could get broader support without them, but the code is much simpler, more elegant and customizable now. I also originally started with a Bliss dependency, but realized it wasn’t worth it for such a tiny script. So, enjoy, and contribute! Multirange</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Multirange: A tiny polyfill for HTML5.1 two-handle sliders</title>
  <link>https://lea.verou.me/2016/05/introducing-multirange-a-tiny-polyfill-for-html5-two-handle-sliders/</link>
  <pubDate>Sun, 17 May 2026 02:45:07 +0200</pubDate>
  <description>As part of my preparation for my talk at CSSDay HTML Special , I was perusing the most recent HTML specs ( WHATWG Living Standard , W3C HTML 5.1 ) to see what undiscovered gems lay there. It turns out that HTML sliders have a lot of cool features specced that arent very well implemented: Ticks that snap via the list attribute and the element. This is fairly decently implemented, except labelled ticks, which is not supported anywhere. Vertical sliders when height &gt; width, implemented nowhere (instead, browsers employ proprietary ways for making sliders vertical: An orient=vertical attribute in Gecko, -webkit-appearance: slider-vertical; in WebKit/Blink and writing-mode: bt-lr; in IE/Edge). Good ol rotate transforms work too, but have the usual problems, such as layout not being affected by the transform. Two-handle sliders for ranges, via the multiple attribute. I made a quick testcase for all three, and to my disappointment (but not to my surprise), support was extremely poor. I was most excited about the last one, since Ive been wanting range sliders in HTML for a long time. Sadly, there are no implementations. But hey, what if I could create a polyfill by cleverly overlaying two sliders? Would it be possible? I started experimenting in JSBin last night, just for the lolz, then soon realized this could actually work and started a GitHub repo . Since CSS variables are now supported almost everywhere, Ive had a lot of fun using them. Sure, I could get broader support without them, but the code is much simpler, more elegant and customizable now. I also originally started with a Bliss dependency, but realized it wasnt worth it for such a tiny script. So, enjoy, and contribute! Multirange</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My positive experience as a woman in tech</title>
  <link>https://lea.verou.me/2015/12/my-positive-experience-as-a-woman-in-tech/</link>
  <pubDate>Sun, 17 May 2026 02:45:06 +0200</pubDate>
  <description>Women speaking up about the sexism they have experienced in tech is great for raising awareness about the issues. However, when no positive stories get out, the overall picture painted is bleak, which could scare even more women away . Lucky for me, I fell in love with programming a decade before I even heard there is a sexism problem in tech . Had I read about it before, I might have decided to go for some other profession. Who wants to be fighting an uphill battle all her life? Thankfully, my experience has been quite different. Being in this industry has brought me nothing but happiness. Yes, there are several women who have had terrible experiences, and I’m in no way discounting them. They may even be the majority, though I am not aware of any statistics. However, there is also the other side. Those of us who have had incredibly positive experiences, and have always been treated with nothing but respect. That side’s stories need to be heard too, not silenced out of fear that we will become complacent and stop trying for more equality. Stories like mine should become the norm, not the exception. I’ve had a number of different roles in tech over the course of my life. I’ve been a student, a speaker &amp; author, I’ve worked at W3C , I’ve started &amp; maintain several successful open source projects and I’m currently dabbling in Computer Science research . In none of these roles did I ever feel I was unfairly treated due to my gender. That is not because I’m oblivious to sexism. I tend to be very sensitive to seeing it, and I often notice even the smallest acts of sexism (“death by a thousand paper cuts”). I see a lot of sexism in society overall. However, inside this industry, my gender never seemed to matter much, except perhaps in positive ways. On my open source repos , I have several contributors, the overwhelming majority of which, is male. I’ve never felt less respected due to my gender. I’ve never felt that my work was taken less seriously than male OSS developers. I’ve never felt my contributors would not listen to me. I’ve never felt my work was unfairly scrutinized. Even when I didn’t know something, or introduced a horrible bug, I’ve never been insulted or berated. The community has been nothing but friendly, helpful and respectful. If anything, I’ve sometimes wondered if my gender is the reason I hardly ever get any shit! On stage, I’ve never gotten any negative reactions. My talks always get excellent reviews, which have nothing to do with me being female. There is sometimes the odd complimentary tweet about my looks, but that’s not only exceedingly rare, but also always combined with a compliment about the actual talk content. My gender only affected my internal motivation: I often felt I had to be good, otherwise I would be painting all female tech speakers in a negative light. But other people are not at fault for my own stereotype threat. My book, CSS Secrets , has been as successful as an advanced CSS book could possibly aspire to be and got to an average of 5 stars on Amazon only a few months after its release. It’s steadily the 5th bestseller on CSS and was No 1 for a while shortly after publication. My gender did not seem to negatively affect any of that, even though there’s a picture of me in the french flap so there are no doubts about me being female (as if the name Lea wasn’t enough of a hint). As a student, I’ve never felt unfairly treated due to my gender by any of my professors, even the ones in Greece, a country that is not particularly famous for its gender equal society, to put it mildly. As a new researcher, I have no experience with publishing papers yet, so I cannot share any experiences on that. However, I’ve been treated with nothing but respect by both my advisor and colleagues. My opinion is always heard and valued and even when people don’t agree, I can debate it as long and as intensely as I want, without being seen as aggressive or “bossy”. I’ve worked at W3C and still participate as an Invited Expert in the CSS Working Group. In neither of these roles did my gender seem to matter in any way. I’ve always felt that my expertise and skillset were valued and my opinions heard. In fact, the most well-respected member of the CSS WG, is the only other woman in it: fantasai . Lastly, In all my years as a working professional, I’ve always negotiated any kind of remuneration, often hard. I’ve never lost an opportunity because of it, or been treated with negativity afterwards. On the flip side, sexism today is rarely overt. Given that hardly anybody over ten will flat out admit they think women are inferior (even to themselves), it’s often hard to tell when a certain behavior stems from sexist beliefs. If someone is a douchebag to you, are they doing it because you’re a woman, or because they’re douchebags? If someone is criticizing your work, are they doing it because they genuinely found something to criticize or because they’re negatively predisposed due to your gender? It’s impossible to know, especially since they don’t know either ! If you confront them on their sexism, they will deny all of it, and truly believe it. It takes a lot of introspection to see one’s internalized stereotypes. Therefore, a lot of the time, you cannot be sure if you have experienced sexist behavior, and there is no way to find out for sure, since the perpetrator doesn’t know either. There are many false positives and false negatives there. Perhaps I don’t feel I have experienced much sexism because I prefer to err on the side of false negatives. Paraphrasing Blackstone , I would rather not call out sexist behavior ten times, than wrongly accuse someone of it once. It might also have to do with my personality: I’m generally confident and can be very assertive. When somebody is being a jerk to me, I will not curl in a ball and question my life choices, I will reply to them in the same tone. However, those two alone cannot make the difference between a pit rampant with sexism and an egalitarian paradise. I think a lot of it is that we have genuinely made progress, and we should celebrate it with more women coming out with their positive experiences (it cannot just be me, right?). Ironically, one of the very few times I have experienced any sexism in the industry was when a dude was trying to be nice to me. I was in a speaker room at a conference in Las Vegas, frantically working on my slides, not participating in any of the conversations around me. At some point, one of the guys said “fuck” in a conversation, then turned and apologized to me. Irritated about the sudden interruption, I lifted my head and looked around. I noticed for the first time that day that I was the only woman in the room. His effort to be courteous made me feel that I was different, the odd one out , the one we must be careful around and treat like a fragile flower. To this day, I regret being too startled to reply “Eh, I don’t give a fuck” .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My positive experience as a woman in tech</title>
  <link>https://lea.verou.me/2015/12/my-positive-experience-as-a-woman-in-tech/</link>
  <pubDate>Sun, 17 May 2026 02:45:06 +0200</pubDate>
  <description>Women speaking up about the sexism they have experienced in tech is great for raising awareness about the issues. However, when no positive stories get out, the overall picture painted is bleak, which could scare even more women away . Lucky for me, I fell in love with programming a decade before I even heard there is a sexism problem in tech . Had I read about it before, I might have decided to go for some other profession. Who wants to be fighting an uphill battle all her life? Thankfully, my experience has been quite different. Being in this industry has brought me nothing but happiness. Yes, there are several women who have had terrible experiences, and Im in no way discounting them. They may even be the majority, though I am not aware of any statistics. However, there is also the other side. Those of us who have had incredibly positive experiences, and have always been treated with nothing but respect. That sides stories need to be heard too, not silenced out of fear that we will become complacent and stop trying for more equality. Stories like mine should become the norm, not the exception. Ive had a number of different roles in tech over the course of my life. Ive been a student, a speaker &amp; author, Ive worked at W3C , Ive started &amp; maintain several successful open source projects and Im currently dabbling in Computer Science research . In none of these roles did I ever feel I was unfairly treated due to my gender. That is not because Im oblivious to sexism. I tend to be very sensitive to seeing it, and I often notice even the smallest acts of sexism (death by a thousand paper cuts). I see a lot of sexism in society overall. However, inside this industry, my gender never seemed to matter much, except perhaps in positive ways. On my open source repos , I have several contributors, the overwhelming majority of which, is male. Ive never felt less respected due to my gender. Ive never felt that my work was taken less seriously than male OSS developers. Ive never felt my contributors would not listen to me. Ive never felt my work was unfairly scrutinized. Even when I didnt know something, or introduced a horrible bug, Ive never been insulted or berated. The community has been nothing but friendly, helpful and respectful. If anything, Ive sometimes wondered if my gender is the reason I hardly ever get any shit! On stage, Ive never gotten any negative reactions. My talks always get excellent reviews, which have nothing to do with me being female. There is sometimes the odd complimentary tweet about my looks, but thats not only exceedingly rare, but also always combined with a compliment about the actual talk content. My gender only affected my internal motivation: I often felt I had to be good, otherwise I would be painting all female tech speakers in a negative light. But other people are not at fault for my own stereotype threat. My book, CSS Secrets , has been as successful as an advanced CSS book could possibly aspire to be and got to an average of 5 stars on Amazon only a few months after its release. Its steadily the 5th bestseller on CSS and was No 1 for a while shortly after publication. My gender did not seem to negatively affect any of that, even though theres a picture of me in the french flap so there are no doubts about me being female (as if the name Lea wasnt enough of a hint). As a student, Ive never felt unfairly treated due to my gender by any of my professors, even the ones in Greece, a country that is not particularly famous for its gender equal society, to put it mildly. As a new researcher, I have no experience with publishing papers yet, so I cannot share any experiences on that. However, Ive been treated with nothing but respect by both my advisor and colleagues. My opinion is always heard and valued and even when people dont agree, I can debate it as long and as intensely as I want, without being seen as aggressive or bossy. Ive worked at W3C and still participate as an Invited Expert in the CSS Working Group. In neither of these roles did my gender seem to matter in any way. Ive always felt that my expertise and skillset were valued and my opinions heard. In fact, the most well-respected member of the CSS WG, is the only other woman in it: fantasai . Lastly, In all my years as a working professional, Ive always negotiated any kind of remuneration, often hard. Ive never lost an opportunity because of it, or been treated with negativity afterwards. On the flip side, sexism today is rarely overt. Given that hardly anybody over ten will flat out admit they think women are inferior (even to themselves), its often hard to tell when a certain behavior stems from sexist beliefs. If someone is a douchebag to you, are they doing it because youre a woman, or because theyre douchebags? If someone is criticizing your work, are they doing it because they genuinely found something to criticize or because theyre negatively predisposed due to your gender? Its impossible to know, especially since they dont know either ! If you confront them on their sexism, they will deny all of it, and truly believe it. It takes a lot of introspection to see ones internalized stereotypes. Therefore, a lot of the time, you cannot be sure if you have experienced sexist behavior, and there is no way to find out for sure, since the perpetrator doesnt know either. There are many false positives and false negatives there. Perhaps I dont feel I have experienced much sexism because I prefer to err on the side of false negatives. Paraphrasing Blackstone , I would rather not call out sexist behavior ten times, than wrongly accuse someone of it once. It might also have to do with my personality: Im generally confident and can be very assertive. When somebody is being a jerk to me, I will not curl in a ball and question my life choices, I will reply to them in the same tone. However, those two alone cannot make the difference between a pit rampant with sexism and an egalitarian paradise. I think a lot of it is that we have genuinely made progress, and we should celebrate it with more women coming out with their positive experiences (it cannot just be me, right?). Ironically, one of the very few times I have experienced any sexism in the industry was when a dude was trying to be nice to me. I was in a speaker room at a conference in Las Vegas, frantically working on my slides, not participating in any of the conversations around me. At some point, one of the guys said fuck in a conversation, then turned and apologized to me. Irritated about the sudden interruption, I lifted my head and looked around. I noticed for the first time that day that I was the only woman in the room. His effort to be courteous made me feel that I was different, the odd one out , the one we must be careful around and treat like a fragile flower. To this day, I regret being too startled to reply Eh, I dont give a fuck .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Bliss: A 3KB library for happier Vanilla JS</title>
  <link>https://lea.verou.me/2015/12/introducing-bliss-a-3kb-library-for-happier-vanilla-js/</link>
  <pubDate>Sun, 17 May 2026 02:45:05 +0200</pubDate>
  <description>Anyone who follows this blog, my twitter, or my work probably is aware that I’m not a huge fan of big libraries . I think wrapper objects are messy, and big libraries are overkill for smaller projects. On large projects, one uses frameworks like React or Angular anyway, not libraries. Anyone who writes Vanilla JS on a daily basis probably is aware that it can sometimes be, ahem, somewhat unpleasant to work with. Sure, the situation is orders of magnitude better than it was when I started. Back then, IE6 was the dominant browser and you needed a helper function to even add event listeners to an element (remember element.attachEvent?) or to get elements by a class! Fun fact: I learned JavaScript back then by writing my own library, called jAsset . I had not heard of jQuery when I started it in 2007, so I had even coded my own selector engine! (Anyone remember slickspeed ?) jAssset had plenty of nice helper functions, its own UI library and a cool logo. I had even started to make a website for its UI components, seen on the right. Sadly, jAsset died the sad inevitable death of all unreleased projects: Without external feedback, I had nobody to hold me back from adding to its API every time I personally needed a helper function. And adding, and adding, and adding… Until it became 5000+ loc long and its benefit of being lightweight or comprehensible had completely vanished. It collapsed under its own weight before it even saw the light of day. I abandoned it and went through a few years of using jQuery as my preferred helper library. Eventually, my distaste for wrapper objects , the constantly improving browser support for new APIs that made Vanilla JS more palatable, and the decline of overly conspicuous browser bugs led me to give it up. It was refreshing, and educational, but soon I came to realize that while Vanilla JS is orders of magnitude better than it was when I started, certain APIs are still quite unwieldy, which can be annoying if you use them often. For example, the Vanilla JS for creating an element, with other elements inside it, events and inline styles is so commonly needed, but also so verbose and WET , it can make one suicidal. However, Vanilla JS does not mean “use no abstractions”. Programming is all about abstractions! The Vanilla JS movement, is about favoring speed, smaller abstractions and understanding of the Web Platform, over big libraries that we treat as a black box. It’s about using libraries to save time, not to skip learning. So, I used my own tiny helpers, on every project. They were small and easy to understand, instead of several KB of code aiming to fix browser bugs I will likely never encounter and let me create complex nested DOM structures with a single JSON-like object. Over time, their API solidified and improved. On larger projects it was a separate file which I had tentatively codenamed Utopia (due to the lack of browser bug fixes and optimistic use of modern APIs). On smaller ones just a few helper methods (I could not live without at least my tiny 2 sloc $() and $$() helpers!). Here is a sample from my open source repos: dabblet.com/utopia.js regexplained/utopia.js dpi.lv/utopia.js css3test.com/utopia.js awesomplete.js $() and/or $$() helpers in: prefixfree CSSS animatable contrast-ratio cubic-bezier.com whathecolor corner-shape css-colors Notice any recurring themes there? :) I never mentioned Utopia.js anywhere, besides silently including it in my projects, so it went largely unnoticed. Sometimes people would look at it, ask me to release it, I’d promise them I would and then nothing. A few years ago, someone noticed it, liked it and documented it a bit (site is down now it seems). However, it was largely my little secret, hidden in public view. For the past half year, I’ve been working hard on my research project at MIT. It’s pretty awesome and is aimed at helping people who know HTML/ CSS but not JS, achieve more with Web technologies (and that’s all I can say for now). It’s also written in JS, so I used Utopia as a helper library, naturally. Utopia evolved even more with this project, got renamed to Bliss and got chainability via my idea about extending DOM prototypes without collisions (can be disabled and the property name is customizable). All this worked fine while I was the only person working on the project. Thankfully, I might get some help soon, and it might be rather inexperienced (the academia equivalent of interns). Help is very welcome, but it did raise the question: How will these people, who likely only know jQuery, work on the project? [1] The answer was that the time has come to polish, document and release Bliss to the world. My plan was to spend a weekend documenting it, but it ended up being a little over a week on and off, when procrastinating from other tasks I had to do. However, I’m very proud of the resulting docs, so much that I gifted myself a domain for it. They are fairly extensive (though some functions still need work) and has two things I always missed in other API docs: Recommendations about what Vanilla JS to use instead when appropriate, instead of guiding people into using library methods even when Vanilla JS would have been perfectly sufficient. A “Show Implementation” button showing the implementation, so you can both learn, and judge whether it’s needed or not, instead of assuming that you should use it over Vanilla JS because it has magic pixie dust. This way, the docs also serve as a source viewer! So, enjoy Bliss . The helper library for people who don’t like helper libraries. :) In a way, it feels that a journey of 8 years, finally ends today. I hope the result makes you blissful too. blissfuljs.com Oh, and don’t forget to follow @blissfuljs on twitter! [1]: Academia is often a little behind tech-wise, so everyone uses jQuery here — hardly any exceptions. Even though browser support doesn’t usually even matter to research projects!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Bliss: A 3KB library for happier Vanilla JS</title>
  <link>https://lea.verou.me/2015/12/introducing-bliss-a-3kb-library-for-happier-vanilla-js/</link>
  <pubDate>Sun, 17 May 2026 02:45:05 +0200</pubDate>
  <description>Anyone who follows this blog, my twitter, or my work probably is aware that Im not a huge fan of big libraries . I think wrapper objects are messy, and big libraries are overkill for smaller projects. On large projects, one uses frameworks like React or Angular anyway, not libraries. Anyone who writes Vanilla JS on a daily basis probably is aware that it can sometimes be, ahem, somewhat unpleasant to work with. Sure, the situation is orders of magnitude better than it was when I started. Back then, IE6 was the dominant browser and you needed a helper function to even add event listeners to an element (remember element.attachEvent?) or to get elements by a class! Fun fact: I learned JavaScript back then by writing my own library, called jAsset . I had not heard of jQuery when I started it in 2007, so I had even coded my own selector engine! (Anyone remember slickspeed ?) jAssset had plenty of nice helper functions, its own UI library and a cool logo. I had even started to make a website for its UI components, seen on the right. Sadly, jAsset died the sad inevitable death of all unreleased projects: Without external feedback, I had nobody to hold me back from adding to its API every time I personally needed a helper function. And adding, and adding, and adding Until it became 5000+ loc long and its benefit of being lightweight or comprehensible had completely vanished. It collapsed under its own weight before it even saw the light of day. I abandoned it and went through a few years of using jQuery as my preferred helper library. Eventually, my distaste for wrapper objects , the constantly improving browser support for new APIs that made Vanilla JS more palatable, and the decline of overly conspicuous browser bugs led me to give it up. It was refreshing, and educational, but soon I came to realize that while Vanilla JS is orders of magnitude better than it was when I started, certain APIs are still quite unwieldy, which can be annoying if you use them often. For example, the Vanilla JS for creating an element, with other elements inside it, events and inline styles is so commonly needed, but also so verbose and WET , it can make one suicidal. However, Vanilla JS does not mean use no abstractions. Programming is all about abstractions! The Vanilla JS movement, is about favoring speed, smaller abstractions and understanding of the Web Platform, over big libraries that we treat as a black box. Its about using libraries to save time, not to skip learning. So, I used my own tiny helpers, on every project. They were small and easy to understand, instead of several KB of code aiming to fix browser bugs I will likely never encounter and let me create complex nested DOM structures with a single JSON-like object. Over time, their API solidified and improved. On larger projects it was a separate file which I had tentatively codenamed Utopia (due to the lack of browser bug fixes and optimistic use of modern APIs). On smaller ones just a few helper methods (I could not live without at least my tiny 2 sloc $() and $$() helpers!). Here is a sample from my open source repos: dabblet.com/utopia.js regexplained/utopia.js dpi.lv/utopia.js css3test.com/utopia.js awesomplete.js $() and/or $$() helpers in: prefixfree CSSS animatable contrast-ratio cubic-bezier.com whathecolor corner-shape css-colors Notice any recurring themes there? :) I never mentioned Utopia.js anywhere, besides silently including it in my projects, so it went largely unnoticed. Sometimes people would look at it, ask me to release it, Id promise them I would and then nothing. A few years ago, someone noticed it, liked it and documented it a bit (site is down now it seems). However, it was largely my little secret, hidden in public view. For the past half year, Ive been working hard on my research project at MIT. Its pretty awesome and is aimed at helping people who know HTML/ CSS but not JS, achieve more with Web technologies (and thats all I can say for now). Its also written in JS, so I used Utopia as a helper library, naturally. Utopia evolved even more with this project, got renamed to Bliss and got chainability via my idea about extending DOM prototypes without collisions (can be disabled and the property name is customizable). All this worked fine while I was the only person working on the project. Thankfully, I might get some help soon, and it might be rather inexperienced (the academia equivalent of interns). Help is very welcome, but it did raise the question: How will these people, who likely only know jQuery, work on the project? [1] The answer was that the time has come to polish, document and release Bliss to the world. My plan was to spend a weekend documenting it, but it ended up being a little over a week on and off, when procrastinating from other tasks I had to do. However, Im very proud of the resulting docs, so much that I gifted myself a domain for it. They are fairly extensive (though some functions still need work) and has two things I always missed in other API docs: Recommendations about what Vanilla JS to use instead when appropriate, instead of guiding people into using library methods even when Vanilla JS would have been perfectly sufficient. A Show Implementation button showing the implementation, so you can both learn, and judge whether its needed or not, instead of assuming that you should use it over Vanilla JS because it has magic pixie dust. This way, the docs also serve as a source viewer! So, enjoy Bliss . The helper library for people who dont like helper libraries. :) In a way, it feels that a journey of 8 years, finally ends today. I hope the result makes you blissful too. blissfuljs.com Oh, and dont forget to follow @blissfuljs on twitter! [1]: Academia is often a little behind tech-wise, so everyone uses jQuery here hardly any exceptions. Even though browser support doesnt usually even matter to research projects!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Copying object properties, the robust way</title>
  <link>https://lea.verou.me/2015/08/copying-properties-the-robust-way/</link>
  <pubDate>Sun, 17 May 2026 02:45:03 +0200</pubDate>
  <description>If, like me, you try to avoid using heavy libraries when not needed , you must have definitely written a helper to copy properties from one object to another at some point. It’s needed so often that it’s just silly to write the same loops over and over again. These days, most of my time is spent working on my research project at MIT , which I will hopefully reveal later this year. In that, I’m using a lightweight homegrown helper library, which I might release separately at some point as I think it has potential in its own right, for a number of reasons. Of course, it needed to have a simple extend() method as well, to copy properties from one object to another. Let’s assume for the purposes of this article that we’re talking about shallow copying, that overwrites are allowed, and let’s omit hasOwnProperty() checks to make code easier to read. It’s a simple task, right? Our first attempt might look like this: $.extend = function (to, from) { for (var property in from) { to[property] = from[property]; } return to; } This works fine, until you try it on objects with accessors or other types of properties defined via Object.defineProperty() or get / set keywords. What do you do then? Our next iteration could look like this: $.extend = function (to, from) { for (var property in from) { Object.defineProperty(to, property, Object.getOwnPropertyDescriptor(from, property)); } return to; } This works much better, until it fails, and it can fail pretty epically. Try this: $.extend(document.body.style, { backgroundColor: &quot;red&quot; }); Both in Chrome and Firefox, the results are super weird. Even though reading document.body.style.backgroundColor will return &quot;red&quot; , no style will have actually been applied. In Firefox it even destroyed the native setter entirely and any future attempts to set document.body.style.backgroundColor in the console did absolutely nothing. In contrast, the previous naïve approach worked fine for this. It’s clear that we need to somehow combine the two approaches, using Object.defineProperty() only when actually needed. But when is it actually not needed? One obvious case is if the descriptor is undefined (such as with some native properties). Also, in simple properties, such as those in our object literal, the descriptor will be of the form {value: somevalue, writable: true, enumerable: true, configurable: true} . So, the next obvious step would be: $.extend = function (to, from) { var descriptor = Object.getOwnPropertyDescriptor(from, property); if (descriptor &amp;&amp; (!descriptor.writable || !descriptor.configurable || !descriptor.enumerable || descriptor.get || descriptor.set)) { Object.defineProperty(to, property, descriptor); } else { to[property] = from[property]; } } This works perfectly, but is a little clumsy. I’ve currently left it at that, but any suggestions for making it more elegant are welcome :) FWIW, I looked at jQuery’s implementation of jQuery.extend() after this, and it seems it doesn’t even handle accessors at all, unless I missed something. Time for a pull request, perhaps… Edit: As MaxArt pointed out in the comments, there is a similar native method in ES6, Object.assign() . However, it does not deal with copying accessors, so does not deal with this problem either.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Copying object properties, the robust way</title>
  <link>https://lea.verou.me/2015/08/copying-properties-the-robust-way/</link>
  <pubDate>Sun, 17 May 2026 02:45:03 +0200</pubDate>
  <description>If, like me, you try to avoid using heavy libraries when not needed , you must have definitely written a helper to copy properties from one object to another at some point. Its needed so often that its just silly to write the same loops over and over again. These days, most of my time is spent working on my research project at MIT , which I will hopefully reveal later this year. In that, Im using a lightweight homegrown helper library, which I might release separately at some point as I think it has potential in its own right, for a number of reasons. Of course, it needed to have a simple extend() method as well, to copy properties from one object to another. Lets assume for the purposes of this article that were talking about shallow copying, that overwrites are allowed, and lets omit hasOwnProperty() checks to make code easier to read. Its a simple task, right? Our first attempt might look like this: $.extend = function (to, from) { for (var property in from) { to[property] = from[property]; } return to; } This works fine, until you try it on objects with accessors or other types of properties defined via Object.defineProperty() or get / set keywords. What do you do then? Our next iteration could look like this: $.extend = function (to, from) { for (var property in from) { Object.defineProperty(to, property, Object.getOwnPropertyDescriptor(from, property)); } return to; } This works much better, until it fails, and it can fail pretty epically. Try this: $.extend(document.body.style, { backgroundColor: &quot;red&quot; }); Both in Chrome and Firefox, the results are super weird. Even though reading document.body.style.backgroundColor will return &quot;red&quot; , no style will have actually been applied. In Firefox it even destroyed the native setter entirely and any future attempts to set document.body.style.backgroundColor in the console did absolutely nothing. In contrast, the previous naïve approach worked fine for this. Its clear that we need to somehow combine the two approaches, using Object.defineProperty() only when actually needed. But when is it actually not needed? One obvious case is if the descriptor is undefined (such as with some native properties). Also, in simple properties, such as those in our object literal, the descriptor will be of the form {value: somevalue, writable: true, enumerable: true, configurable: true} . So, the next obvious step would be: $.extend = function (to, from) { var descriptor = Object.getOwnPropertyDescriptor(from, property); if (descriptor &amp;&amp; (!descriptor.writable || !descriptor.configurable || !descriptor.enumerable || descriptor.get || descriptor.set)) { Object.defineProperty(to, property, descriptor); } else { to[property] = from[property]; } } This works perfectly, but is a little clumsy. Ive currently left it at that, but any suggestions for making it more elegant are welcome :) FWIW, I looked at jQuerys implementation of jQuery.extend() after this, and it seems it doesnt even handle accessors at all, unless I missed something. Time for a pull request, perhaps Edit: As MaxArt pointed out in the comments, there is a similar native method in ES6, Object.assign() . However, it does not deal with copying accessors, so does not deal with this problem either.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On the blindness of blind reviews</title>
  <link>https://lea.verou.me/2015/08/on-the-blindness-of-blind-reviews/</link>
  <pubDate>Sun, 17 May 2026 02:25:19 +0200</pubDate>
  <description>Over the last couple of years, blind reviews have been popularized as the ultimate method for fair talk selection in industry conferences. While I don’t really submit proposals myself, I have served several times on the other side of the process, doing speaker selection in conference committees, and the more data points I collect, the more convinced I become that the blind selection process is fundamentally flawed. Blind reviews come from the world of academia. However, in academic conferences, you do not judge a talk by a 1-2 paragraph abstract, but by a 10+ page paper, so there’s way more to judge by. In addition, in academia the content of the research matters infinitely more than the quality of a talk. In industry conferences, selection committees in blind reviews have both way less data to use , and a much harder task , as they need to balance several factors (content, speaker skill, talk quality etc). It’s no surprise that the results end up being even more of a gamble. Blind reviews result in conservative talk selection. More often than not, I remember me and my fellow committee members saying “Damn, this talk could be great with the right presenter, but that’s rare” and giving it a poor or average score. Few topics can make good talks regardless of the presenters. Therefore, when there is little information on the speaker in the initial selection round, talk selection ends up being conservative, rejecting more challenging topics that need a skilled speaker to shine and sticking to safer choices. One of my most successful talks ever was “The humble border-radius” which was shortlisted for a .net award for Conference Talk of The Year 2014. It would never have passed any blind review. There is no committee in their right mind that would have accepted a 45 minute talk about …border-radius. The conferences I presented it at invited me as a speaker, carte blanche, and trusted me to present on whatever I felt like. Judging by the reviews, they were not disappointed. In addition, all too many times I’ve seen great speakers get poor scores in blind reviews, not because their talks were not good, but because writing good abstracts is an entirely separate skill . Blind reviews remove anything that could cause bias, but they do so by striping all personality away from a proposal. In addition, a good abstract for a blind review is not necessarily a good abstract in general. For example, blind reviews penalize more mysterious/teasy abstracts and tend to be skewed towards overly detailed ones, since it’s the only data the committee gets for these talks (bonus points here for CfS that have a separate field for more details to conf organizers). “But what about newcomers to the conference circuit? What about bias elimination?” one might ask. Both very valid concerns. I’m not saying any kind of anonymization is a bad idea. I’m saying that in their present form in industry conferences, blind reviews are flawed. For example, an initial round of blind reviews to pick good talks, without rejecting any at that stage, would probably solve these issues, without suffering from the flaws mentioned above. Disclaimer: I do recognize that most people in these committees are doing their best to select fairly, and putting many hours of (usually volunteer) work in it. I’m not criticizing them, I’m criticizing the process. And yes, I recognize that it’s a process that has come out of very good intentions (eliminating bias). However, good intentions are not a guarantee for infallibility .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On the blindness of blind reviews</title>
  <link>https://lea.verou.me/2015/08/on-the-blindness-of-blind-reviews/</link>
  <pubDate>Sun, 17 May 2026 02:25:19 +0200</pubDate>
  <description>Over the last couple of years, blind reviews have been popularized as the ultimate method for fair talk selection in industry conferences. While I dont really submit proposals myself, I have served several times on the other side of the process, doing speaker selection in conference committees, and the more data points I collect, the more convinced I become that the blind selection process is fundamentally flawed. Blind reviews come from the world of academia. However, in academic conferences, you do not judge a talk by a 1-2 paragraph abstract, but by a 10+ page paper, so theres way more to judge by. In addition, in academia the content of the research matters infinitely more than the quality of a talk. In industry conferences, selection committees in blind reviews have both way less data to use , and a much harder task , as they need to balance several factors (content, speaker skill, talk quality etc). Its no surprise that the results end up being even more of a gamble. Blind reviews result in conservative talk selection. More often than not, I remember me and my fellow committee members saying Damn, this talk could be great with the right presenter, but thats rare and giving it a poor or average score. Few topics can make good talks regardless of the presenters. Therefore, when there is little information on the speaker in the initial selection round, talk selection ends up being conservative, rejecting more challenging topics that need a skilled speaker to shine and sticking to safer choices. One of my most successful talks ever was The humble border-radius which was shortlisted for a .net award for Conference Talk of The Year 2014. It would never have passed any blind review. There is no committee in their right mind that would have accepted a 45 minute talk about border-radius. The conferences I presented it at invited me as a speaker, carte blanche, and trusted me to present on whatever I felt like. Judging by the reviews, they were not disappointed. In addition, all too many times Ive seen great speakers get poor scores in blind reviews, not because their talks were not good, but because writing good abstracts is an entirely separate skill . Blind reviews remove anything that could cause bias, but they do so by striping all personality away from a proposal. In addition, a good abstract for a blind review is not necessarily a good abstract in general. For example, blind reviews penalize more mysterious/teasy abstracts and tend to be skewed towards overly detailed ones, since its the only data the committee gets for these talks (bonus points here for CfS that have a separate field for more details to conf organizers). But what about newcomers to the conference circuit? What about bias elimination? one might ask. Both very valid concerns. Im not saying any kind of anonymization is a bad idea. Im saying that in their present form in industry conferences, blind reviews are flawed. For example, an initial round of blind reviews to pick good talks, without rejecting any at that stage, would probably solve these issues, without suffering from the flaws mentioned above. Disclaimer: I do recognize that most people in these committees are doing their best to select fairly, and putting many hours of (usually volunteer) work in it. Im not criticizing them, Im criticizing the process. And yes, I recognize that its a process that has come out of very good intentions (eliminating bias). However, good intentions are not a guarantee for infallibility .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Stretchy: Form element autosizing, the way it should be</title>
  <link>https://lea.verou.me/2015/07/stretchy-form-element-autosizing-the-way-it-should-be/</link>
  <pubDate>Sun, 17 May 2026 02:25:18 +0200</pubDate>
  <description>As you might be aware, these days a good chunk of my time is spent working on research, at MIT . Although it’s still too early to talk about my research project, I can say that it’s related to the Web and it will be open source, both of which are pretty awesome (getting paid to work on cool open source stuff is the dream, right?). The one thing I can mention about my project is that it involves a lot of editing of Web content. And since contentEditable is a mess, as you all know, I decided to use form controls styled like the content being edited. This meant that I needed a good script for form control autosizing, one that worked on multiple types of form controls (inputs, textareas, even select menus). In addition, I needed the script to smoothly work for newly added controls, without me having to couple the rest of my code with it and call API methods or fire custom events every time new controls were added anywhere. A quick look at the existing options quickly made it obvious that I had to write my own. After writing it, I realized this could be released entirely separately as it was a standalone utility. So Stretchy was born :) I made a quick page for it, fixed a few cross-browser bugs that needed fixing anyway, put it up on Github and here it is! Enjoy! PS: You can also use it as a bookmarklet, to autosize form controls on an existing page, if a form is bothering you with its poor usability. You will find it in the footer.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Stretchy: Form element autosizing, the way it should be</title>
  <link>https://lea.verou.me/2015/07/stretchy-form-element-autosizing-the-way-it-should-be/</link>
  <pubDate>Sun, 17 May 2026 02:25:18 +0200</pubDate>
  <description>As you might be aware, these days a good chunk of my time is spent working on research, at MIT . Although its still too early to talk about my research project, I can say that its related to the Web and it will be open source, both of which are pretty awesome (getting paid to work on cool open source stuff is the dream, right?). The one thing I can mention about my project is that it involves a lot of editing of Web content. And since contentEditable is a mess, as you all know, I decided to use form controls styled like the content being edited. This meant that I needed a good script for form control autosizing, one that worked on multiple types of form controls (inputs, textareas, even select menus). In addition, I needed the script to smoothly work for newly added controls, without me having to couple the rest of my code with it and call API methods or fire custom events every time new controls were added anywhere. A quick look at the existing options quickly made it obvious that I had to write my own. After writing it, I realized this could be released entirely separately as it was a standalone utility. So Stretchy was born :) I made a quick page for it, fixed a few cross-browser bugs that needed fixing anyway, put it up on Github and here it is! Enjoy! PS: You can also use it as a bookmarklet, to autosize form controls on an existing page, if a form is bothering you with its poor usability. You will find it in the footer.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Spot the unsubscribe (link)!</title>
  <link>https://lea.verou.me/2015/07/spot-the-unsubscribe-link/</link>
  <pubDate>Sun, 17 May 2026 02:25:17 +0200</pubDate>
  <description>After getting fed up with too many “promotional” emails and newsletters with incredibly obscure unsubscribe links, I decided to make this tumblr to point out such examples of digital douchebaggery. This annoying dark pattern is so widespread that Google even added a feature to Gmail for making those unsubscribe links obvious ! Unsubscribe links are crucial to promotional emails. They are not just another menu item. They are not something that should be hidden in a blurb of tiny low contrast text. Unsubscribe links should be immediately obvious to anyone looking for them. You want people to be reading your email because they’re interested, not because they can‘t find the way out. Otherwise you are the digital equivalent of those annoying door-to-door salesmen who just won’t go away. — From my introductory post on Spot the unsubscribe! On the spur of the moment, after yet another email newsletter with a hard to find Unsubscribe link, I decided to quickly put together a tumblog about this UX pet peeve of mine, called Spot the Unsubscribe! . In less than an hour, it was ready and had a few posts as well :) Hopefully if this bothers others as well, there will be submissions . Otherwise, new posts will be rather infrequent.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Spot the unsubscribe (link)!</title>
  <link>https://lea.verou.me/2015/07/spot-the-unsubscribe-link/</link>
  <pubDate>Sun, 17 May 2026 02:25:17 +0200</pubDate>
  <description>After getting fed up with too many promotional emails and newsletters with incredibly obscure unsubscribe links, I decided to make this tumblr to point out such examples of digital douchebaggery. This annoying dark pattern is so widespread that Google even added a feature to Gmail for making those unsubscribe links obvious ! Unsubscribe links are crucial to promotional emails. They are not just another menu item. They are not something that should be hidden in a blurb of tiny low contrast text. Unsubscribe links should be immediately obvious to anyone looking for them. You want people to be reading your email because theyre interested, not because they cant find the way out. Otherwise you are the digital equivalent of those annoying door-to-door salesmen who just wont go away. From my introductory post on Spot the unsubscribe! On the spur of the moment, after yet another email newsletter with a hard to find Unsubscribe link, I decided to quickly put together a tumblog about this UX pet peeve of mine, called Spot the Unsubscribe! . In less than an hour, it was ready and had a few posts as well :) Hopefully if this bothers others as well, there will be submissions . Otherwise, new posts will be rather infrequent.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Conical gradients, today!</title>
  <link>https://lea.verou.me/2015/06/conical-gradients-today/</link>
  <pubDate>Sun, 17 May 2026 02:25:16 +0200</pubDate>
  <description>It’s no secret that I like conical gradients. In as early as 2011, I wrote a draft for conical-gradient() in CSS , that Tab later said helped him when he added them in CSS Image Values Level 4 in 2012. However, almost three years later, no progress has been made in implementing them. Sure, the spec is still relatively incomplete, but that’s not the reason conical gradients have gotten no traction. Far more underspecified features have gotten experimental implementations in the past. The reason conical gradients are still unimplemented, is because very few developers know they exist, so browsers see no demand. Another reason was that Cairo , the graphics library used in Chrome and Firefox had no way of drawing a conical gradient. However, this changed a while ago, when they supported mesh gradients , of which conical gradients are a mere special case. Recently, I was giving a talk on creating pie charts with CSS on a few conferences, and yet again, I was reminded of how useful conical gradients can be. While every CSS or SVG solution is several lines of code with varying levels of hackiness, conical gradients can give us a pie chart with a straightforward, DRY, one liner. For example, this is how to create a pie chart that shows 40% in gold and 60% in #f06: padding: 5em; /* size */ background: conic-gradient(gold 40%, #f06 0); border-radius: 50%; /* make it round */ So, I decided to take matters in my own hands. I wrote a polyfill , which I also used in my talk to demonstrate how awesome conical gradients can be and what cool things they can do. Today, during my CSSConf talk, I released it publicly. In addition, I mention to developers how important speaking up is for getting their favorite features implemented. Browsers prioritize which features to implement based on what developers ask for. It’s a pity that so few of us realize how much of a say we collectively have in this. This is more obvious with Microsoft and their Uservoice forum where developers can vote on which features they want to see worked on, but pretty much every major browser works in a similar way. They monitor what developers request and what other browsers implement, and decide accordingly. The squeaky wheel will get the feature, so if you really want to see something implemented, speak up . Since “speaking up” can be a bit vague ( “speak up where?” I can hear you asking), I also filed bug reports with all major browsers, that you can also find in the polyfill page , so that you can comment or vote on them. That doesn’t mean that speaking up on blogs or social media is not useful though: That’s why browsers have devrel teams. The more noise we collectively make about the features we want, the more likely it is to be heard. However, the odds are higher if we all channel our voices to the venues browser developers follow and our voice is stronger and louder if we concentrate it in the same places instead of having many separate voices all over the place. Also, I’m using the term “noise” here a bit figuratively. While it’s valuable to make it clear that we are interested in a certain feature, it’s even more useful to say why . Providing use cases will not only grab browsers’ attention more, but it will also convince other developers as well. So go ahead, play with conic gradients, and if you agree with me that they are fucking awesome and we need them natively on the Web, make noise . conic-gradient() polyfill</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Conical gradients, today!</title>
  <link>https://lea.verou.me/2015/06/conical-gradients-today/</link>
  <pubDate>Sun, 17 May 2026 02:25:16 +0200</pubDate>
  <description>Its no secret that I like conical gradients. In as early as 2011, I wrote a draft for conical-gradient() in CSS , that Tab later said helped him when he added them in CSS Image Values Level 4 in 2012. However, almost three years later, no progress has been made in implementing them. Sure, the spec is still relatively incomplete, but thats not the reason conical gradients have gotten no traction. Far more underspecified features have gotten experimental implementations in the past. The reason conical gradients are still unimplemented, is because very few developers know they exist, so browsers see no demand. Another reason was that Cairo , the graphics library used in Chrome and Firefox had no way of drawing a conical gradient. However, this changed a while ago, when they supported mesh gradients , of which conical gradients are a mere special case. Recently, I was giving a talk on creating pie charts with CSS on a few conferences, and yet again, I was reminded of how useful conical gradients can be. While every CSS or SVG solution is several lines of code with varying levels of hackiness, conical gradients can give us a pie chart with a straightforward, DRY, one liner. For example, this is how to create a pie chart that shows 40% in gold and 60% in #f06: padding: 5em; /* size */ background: conic-gradient(gold 40%, #f06 0); border-radius: 50%; /* make it round */ So, I decided to take matters in my own hands. I wrote a polyfill , which I also used in my talk to demonstrate how awesome conical gradients can be and what cool things they can do. Today, during my CSSConf talk, I released it publicly. In addition, I mention to developers how important speaking up is for getting their favorite features implemented. Browsers prioritize which features to implement based on what developers ask for. Its a pity that so few of us realize how much of a say we collectively have in this. This is more obvious with Microsoft and their Uservoice forum where developers can vote on which features they want to see worked on, but pretty much every major browser works in a similar way. They monitor what developers request and what other browsers implement, and decide accordingly. The squeaky wheel will get the feature, so if you really want to see something implemented, speak up . Since speaking up can be a bit vague ( speak up where? I can hear you asking), I also filed bug reports with all major browsers, that you can also find in the polyfill page , so that you can comment or vote on them. That doesnt mean that speaking up on blogs or social media is not useful though: Thats why browsers have devrel teams. The more noise we collectively make about the features we want, the more likely it is to be heard. However, the odds are higher if we all channel our voices to the venues browser developers follow and our voice is stronger and louder if we concentrate it in the same places instead of having many separate voices all over the place. Also, Im using the term noise here a bit figuratively. While its valuable to make it clear that we are interested in a certain feature, its even more useful to say why . Providing use cases will not only grab browsers attention more, but it will also convince other developers as well. So go ahead, play with conic gradients, and if you agree with me that they are fucking awesome and we need them natively on the Web, make noise . conic-gradient() polyfill</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Idea: Extending native DOM prototypes without collisions</title>
  <link>https://lea.verou.me/2015/04/idea-extending-native-dom-prototypes-without-collisions/</link>
  <pubDate>Sun, 17 May 2026 02:25:15 +0200</pubDate>
  <description>As I pointed out in yesterday’s blog post , one of the reasons why I don’t like using jQuery is its wrapper objects. For jQuery, this was a wise decision: Back in 2006 when it was first developed, IE releases had a pretty icky memory leak bug that could be easily triggered when one added properties to elements. Oh, and we also didn’t have access to element prototypes on IE back then, so we had to add these properties manually on every element. Prototype.js attempted to go that route and the result was such a mess that they decided to change their decision in Prototype 2.0 and go with wrapper objects too. There were even long essays being written back then about how much of a monumentally bad idea it was to extend DOM elements . The first IE release that exposed element prototypes was IE8: We got access to Node.prototype, Element.prototype and a few more. Some were mutable, some were not. On IE9, we got the full bunch, including HTMLElement.prototype and its descendants, such as HTMLParagraphElement. The memory leak bugs were mitigated in IE8 and fixed in IE9. However, we still don’t extend native DOM elements, and for good reason: collisions are still a very real risk. No library wants to add a bunch of methods on elements, it’s just bad form. It’s like being invited in someone’s house and defecating all over the floor. But what if we could add methods to elements without the chance of collisions? (well, technically, by minimizing said chance). We could only add one property to Element.prototype, and then hang all our methods on that. E.g. if our library was called yolo and had two methods, foo() and bar(), calls to it would look like: var element = document.querySelector(&quot;.someclass&quot;); element.yolo.foo(); element.yolo.bar(); // or you can even chain, if you return the element in each of them! element.yolo.foo().yolo.bar(); Sure, it’s more awkward than wrapper objects, but the benefit of using native DOM elements is worth it if you ask me. Of course, YMMV. It’s basically exactly the same thing we do with globals : We all know that adding tons of global variables is bad practice, so every library adds one global and hangs everything off of that. However, if we try to implement something like this in the naïve way, we will find that it’s kind of hard to reference the element used from our namespaced functions: Element.prototype.yolo = { foo: function () { console.log(this); }, bar: function () { /* ... */ } }; someElement.yolo.foo(); // Object {foo: function, bar: function} What happened here? this inside any of these functions refers to the object that they are called on, not the element that object is hanging on! We need to be a bit more clever to get around this issue. Keep in mind that this in the object inside yolo would have access to the element we’re trying to hang these methods off of. But we’re not running any code there, so we’re not taking advantage of that. If only we could get a reference to that object’s context! However, running a function (e.g. element.yolo().foo() ) would spoil our nice API. Wait a second. We can run code on properties, via ES5 accessors! We could do something like this: Object.defineProperty(Element.prototype, &quot;yolo&quot;, { get: function () { return { element: this, foo: function() { console.log(this.element); }, bar: function() { /* ... */ } } }, configurable: true, writeable: false }); someElement.yolo.foo(); // It works! (Logs our actual element) This works, but there is a rather annoying issue here: We are generating this object and redefining our functions every single time this property is called. This is a rather bad idea for performance. Ideally, we want to generate this object once , and then return the generated object. We also don’t want every element to have its own completely separate instance of the functions we defined, we want to define these functions on a prototype, and use the wonderful JS inheritance for them, so that our library is also dynamically extensible . Luckily, there is a way to do all this too: var Yolo = function(element) { this.element = element; }; Yolo.prototype = { foo: function() { console.log(this.element); }, bar: function() { /* ... */ } }; Object.defineProperty(Element.prototype, &quot;yolo&quot;, { get: function () { Object.defineProperty(this, &quot;yolo&quot;, { value: new Yolo(this) }); return this.yolo; }, configurable: true, writeable: false }); someElement.yolo.foo(); // It works! (Logs our actual element) // And it’s dynamically extensible too! Yolo.prototype.baz = function(color) { this.element.style.background = color; }; someElement.yolo.baz(&quot;red&quot;) // Our element gets a red background Note that in the above, the getter is only executed once . After that, it overwrites the yolo property with a static value: An instance of the Yolo object. Since we’re using Object.defineProperty() we also don’t run into the issue of breaking enumeration ( for..in loops), since these properties have enumerable: false by default. There is still the wart that these methods need to use this.element instead of this . We could fix this by wrapping them: for (let method in Yolo.prototype) { Yolo.prototype[method] = function(){ var callback = Yolo.prototype[method]; Yolo.prototype[method] = function () { var ret = callback.apply(this.element, arguments); // Return the element, for chainability! return ret === undefined? this.element : ret; } } } However, now you can’t dynamically add methods to Yolo.prototype and have them automatically work like the native Yolo methods in element.yolo , so it kinda hurts extensibility (of course you could still add methods that use this.element and they would work). Thoughts?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Idea: Extending native DOM prototypes without collisions</title>
  <link>https://lea.verou.me/2015/04/idea-extending-native-dom-prototypes-without-collisions/</link>
  <pubDate>Sun, 17 May 2026 02:25:15 +0200</pubDate>
  <description>As I pointed out in yesterdays blog post , one of the reasons why I dont like using jQuery is its wrapper objects. For jQuery, this was a wise decision: Back in 2006 when it was first developed, IE releases had a pretty icky memory leak bug that could be easily triggered when one added properties to elements. Oh, and we also didnt have access to element prototypes on IE back then, so we had to add these properties manually on every element. Prototype.js attempted to go that route and the result was such a mess that they decided to change their decision in Prototype 2.0 and go with wrapper objects too. There were even long essays being written back then about how much of a monumentally bad idea it was to extend DOM elements . The first IE release that exposed element prototypes was IE8: We got access to Node.prototype, Element.prototype and a few more. Some were mutable, some were not. On IE9, we got the full bunch, including HTMLElement.prototype and its descendants, such as HTMLParagraphElement. The memory leak bugs were mitigated in IE8 and fixed in IE9. However, we still dont extend native DOM elements, and for good reason: collisions are still a very real risk. No library wants to add a bunch of methods on elements, its just bad form. Its like being invited in someones house and defecating all over the floor. But what if we could add methods to elements without the chance of collisions? (well, technically, by minimizing said chance). We could only add one property to Element.prototype, and then hang all our methods on that. E.g. if our library was called yolo and had two methods, foo() and bar(), calls to it would look like: var element = document.querySelector(&quot;.someclass&quot;); element.yolo.foo(); element.yolo.bar(); // or you can even chain, if you return the element in each of them! element.yolo.foo().yolo.bar(); Sure, its more awkward than wrapper objects, but the benefit of using native DOM elements is worth it if you ask me. Of course, YMMV. Its basically exactly the same thing we do with globals : We all know that adding tons of global variables is bad practice, so every library adds one global and hangs everything off of that. However, if we try to implement something like this in the naïve way, we will find that its kind of hard to reference the element used from our namespaced functions: Element.prototype.yolo = { foo: function () { console.log(this); }, bar: function () { /* ... */ } }; someElement.yolo.foo(); // Object {foo: function, bar: function} What happened here? this inside any of these functions refers to the object that they are called on, not the element that object is hanging on! We need to be a bit more clever to get around this issue. Keep in mind that this in the object inside yolo would have access to the element were trying to hang these methods off of. But were not running any code there, so were not taking advantage of that. If only we could get a reference to that objects context! However, running a function (e.g. element.yolo().foo() ) would spoil our nice API. Wait a second. We can run code on properties, via ES5 accessors! We could do something like this: Object.defineProperty(Element.prototype, &quot;yolo&quot;, { get: function () { return { element: this, foo: function() { console.log(this.element); }, bar: function() { /* ... */ } } }, configurable: true, writeable: false }); someElement.yolo.foo(); // It works! (Logs our actual element) This works, but there is a rather annoying issue here: We are generating this object and redefining our functions every single time this property is called. This is a rather bad idea for performance. Ideally, we want to generate this object once , and then return the generated object. We also dont want every element to have its own completely separate instance of the functions we defined, we want to define these functions on a prototype, and use the wonderful JS inheritance for them, so that our library is also dynamically extensible . Luckily, there is a way to do all this too: var Yolo = function(element) { this.element = element; }; Yolo.prototype = { foo: function() { console.log(this.element); }, bar: function() { /* ... */ } }; Object.defineProperty(Element.prototype, &quot;yolo&quot;, { get: function () { Object.defineProperty(this, &quot;yolo&quot;, { value: new Yolo(this) }); return this.yolo; }, configurable: true, writeable: false }); someElement.yolo.foo(); // It works! (Logs our actual element) // And its dynamically extensible too! Yolo.prototype.baz = function(color) { this.element.style.background = color; }; someElement.yolo.baz(&quot;red&quot;) // Our element gets a red background Note that in the above, the getter is only executed once . After that, it overwrites the yolo property with a static value: An instance of the Yolo object. Since were using Object.defineProperty() we also dont run into the issue of breaking enumeration ( for..in loops), since these properties have enumerable: false by default. There is still the wart that these methods need to use this.element instead of this . We could fix this by wrapping them: for (let method in Yolo.prototype) { Yolo.prototype[method] = function(){ var callback = Yolo.prototype[method]; Yolo.prototype[method] = function () { var ret = callback.apply(this.element, arguments); // Return the element, for chainability! return ret === undefined? this.element : ret; } } } However, now you cant dynamically add methods to Yolo.prototype and have them automatically work like the native Yolo methods in element.yolo , so it kinda hurts extensibility (of course you could still add methods that use this.element and they would work). Thoughts?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>jQuery considered harmful</title>
  <link>https://lea.verou.me/2015/04/jquery-considered-harmful/</link>
  <pubDate>Sun, 17 May 2026 02:25:14 +0200</pubDate>
  <description>Heh, I always wanted to do one of those “X considered harmful” posts*. :D Before I start, let me say that I think jQuery has helped tremendously to move the Web forward . It gave developers power to do things that were previously unthinkable, and pushed the browser manufacturers to implement these things natively (without jQuery we probably wouldn’t have document.querySelectorAll now). And jQuery is still needed for those that cannot depend on the goodies we have today and have to support relics of the past like IE8 or worse. However, as much as I feel for these poor souls, they are the minority. There are tons of developers that don’t need to support old browsers with a tiny market share. And let’s not forget those who aren’t even Web professionals: Students and researchers not only don’t need to support old browsers, but can often get by just supporting a single browser! You would expect that everyone in academia would be having tons of fun using all the modern goodies of the Open Web Platform, right? And yet, I haven’t seen jQuery being so prominent anywhere else as much as it is in academia. Why? Because this is what they know, and they really don’t have the time or interest to follow the news on the Open Web Platform. They don’t know what they need jQuery for, so they just use jQuery anyway. However, being able to do these things natively now is not the only reason I’d rather avoid jQuery. Yes, you probably don’t really need it… I’m certainly not the first one to point out how much of jQuery usage is about things you can do natively, so I won’t spend time repeating what others have written. Just visit the following and dive in: You might not need jQuery You don’t need jQuery! Do you really need jQuery? 10 tips for writing JavaScript without jQuery …and lots more. Just try googling “you don’t need jQuery” and you will find plenty. I will also not spend time talking about file size or how much faster native methods are. These have been talked about before. Today, I want to make a point that is not frequently talked about… …but that’s not even the biggest reason not to use it today To avoid extending the native element prototypes, jQuery uses its own wrapper objects . Extending native objects in the past was a huge no-no, not only due to potential collisions, but also due to memory leaks in old IE. So, what is returned when you run $(&quot;div&quot;) is not a reference to an element, or a NodeList, it’s a jQuery object. This means that a jQuery object has completely different methods available to it than a reference to a DOM element, an array with elements or any type of NodeList. However, these native objects come up all the time in real code — as much as jQuery tries to abstract them away, you always have to deal with them, even if it’s just wrapping them in $(). For example, the context when a callback is called via jQuery’s .bind() method is a reference to an HTML element, not a jQuery collection. Not to mention that often you use code from multiple sources — some of them assume jQuery, some don’t. Therefore, you always end up with code that mixes jQuery objects, native elements and NodeLists . And this is where the hell begins. If the developer has followed a naming convention for which variables contain jQuery objects (prepending the variable names with a dollar sign is the common one I believe) and which contain native elements, this is less of a problem (humans often end up forgetting to follow such conventions, but let’s assume a perfect world here). However, in most cases no such convention is followed, which results in the code being incredibly hard to understand by anyone unfamiliar with it. Every edit entails a lot of trial and error now (“Oh, it’s not a jQuery object, I have to wrap it with $() !” or “Oh, it’s not an element, I have to use [0] to get an element!”). To avoid such confusion, developers making edits often end up wrapping anything in $() defensively, so throughout the code, the same variable will have gone through $() multiple times. For the same reason, it also becomes especially hard to refactor jQuery out of said code. You are essentially locked in . Even if naming conventions have been followed, you can’t just deal only with jQuery objects. You often need to use a native DOM method or call a function from a script that doesn’t depend on jQuery. Soon, conversions to and from jQuery objects are all over the place, cluttering your code. In addition, when you add code to said codebase, you usually end up wrapping every element or nodelist reference with $() as well, because you don’t know what input you’re getting. So, not only you’re locked in, but all future code you write for the same codebase is also locked in . Get any random script with a jQuery dependency that you didn’t write yourself and try to refactor it so that it doesn’t need jQuery. I dare you. You will see that your main issue will not be how to convert the functionality to use native APIs, but understanding what the hell is going on. A pragmatic path to JS nudity Sure, many libraries today require jQuery, and like I recently tweeted , avoiding it entirely can feel like you’re some sort of digital vegan. However, this doesn’t mean you have to use it yourself. Libraries can always be replaced in the future, when good non-jQuery alternatives become available. Also, most libraries are written in such a way that they do not require the $ variable to be aliased to jQuery. Just call jQuery.noConflict() to reclaim the $ variable and be able to assign it to whatever you see fit. For example, I often define these helper functions, inspired from the Command Line API : // Returns first element that matches CSS selector {expr}. // Querying can optionally be restricted to {container}’s descendants function $(expr, container) { return typeof expr === &quot;string&quot;? (container || document).querySelector(expr) : expr || null; } // Returns all elements that match CSS selector {expr} as an array. // Querying can optionally be restricted to {container}’s descendants function $$(expr, container) { return [].slice.call((container || document).querySelectorAll(expr)); } In addition, I think that having to type jQuery instead of $ every time you use it somehow makes you think twice about superfluously using it without really needing to, but I could be wrong :) Also, if you actually like the jQuery API, but want to avoid the bloat, consider using Zepto . * I thought it was brutally obvious that the title was tongue-in-cheek, but hey, it’s the Internet, and nothing is obvious. So there: The title is tongue-in-cheek and I’m very well aware of Eric’s classic essay against such titles .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Awesomplete: 2KB autocomplete with zero dependencies</title>
  <link>https://lea.verou.me/2015/02/awesomplete-2kb-autocomplete-with-zero-dependencies/</link>
  <pubDate>Sun, 17 May 2026 02:25:14 +0200</pubDate>
  <description>Sorry for the lack of posts for the past 7 (!) months, I’ve been super busy working on my book , which up to a certain point, I couldn’t even imagine finishing, but I’m finally there! I’ve basically tried to cram all the CSS wisdom I’ve accumulated over the years in it :P (which is partly why it took so long, I kept remembering more things that just *had* to be in it. Its page count on the O’Reilly website had to be updated 3 times, from 250 to 300 to 350 and it looks like the final is gonna be closer to 400 pages) and it’s gonna be super awesome ( preorder here! ) :D . I have been posting a few CSS tricks now and then on my twitter account , but haven’t found any time to write a proper blog post. Anyhow, despite being super busy with MIT (which btw is amazing, challenging in a good way, and full of fantastic people. So glad to be here!) and the book, I recently needed an autocomplete widget for something. Surprisingly, I don’t think I ever had needed to choose one in the past. I’ve worked with apps that had it, but in those cases it was already there. At first, I didn’t fret. Finally, a chance to use the HTML5 , so exciting! However, the more I played with it, the more my excitement was dying a slow death, taking my open web standards dreams and hopes along with it. Not only it’s incredibly inconsistent across browsers (e.g. Chrome matches only from the start, Firefox anywhere!), it’s also not hackable or customizable in any way. Not even if I got my hands dirty and used proprietary CSS, I still couldn’t do anything as simple as changing how the matching happens, styling the dropdown or highlighting the matching text! So, with a heavy heart, I decided to use a script. However, when I looked into it, everything seemed super bloated for my needs and anything with half decent usability required jQuery, which results in even more bloat. So, I did what every crazy person with a severe case of NIH Syndrome would: I wrote one . It was super fun, and I don’t regret it, although now I’m even more pressed for time to meet my real deadlines. I wrote it primarily for myself, so even if nobody else uses it, ho hum, it was more fun than alternative ways to take a break. However, it’s my duty to put it on Github, in case someone else wants it and in case the community wants to take it into its loving, caring hands and pull request the hell out of it. To be honest, I think it’s both pretty and pretty useful and even though it won’t suit complex needs out of the box, it’s pretty hackable/extensible. I even wrote quite a bit of documentation at some point this week when I was too sleepy to work and not sufficiently sleepy to sleep — because apparently that’s what was missing from my life: even more technical writing. I saved the best for last: It’s so lightweight you might end up chasing it around if there’s a lot of wind when you download it. It’s currently a little under 1.5KB minified &amp; gzipped (the website says 2KB because it will probably grow with commits and I don’t want to have to remember to update it all the time), with zero dependencies ! :D And it’s even been verified to work in IE9 (sorta), IE10+, Chrome, Firefox, Safari 5+, Mobile Safari ! ’Nuff said. Get it now! PS: If you’re about to leave a comment on how it’s not called “ autocomplete ”, but “ typeahead ”, please go choke on a bucket of cocks instead. :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>jQuery considered harmful</title>
  <link>https://lea.verou.me/2015/04/jquery-considered-harmful/</link>
  <pubDate>Sun, 17 May 2026 02:25:14 +0200</pubDate>
  <description>Heh, I always wanted to do one of those X considered harmful posts*. :D Before I start, let me say that I think jQuery has helped tremendously to move the Web forward . It gave developers power to do things that were previously unthinkable, and pushed the browser manufacturers to implement these things natively (without jQuery we probably wouldnt have document.querySelectorAll now). And jQuery is still needed for those that cannot depend on the goodies we have today and have to support relics of the past like IE8 or worse. However, as much as I feel for these poor souls, they are the minority. There are tons of developers that dont need to support old browsers with a tiny market share. And lets not forget those who arent even Web professionals: Students and researchers not only dont need to support old browsers, but can often get by just supporting a single browser! You would expect that everyone in academia would be having tons of fun using all the modern goodies of the Open Web Platform, right? And yet, I havent seen jQuery being so prominent anywhere else as much as it is in academia. Why? Because this is what they know, and they really dont have the time or interest to follow the news on the Open Web Platform. They dont know what they need jQuery for, so they just use jQuery anyway. However, being able to do these things natively now is not the only reason Id rather avoid jQuery. Yes, you probably dont really need it Im certainly not the first one to point out how much of jQuery usage is about things you can do natively, so I wont spend time repeating what others have written. Just visit the following and dive in: You might not need jQuery You dont need jQuery! Do you really need jQuery? 10 tips for writing JavaScript without jQuery and lots more. Just try googling you dont need jQuery and you will find plenty. I will also not spend time talking about file size or how much faster native methods are. These have been talked about before. Today, I want to make a point that is not frequently talked about but thats not even the biggest reason not to use it today To avoid extending the native element prototypes, jQuery uses its own wrapper objects . Extending native objects in the past was a huge no-no, not only due to potential collisions, but also due to memory leaks in old IE. So, what is returned when you run $(&quot;div&quot;) is not a reference to an element, or a NodeList, its a jQuery object. This means that a jQuery object has completely different methods available to it than a reference to a DOM element, an array with elements or any type of NodeList. However, these native objects come up all the time in real code as much as jQuery tries to abstract them away, you always have to deal with them, even if its just wrapping them in $(). For example, the context when a callback is called via jQuerys .bind() method is a reference to an HTML element, not a jQuery collection. Not to mention that often you use code from multiple sources some of them assume jQuery, some dont. Therefore, you always end up with code that mixes jQuery objects, native elements and NodeLists . And this is where the hell begins. If the developer has followed a naming convention for which variables contain jQuery objects (prepending the variable names with a dollar sign is the common one I believe) and which contain native elements, this is less of a problem (humans often end up forgetting to follow such conventions, but lets assume a perfect world here). However, in most cases no such convention is followed, which results in the code being incredibly hard to understand by anyone unfamiliar with it. Every edit entails a lot of trial and error now (Oh, its not a jQuery object, I have to wrap it with $() ! or Oh, its not an element, I have to use [0] to get an element!). To avoid such confusion, developers making edits often end up wrapping anything in $() defensively, so throughout the code, the same variable will have gone through $() multiple times. For the same reason, it also becomes especially hard to refactor jQuery out of said code. You are essentially locked in . Even if naming conventions have been followed, you cant just deal only with jQuery objects. You often need to use a native DOM method or call a function from a script that doesnt depend on jQuery. Soon, conversions to and from jQuery objects are all over the place, cluttering your code. In addition, when you add code to said codebase, you usually end up wrapping every element or nodelist reference with $() as well, because you dont know what input youre getting. So, not only youre locked in, but all future code you write for the same codebase is also locked in . Get any random script with a jQuery dependency that you didnt write yourself and try to refactor it so that it doesnt need jQuery. I dare you. You will see that your main issue will not be how to convert the functionality to use native APIs, but understanding what the hell is going on. A pragmatic path to JS nudity Sure, many libraries today require jQuery, and like I recently tweeted , avoiding it entirely can feel like youre some sort of digital vegan. However, this doesnt mean you have to use it yourself. Libraries can always be replaced in the future, when good non-jQuery alternatives become available. Also, most libraries are written in such a way that they do not require the $ variable to be aliased to jQuery. Just call jQuery.noConflict() to reclaim the $ variable and be able to assign it to whatever you see fit. For example, I often define these helper functions, inspired from the Command Line API : // Returns first element that matches CSS selector {expr}. // Querying can optionally be restricted to {container}s descendants function $(expr, container) { return typeof expr === &quot;string&quot;? (container || document).querySelector(expr) : expr || null; } // Returns all elements that match CSS selector {expr} as an array. // Querying can optionally be restricted to {container}s descendants function $$(expr, container) { return [].slice.call((container || document).querySelectorAll(expr)); } In addition, I think that having to type jQuery instead of $ every time you use it somehow makes you think twice about superfluously using it without really needing to, but I could be wrong :) Also, if you actually like the jQuery API, but want to avoid the bloat, consider using Zepto . * I thought it was brutally obvious that the title was tongue-in-cheek, but hey, its the Internet, and nothing is obvious. So there: The title is tongue-in-cheek and Im very well aware of Erics classic essay against such titles .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Awesomplete: 2KB autocomplete with zero dependencies</title>
  <link>https://lea.verou.me/2015/02/awesomplete-2kb-autocomplete-with-zero-dependencies/</link>
  <pubDate>Sun, 17 May 2026 02:25:14 +0200</pubDate>
  <description>Sorry for the lack of posts for the past 7 (!) months, Ive been super busy working on my book , which up to a certain point, I couldnt even imagine finishing, but Im finally there! Ive basically tried to cram all the CSS wisdom Ive accumulated over the years in it :P (which is partly why it took so long, I kept remembering more things that just *had* to be in it. Its page count on the OReilly website had to be updated 3 times, from 250 to 300 to 350 and it looks like the final is gonna be closer to 400 pages) and its gonna be super awesome ( preorder here! ) :D . I have been posting a few CSS tricks now and then on my twitter account , but havent found any time to write a proper blog post. Anyhow, despite being super busy with MIT (which btw is amazing, challenging in a good way, and full of fantastic people. So glad to be here!) and the book, I recently needed an autocomplete widget for something. Surprisingly, I dont think I ever had needed to choose one in the past. Ive worked with apps that had it, but in those cases it was already there. At first, I didnt fret. Finally, a chance to use the HTML5 , so exciting! However, the more I played with it, the more my excitement was dying a slow death, taking my open web standards dreams and hopes along with it. Not only its incredibly inconsistent across browsers (e.g. Chrome matches only from the start, Firefox anywhere!), its also not hackable or customizable in any way. Not even if I got my hands dirty and used proprietary CSS, I still couldnt do anything as simple as changing how the matching happens, styling the dropdown or highlighting the matching text! So, with a heavy heart, I decided to use a script. However, when I looked into it, everything seemed super bloated for my needs and anything with half decent usability required jQuery, which results in even more bloat. So, I did what every crazy person with a severe case of NIH Syndrome would: I wrote one . It was super fun, and I dont regret it, although now Im even more pressed for time to meet my real deadlines. I wrote it primarily for myself, so even if nobody else uses it, ho hum, it was more fun than alternative ways to take a break. However, its my duty to put it on Github, in case someone else wants it and in case the community wants to take it into its loving, caring hands and pull request the hell out of it. To be honest, I think its both pretty and pretty useful and even though it wont suit complex needs out of the box, its pretty hackable/extensible. I even wrote quite a bit of documentation at some point this week when I was too sleepy to work and not sufficiently sleepy to sleep because apparently thats what was missing from my life: even more technical writing. I saved the best for last: Its so lightweight you might end up chasing it around if theres a lot of wind when you download it. Its currently a little under 1.5KB minified &amp; gzipped (the website says 2KB because it will probably grow with commits and I dont want to have to remember to update it all the time), with zero dependencies ! :D And its even been verified to work in IE9 (sorta), IE10+, Chrome, Firefox, Safari 5+, Mobile Safari ! Nuff said. Get it now! PS: If youre about to leave a comment on how its not called autocomplete , but typeahead , please go choke on a bucket of cocks instead. :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>An easy notation for grayscale colors</title>
  <link>https://lea.verou.me/2014/07/an-easy-notation-for-grayscale-colors/</link>
  <pubDate>Sun, 17 May 2026 02:25:13 +0200</pubDate>
  <description>These days, there is a lengthy discussion in the CSS WG about how to name a function that produces shades of gray (from white to black) with varying degrees of transparency, and we need your feedback about which name is easier to use. The current proposals are: 1. gray(lightness [, alpha]) In this proposal gray(0%) is black, gray(50%) is gray and gray(100%) is white. It also accepts numbers from 0-255 which correspond to rgb(x,x,x) values, so that gray(255) is white and gray(0) is black. It also accepts an optional second argument for alpha transparency , so that gray(0, .5) would be equivalent to rgba(0,0,0,.5). This is the naming of the function in the current CSS Color Level 4 draft . 2. white(lightness [, alpha]) Its arguments work in the same way as gray(), but it’s consistent with the expectation that function names that accept percentages give the “full effect” at 100%. gray(100%) sounds like a shade of gray, when it’s actually white. white(100%) is white, which might be more consistent with author expectations. Of course, this also accepts alpha transparency, like all the proposals listed here. 3. black(lightness [, alpha]) black() would work in the opposite way: black(0%) would be white, black(100%) would be black and black(50%,.5) would be semi-transparent gray. The idea is that people are familiar thinking that way from grayscale printing. 4. rgb() with one argument and rgba() with two arguments rgb(x) would be a shorthand to rgb(x, x, x) and rgba(x, y) would be a shorthand to rgba(x, x, x, y). So, rgb(0) would be black and rgb(100%) or rgb(255) would be white. The benefit is that authors are already accustomed to using rgb() for colors, and this would just be a shortcut. However, note how you will need to change the function name to get a semi-transparent version of the color. Also, if in the future one needs to change the color to not be a shade of gray, a function name change is not needed. I’ve written some SCSS to emulate these functions so you can play with them in your stylesheets and figure out which one is more intuitive. Unfortunately rgb(x)/rgba(x,a) cannot be polyfilled in that way, as that would overwrite the native rgb()/rgba() functions. Which might be an argument against them, as being able to polyfill through a preprocessor is quite a benefit for a new color format IMO. You can vote here , but that’s mainly for easy vote counting. It’s strongly encouraged that you also leave a comment justifying your opinion, either here or in the list. Vote now! Also tl;dr If you can’t be bothered to read the post and understand the proposals well, please, refrain from voting.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>An easy notation for grayscale colors</title>
  <link>https://lea.verou.me/2014/07/an-easy-notation-for-grayscale-colors/</link>
  <pubDate>Sun, 17 May 2026 02:25:13 +0200</pubDate>
  <description>These days, there is a lengthy discussion in the CSS WG about how to name a function that produces shades of gray (from white to black) with varying degrees of transparency, and we need your feedback about which name is easier to use. The current proposals are: 1. gray(lightness [, alpha]) In this proposal gray(0%) is black, gray(50%) is gray and gray(100%) is white. It also accepts numbers from 0-255 which correspond to rgb(x,x,x) values, so that gray(255) is white and gray(0) is black. It also accepts an optional second argument for alpha transparency , so that gray(0, .5) would be equivalent to rgba(0,0,0,.5). This is the naming of the function in the current CSS Color Level 4 draft . 2. white(lightness [, alpha]) Its arguments work in the same way as gray(), but its consistent with the expectation that function names that accept percentages give the full effect at 100%. gray(100%) sounds like a shade of gray, when its actually white. white(100%) is white, which might be more consistent with author expectations. Of course, this also accepts alpha transparency, like all the proposals listed here. 3. black(lightness [, alpha]) black() would work in the opposite way: black(0%) would be white, black(100%) would be black and black(50%,.5) would be semi-transparent gray. The idea is that people are familiar thinking that way from grayscale printing. 4. rgb() with one argument and rgba() with two arguments rgb(x) would be a shorthand to rgb(x, x, x) and rgba(x, y) would be a shorthand to rgba(x, x, x, y). So, rgb(0) would be black and rgb(100%) or rgb(255) would be white. The benefit is that authors are already accustomed to using rgb() for colors, and this would just be a shortcut. However, note how you will need to change the function name to get a semi-transparent version of the color. Also, if in the future one needs to change the color to not be a shade of gray, a function name change is not needed. Ive written some SCSS to emulate these functions so you can play with them in your stylesheets and figure out which one is more intuitive. Unfortunately rgb(x)/rgba(x,a) cannot be polyfilled in that way, as that would overwrite the native rgb()/rgba() functions. Which might be an argument against them, as being able to polyfill through a preprocessor is quite a benefit for a new color format IMO. You can vote here , but thats mainly for easy vote counting. Its strongly encouraged that you also leave a comment justifying your opinion, either here or in the list. Vote now! Also tl;dr If you cant be bothered to read the post and understand the proposals well, please, refrain from voting.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Image comparison slider with pure CSS</title>
  <link>https://lea.verou.me/2014/07/image-comparison-slider-with-pure-css/</link>
  <pubDate>Sun, 17 May 2026 02:25:12 +0200</pubDate>
  <description>As a few of you know, I have been spending a good part of this year writing a book for O’Reilly called “CSS Secrets” ( preorder here! ). I wanted to include a “secret” about the various uses of the resize property, as it’s one of my favorite underdogs, since it rarely gets any love. However, just mentioning the typical use case of improving the UX of text fields didn’t feel like enough of a secret at all. The whole purpose of the book is to get authors to think outside the box about what’s possible with CSS, not to recite widely known applications of CSS features. So I started brainstorming: What else could we do with it? Then I remembered Dudley’s awesome Before/After image slider from a while ago . While I loved the result, the markup isn’t great and it requires scripting. Also, both images are CSS backgrounds, so for a screen reader, there are no images there. And then it dawned on me: What if I overlaid a on an image and made it horizontally resizable through the resize property? I tried it, and as you can see below, it worked! The good parts: More semantic markup (2 images &amp; 2 divs). If object-fit was widely supported , it could even be just one div and two images. No JS Less CSS code Of course, few things come with no drawbacks. In this case: One big drawback is keyboard accessibility. Dudley’s demo uses a range input, so it’s keyboard accessible by design. You can only drag from the bottom right corners. In Dudley’s demo, you can click at any point in the slider. And yes, I did try to style ::webkit-resizer and increase its size so that at least it has smoother UX in Webkit. However, no matter what I tried, nothing seemed to work. Also, none of the two seems to work on mobile. It might not be perfect, but I thought it’s a pretty cool demo of what’s possible with the resize property, as everybody seems to only use it in textareas and the like, but its potential is much bigger. And now if you’ll excuse me, I have a chapter to write ;) Edit: It looks like somebody figured out a similar solution a few months ago, which does manage to make the resizer full height, albeit with less semantic HTML and more flimsy CSS. The main idea is that you use a separate element for the resizing (in this case a textarea) with a height of 15px = the height of the resizer. Then, they apply a scaleY() transform to stretch that 15px to the height of the image. Pretty cool! Unfortunately, it requires hardcoding the image size in the CSS.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Dynamically generated SVG through SASS + A 3D animated RGB cube!</title>
  <link>https://lea.verou.me/2014/04/dynamically-generated-svg-through-sass-a-3d-animated-rgb-cube/</link>
  <pubDate>Sun, 17 May 2026 02:25:12 +0200</pubDate>
  <description>Today, I was giving the opening keynote at Codemania in Auckland, New Zealand. It was a talk about color from a math/dev perspective. It went quite well, despite my complete lack of sleep. I mean that quite literally: I hadn’t slept all night. No, it wasn’t the jetlag or the nervousness that kept me up. It was my late minute decision to replace the static, low-res image of an RGB cube I was using until then with a 3D cube generated with CSS and animated with CSS animations . Next thing I knew, it was light outside and I had to start getting ready. However, I don’t regret literally losing sleep to make a slide that is only shown for 20 seconds at most. Not only it was super fun to develop, but also yielded a few things that I thought were interesting enough to blog about. The most challenging part wasn’t actually the 3D cube. This has been done tons of times before, it was probably the most common demo for CSS 3D transforms a couple of years ago. The only part of this that could be of interest is that mine only used 2 elements for the cube. This is a dabblet of the cube, without any RGB gradients on it: The challenging part was creating the gradients for the 6 sides. These are not plain gradients, as you can see below: These are basically two linear gradients from left to right, with the topmost one being masked with a gradient from top to bottom. You can use CSS Masking to achieve this (for Chrome/Safari) and SVG Masks for Firefox, but this masks the whole element, which would hide the pseudo-elements needed for the sides. What I needed was masks applied to backgrounds only, not the whole element. It seemed obvious that the best idea would be to use SVG background images. For example, here is the SVG background needed for the top left one : However, I didn’t want to have 6 separate SVG files, especially with this kind of repetition (cross-linking to reuse gradients and masks across different files is still fairly buggy in certain browsers). I wanted to be able to edit this straight from my CSS. And then it hit me: I was using SASS already. I could code SASS functions that generate SVG data URIs! Here’s the set of SVG generating SASS functions I ended up writing: @function inline-svg($content, $width: $side, $height: $side) { @return url(&#39;data:image/svg+xml,#{$content}&#39;); } @function svg-rect($fill, $width: &#39;100%&#39;, $height: $width, $x: &#39;0&#39;, $y: &#39;0&#39;) { @return unquote(&#39;&#39;); } @function svg-gradient($id, $color1, $color2, $x1: 0, $x2: 0, $y1: 0, $y2: 1) { @return unquote(&#39; &#39;); } @function svg-mask($id, $content) { @return unquote(&#39;#{$content}&#39;); } And then I was able to generate each RGB plane with another function that made use of them: @function rgb-plane($c1, $c2, $c3, $c4) { @return inline-svg( svg-gradient(&#39;top&#39;, $c1, $c2) + svg-gradient(&#39;bottom&#39;, $c3, $c4) + svg-gradient(&#39;gradient&#39;, white, black, 0, 1, 0, 0) + svg-mask(&#39;gradient-mask&#39;, svg-rect(&#39;url(%23gradient)&#39;)) + svg-rect(&#39;url(%23bottom)&#39;) + svg-rect(&#39;url(%23top)&quot; mask=&quot;url(%23gradient-mask)&#39;) ); } /* ... */ .cube { background: rgb-plane(blue, black, aqua, lime); &amp;::before { background: rgb-plane(blue, fuchsia, aqua, white); } &amp;::after { background: rgb-plane(fuchsia, red, blue, black); } } .cube .sides { background: rgb-plane(yellow, lime, red, black); &amp;::before { background: rgb-plane(yellow, white, red, fuchsia); } &amp;::after { background: rgb-plane(white, aqua, yellow, lime); } } However, the same functions can be used for all sorts of SVG backgrounds and it’s very easy to add a new one. E.g. to make polygons: @function svg-polygon($fill, $points) { @return unquote(&#39;&#39;); } @function svg-circle($fill, $r: &#39;50%&#39;, $cx: &#39;50%&#39;, $cy: &#39;50%&#39;) { @return unquote(&#39;&#39;); } You can see the whole SCSS file here and its CSS output here . Warning: Keep in mind that IE9 and some older versions of other browsers have issues with unencoded SVG data URIs. Also, you still need to escape hashes ( %23 instead of # ), otherwise Firefox fails.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Image comparison slider with pure CSS</title>
  <link>https://lea.verou.me/2014/07/image-comparison-slider-with-pure-css/</link>
  <pubDate>Sun, 17 May 2026 02:25:12 +0200</pubDate>
  <description>As a few of you know, I have been spending a good part of this year writing a book for OReilly called CSS Secrets ( preorder here! ). I wanted to include a secret about the various uses of the resize property, as its one of my favorite underdogs, since it rarely gets any love. However, just mentioning the typical use case of improving the UX of text fields didnt feel like enough of a secret at all. The whole purpose of the book is to get authors to think outside the box about whats possible with CSS, not to recite widely known applications of CSS features. So I started brainstorming: What else could we do with it? Then I remembered Dudleys awesome Before/After image slider from a while ago . While I loved the result, the markup isnt great and it requires scripting. Also, both images are CSS backgrounds, so for a screen reader, there are no images there. And then it dawned on me: What if I overlaid a on an image and made it horizontally resizable through the resize property? I tried it, and as you can see below, it worked! The good parts: More semantic markup (2 images &amp; 2 divs). If object-fit was widely supported , it could even be just one div and two images. No JS Less CSS code Of course, few things come with no drawbacks. In this case: One big drawback is keyboard accessibility. Dudleys demo uses a range input, so its keyboard accessible by design. You can only drag from the bottom right corners. In Dudleys demo, you can click at any point in the slider. And yes, I did try to style ::webkit-resizer and increase its size so that at least it has smoother UX in Webkit. However, no matter what I tried, nothing seemed to work. Also, none of the two seems to work on mobile. It might not be perfect, but I thought its a pretty cool demo of whats possible with the resize property, as everybody seems to only use it in textareas and the like, but its potential is much bigger. And now if youll excuse me, I have a chapter to write ;) Edit: It looks like somebody figured out a similar solution a few months ago, which does manage to make the resizer full height, albeit with less semantic HTML and more flimsy CSS. The main idea is that you use a separate element for the resizing (in this case a textarea) with a height of 15px = the height of the resizer. Then, they apply a scaleY() transform to stretch that 15px to the height of the image. Pretty cool! Unfortunately, it requires hardcoding the image size in the CSS.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Dynamically generated SVG through SASS + A 3D animated RGB cube!</title>
  <link>https://lea.verou.me/2014/04/dynamically-generated-svg-through-sass-a-3d-animated-rgb-cube/</link>
  <pubDate>Sun, 17 May 2026 02:25:12 +0200</pubDate>
  <description>Today, I was giving the opening keynote at Codemania in Auckland, New Zealand. It was a talk about color from a math/dev perspective. It went quite well, despite my complete lack of sleep. I mean that quite literally: I hadnt slept all night. No, it wasnt the jetlag or the nervousness that kept me up. It was my late minute decision to replace the static, low-res image of an RGB cube I was using until then with a 3D cube generated with CSS and animated with CSS animations . Next thing I knew, it was light outside and I had to start getting ready. However, I dont regret literally losing sleep to make a slide that is only shown for 20 seconds at most. Not only it was super fun to develop, but also yielded a few things that I thought were interesting enough to blog about. The most challenging part wasnt actually the 3D cube. This has been done tons of times before, it was probably the most common demo for CSS 3D transforms a couple of years ago. The only part of this that could be of interest is that mine only used 2 elements for the cube. This is a dabblet of the cube, without any RGB gradients on it: The challenging part was creating the gradients for the 6 sides. These are not plain gradients, as you can see below: These are basically two linear gradients from left to right, with the topmost one being masked with a gradient from top to bottom. You can use CSS Masking to achieve this (for Chrome/Safari) and SVG Masks for Firefox, but this masks the whole element, which would hide the pseudo-elements needed for the sides. What I needed was masks applied to backgrounds only, not the whole element. It seemed obvious that the best idea would be to use SVG background images. For example, here is the SVG background needed for the top left one : However, I didnt want to have 6 separate SVG files, especially with this kind of repetition (cross-linking to reuse gradients and masks across different files is still fairly buggy in certain browsers). I wanted to be able to edit this straight from my CSS. And then it hit me: I was using SASS already. I could code SASS functions that generate SVG data URIs! Heres the set of SVG generating SASS functions I ended up writing: @function inline-svg($content, $width: $side, $height: $side) { @return url(&#39;data:image/svg+xml,#{$content}&#39;); } @function svg-rect($fill, $width: &#39;100%&#39;, $height: $width, $x: &#39;0&#39;, $y: &#39;0&#39;) { @return unquote(&#39;&#39;); } @function svg-gradient($id, $color1, $color2, $x1: 0, $x2: 0, $y1: 0, $y2: 1) { @return unquote(&#39; &#39;); } @function svg-mask($id, $content) { @return unquote(&#39;#{$content}&#39;); } And then I was able to generate each RGB plane with another function that made use of them: @function rgb-plane($c1, $c2, $c3, $c4) { @return inline-svg( svg-gradient(&#39;top&#39;, $c1, $c2) + svg-gradient(&#39;bottom&#39;, $c3, $c4) + svg-gradient(&#39;gradient&#39;, white, black, 0, 1, 0, 0) + svg-mask(&#39;gradient-mask&#39;, svg-rect(&#39;url(%23gradient)&#39;)) + svg-rect(&#39;url(%23bottom)&#39;) + svg-rect(&#39;url(%23top)&quot; mask=&quot;url(%23gradient-mask)&#39;) ); } /* ... */ .cube { background: rgb-plane(blue, black, aqua, lime); &amp;::before { background: rgb-plane(blue, fuchsia, aqua, white); } &amp;::after { background: rgb-plane(fuchsia, red, blue, black); } } .cube .sides { background: rgb-plane(yellow, lime, red, black); &amp;::before { background: rgb-plane(yellow, white, red, fuchsia); } &amp;::after { background: rgb-plane(white, aqua, yellow, lime); } } However, the same functions can be used for all sorts of SVG backgrounds and its very easy to add a new one. E.g. to make polygons: @function svg-polygon($fill, $points) { @return unquote(&#39;&#39;); } @function svg-circle($fill, $r: &#39;50%&#39;, $cx: &#39;50%&#39;, $cy: &#39;50%&#39;) { @return unquote(&#39;&#39;); } You can see the whole SCSS file here and its CSS output here . Warning: Keep in mind that IE9 and some older versions of other browsers have issues with unencoded SVG data URIs. Also, you still need to escape hashes ( %23 instead of # ), otherwise Firefox fails.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>I’m going to MIT!!</title>
  <link>https://lea.verou.me/2014/02/im-going-to-mit/</link>
  <pubDate>Sun, 17 May 2026 02:25:11 +0200</pubDate>
  <description>Last year, I did something crazy, that I’ve been wanting to do since I was little: I applied to MIT’s PhD program in Electrical Engineering and Computer Science. It was not only crazy because I have been working for several years already, but also because I only applied to MIT, as I decided I did not want to go to any other university, both for pragmatic and emotional reasons. As any prospective grad student will tell you, applying to only one top university is recipe for failure. I didn’t tell many people, but everyone who knew thought I’d get in — except me. You see, I wasn’t a typical candidate. Sure, I have done lots of things I’m proud of, but I didn’t have an amazing GPA or publications in prestigious academic conferences. It felt like a very long shot, so you can imagine my excitement when I received the letters of acceptance, about a week ago. I will remember that moment forever. I was watching Breaking Bad, feeling miserable over a breakup that happened only a few hours earlier. About a minute into the episode (s05e09), I saw an email notification titled “Your application to MIT EECS”. My first thought was that there was some problem with my application. And then I read the first few lines: Dear Michailia Verou: If you have not already heard from them, you will shortly receive a letter from the EECS department at MIT, informing you that you have been admitted to the graduate program in Computer Science at MIT next fall. Congratulations!! WHAAAA? Was it a scam? But then, how did they have all my details? Holy mother of the Flying Spaghetti Monster, I got in!!! Soon thereafter, a letter from CSAIL followed (where I said I wanted to work, specifically in the UID ), and then even more letters. I started calling everyone who knew I applied to share the news, though it proved quite hard to form sentences instead of uncontrollably screaming in joy. I was (and am!) so excited about the future, that it completely overshadows any other life problems (at least for the time being). Of course, my happiness is mixed with sheer terror. I keep worrying that I will be the dumbest person in the room, or that I don’t remember as much from my undergrad studies as the others will. I’m even terrified of meeting my future advisor(s) in case getting to know me better makes them wonder why I was accepted. But I try to remind myself about impostor syndrome , and from what I’ve read in forums &amp; blogs, it seems that I’m not alone in having such fears. I held off blogging about it until I felt I was able to write something coherent, but I can’t wait to share my excitement any longer. To the future! To real life plot twists! To MIT! Boy, I’m thrilled. :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Im going to MIT!!</title>
  <link>https://lea.verou.me/2014/02/im-going-to-mit/</link>
  <pubDate>Sun, 17 May 2026 02:25:11 +0200</pubDate>
  <description>Last year, I did something crazy, that Ive been wanting to do since I was little: I applied to MITs PhD program in Electrical Engineering and Computer Science. It was not only crazy because I have been working for several years already, but also because I only applied to MIT, as I decided I did not want to go to any other university, both for pragmatic and emotional reasons. As any prospective grad student will tell you, applying to only one top university is recipe for failure. I didnt tell many people, but everyone who knew thought Id get in except me. You see, I wasnt a typical candidate. Sure, I have done lots of things Im proud of, but I didnt have an amazing GPA or publications in prestigious academic conferences. It felt like a very long shot, so you can imagine my excitement when I received the letters of acceptance, about a week ago. I will remember that moment forever. I was watching Breaking Bad, feeling miserable over a breakup that happened only a few hours earlier. About a minute into the episode (s05e09), I saw an email notification titled Your application to MIT EECS. My first thought was that there was some problem with my application. And then I read the first few lines: Dear Michailia Verou: If you have not already heard from them, you will shortly receive a letter from the EECS department at MIT, informing you that you have been admitted to the graduate program in Computer Science at MIT next fall. Congratulations!! WHAAAA? Was it a scam? But then, how did they have all my details? Holy mother of the Flying Spaghetti Monster, I got in!!! Soon thereafter, a letter from CSAIL followed (where I said I wanted to work, specifically in the UID ), and then even more letters. I started calling everyone who knew I applied to share the news, though it proved quite hard to form sentences instead of uncontrollably screaming in joy. I was (and am!) so excited about the future, that it completely overshadows any other life problems (at least for the time being). Of course, my happiness is mixed with sheer terror. I keep worrying that I will be the dumbest person in the room, or that I dont remember as much from my undergrad studies as the others will. Im even terrified of meeting my future advisor(s) in case getting to know me better makes them wonder why I was accepted. But I try to remind myself about impostor syndrome , and from what Ive read in forums &amp; blogs, it seems that Im not alone in having such fears. I held off blogging about it until I felt I was able to write something coherent, but I cant wait to share my excitement any longer. To the future! To real life plot twists! To MIT! Boy, Im thrilled. :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Whathecolor: A color game for web developers!</title>
  <link>https://lea.verou.me/2014/02/whathecolor-a-color-game-for-web-developers/</link>
  <pubDate>Sun, 17 May 2026 02:25:10 +0200</pubDate>
  <description>I’ve been interested in digital color for a long time, and this year I decided to risk giving a technical talk about color some of the conferences I’m speaking at. “Why is that risky?” you might ask. Well, it might end up being really interesting, or it may end up alienating both designers because it’s too technical and developers because it’s about a “designery” topic. In preparation for this talk, I decided to make a simple game to see how well I and other web developers understand color, and especially CSS notations of color. Meet Whathecolor ! The idea is simple: You are presented with a color and you try to type in a CSS color that matches it. It could be anything, from hsl() or rgb() to even named colors (although that would be stupid). It would be interesting to see what averages people get by trying hsl() vs rgb() and whether the former is as easier for web developers as we think. Feel free to post your results here or on twitter! Perhaps in the future, something like this could be used by the CSS WG to test the usability of color notations we’re thinking of adding to CSS instead of speculating about it. Disclaimer: This is a quick hack. Please don’t complain that it doesn’t look great on your phone and stuff like that. Also, yes, if you want to cheat, it’s super easy, but I have no idea why somebody would cheat on something like this. Play Color proximity A challenging part in developing this was calculating the proximity of two colors to show the user how close they are getting. My first thought was to use the Euclidean distance of the two colors in the RGB cube and divide it by the maximum distance the color could have from any other RGB color. However, this proved out to be inaccurate in many cases, probably due to the lack of perceptual uniformity in RGB. As an example, try #f0f and #ff80ff . Although they are quite similar visually, the reported proximity was around 66% (1 - 128/382). So I researched existing algorithms to get the proximity of two colors. Like most things color-related, it looks like Color Difference is not quite as simple as I thought, and is considered a topic of interest in Color Science. However, converting to L*a*b* and using the CIE94 and CIEDE2000 formulas seemed a bit of an overkill for this and I wasn’t terribly impressed with the CIE76 formula after trying the results out online for some sample pairs (e.g. it gives ~60% for the aforementioned pair, which is even lower than what I got with my naïve RGB method!). So I experimented a bit and ended up using an average of my original idea and a sum of the HSL differences (divided by the max differences), which seems to work relatively ok. There are still cases where it’s off, but ho hum. After all, the proximity is mainly useful when you get close enough to the color (&gt;90%), as until then you tend to play it by eye. Any improvements on the algorithm used are welcome. Or if enough people think it’s not working very well, I’ll bite the bullet and end up using DeltaE. Other notes You do not need a proximity of 100% to win, since rounding errors might prevent you from matching the exact color if you’re using HSL. Also, because matching the exact same color isn’t really important, as long as you get close enough that any difference is imperceptible. I wrote a Color “class” for this, which you can find in color.js . Like most of my open source stuff, it’s MIT licensed. Maybe it could be useful in some other color-related project, who knows. My original idea was to have “levels”, where the color would get increasingly more difficult to get. For example, in the first level, you’d only have to guess simple colors whose RGB coordinates were either 0, 128 or 255. So, my Color.random() method accepts an entropy parameter, for that level. However, when I tested the game with truly random colors (any integer from 0 to 255), it turned out it wasn’t really that hard (it took me about a minute to guess each color), so I ditched the idea of levels early on. The code is still there though. An idea about making it harder in the future would be to introduce semi-transparent (RGBA/HSLA) colors. That would be fun :evil_grin: PS: The times in this screenshot aren’t real, I wanted to take one quickly, so I used the dev tools.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Whathecolor: A color game for web developers!</title>
  <link>https://lea.verou.me/2014/02/whathecolor-a-color-game-for-web-developers/</link>
  <pubDate>Sun, 17 May 2026 02:25:10 +0200</pubDate>
  <description>Ive been interested in digital color for a long time, and this year I decided to risk giving a technical talk about color some of the conferences Im speaking at. Why is that risky? you might ask. Well, it might end up being really interesting, or it may end up alienating both designers because its too technical and developers because its about a designery topic. In preparation for this talk, I decided to make a simple game to see how well I and other web developers understand color, and especially CSS notations of color. Meet Whathecolor ! The idea is simple: You are presented with a color and you try to type in a CSS color that matches it. It could be anything, from hsl() or rgb() to even named colors (although that would be stupid). It would be interesting to see what averages people get by trying hsl() vs rgb() and whether the former is as easier for web developers as we think. Feel free to post your results here or on twitter! Perhaps in the future, something like this could be used by the CSS WG to test the usability of color notations were thinking of adding to CSS instead of speculating about it. Disclaimer: This is a quick hack. Please dont complain that it doesnt look great on your phone and stuff like that. Also, yes, if you want to cheat, its super easy, but I have no idea why somebody would cheat on something like this. Play Color proximity A challenging part in developing this was calculating the proximity of two colors to show the user how close they are getting. My first thought was to use the Euclidean distance of the two colors in the RGB cube and divide it by the maximum distance the color could have from any other RGB color. However, this proved out to be inaccurate in many cases, probably due to the lack of perceptual uniformity in RGB. As an example, try #f0f and #ff80ff . Although they are quite similar visually, the reported proximity was around 66% (1 - 128/382). So I researched existing algorithms to get the proximity of two colors. Like most things color-related, it looks like Color Difference is not quite as simple as I thought, and is considered a topic of interest in Color Science. However, converting to L*a*b* and using the CIE94 and CIEDE2000 formulas seemed a bit of an overkill for this and I wasnt terribly impressed with the CIE76 formula after trying the results out online for some sample pairs (e.g. it gives ~60% for the aforementioned pair, which is even lower than what I got with my naïve RGB method!). So I experimented a bit and ended up using an average of my original idea and a sum of the HSL differences (divided by the max differences), which seems to work relatively ok. There are still cases where its off, but ho hum. After all, the proximity is mainly useful when you get close enough to the color (&gt;90%), as until then you tend to play it by eye. Any improvements on the algorithm used are welcome. Or if enough people think its not working very well, Ill bite the bullet and end up using DeltaE. Other notes You do not need a proximity of 100% to win, since rounding errors might prevent you from matching the exact color if youre using HSL. Also, because matching the exact same color isnt really important, as long as you get close enough that any difference is imperceptible. I wrote a Color class for this, which you can find in color.js . Like most of my open source stuff, its MIT licensed. Maybe it could be useful in some other color-related project, who knows. My original idea was to have levels, where the color would get increasingly more difficult to get. For example, in the first level, youd only have to guess simple colors whose RGB coordinates were either 0, 128 or 255. So, my Color.random() method accepts an entropy parameter, for that level. However, when I tested the game with truly random colors (any integer from 0 to 255), it turned out it wasnt really that hard (it took me about a minute to guess each color), so I ditched the idea of levels early on. The code is still there though. An idea about making it harder in the future would be to introduce semi-transparent (RGBA/HSLA) colors. That would be fun :evil_grin: PS: The times in this screenshot arent real, I wanted to take one quickly, so I used the dev tools.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Vote for me in the net awards 2014!</title>
  <link>https://lea.verou.me/2014/01/vote-for-me-in-the-net-awards-2014/</link>
  <pubDate>Sun, 17 May 2026 02:25:09 +0200</pubDate>
  <description>I was excited and surprised to find out I’ve been in the 10 finalists for two (2!) categories in the net awards this year: Outstanding contribution Conference Talk of the Year (for The humble border-radius) You can vote for me by clicking the above links and have my eternal gratitude :-) Furthermore, the CERN line mode browser project, of which I was a part of , has been nominated in “Best Collaborative Project” ! I’ve given “The humble border-radius” more than once, so you can pick one to watch below (in the net awards page they’ve linked to the FOWD London one): CSSDay CSSConf FOWD London Front-Trends Abridged version (only 15 minutes!) from this year’s O’Reilly Fluent I would recommend the CSSDay one, as it changes every time and that one is newer. Thanks again to anyone who nominated me and everyone who votes for me. You’re all wonderful.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Vote for me in the net awards 2014!</title>
  <link>https://lea.verou.me/2014/01/vote-for-me-in-the-net-awards-2014/</link>
  <pubDate>Sun, 17 May 2026 02:25:09 +0200</pubDate>
  <description>I was excited and surprised to find out Ive been in the 10 finalists for two (2!) categories in the net awards this year: Outstanding contribution Conference Talk of the Year (for The humble border-radius) You can vote for me by clicking the above links and have my eternal gratitude :-) Furthermore, the CERN line mode browser project, of which I was a part of , has been nominated in Best Collaborative Project ! Ive given The humble border-radius more than once, so you can pick one to watch below (in the net awards page theyve linked to the FOWD London one): CSSDay CSSConf FOWD London Front-Trends Abridged version (only 15 minutes!) from this years OReilly Fluent I would recommend the CSSDay one, as it changes every time and that one is newer. Thanks again to anyone who nominated me and everyone who votes for me. Youre all wonderful.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Smooth state animations with animation-play-state</title>
  <link>https://lea.verou.me/2014/01/smooth-state-animations-with-animation-play-state/</link>
  <pubDate>Sun, 17 May 2026 02:25:08 +0200</pubDate>
  <description>When a CSS animation is applied from the beginning of the page load, things are easy. You just use the animation property with appropriate parameters, and you’re done. However, what if the animation is applied on a certain state, e.g. :hover, :active, :focus or a JS-triggered class change? A naïve approach would be to try something like this: However, this means that when you hover out of the element, it abruptly snaps to its original state (no rotation). In many cases, it would be a more desirable to have it freeze in the last shown frame, until we hover over it again. To achieve that, we can apply the animation from the beginning, with animation-play-state: paused; and just change it on :hover to animation-play-state: running; . This is what happens then: I figured this out when I was recently helping my good friend Julian with his one page website *. When you hover over the figure, it starts scrolling, but when you hover out of it, it doesn’t snap back to its original position, which would’ve looked awful. *Beware it’s still a bit rough around the edges, e.g. the result has some rendering bugs on Firefox &amp; IE plus some unsupported features messing it up (e.g. baseline-shift in SVG), but those are for another day as I had work to do and this ended up taking longer than the few hours I expected. Beyond the animation, you might want to explore the CSS-only buttons (see what I did there?) or the leather figure frame. Credits to Laura Kalbag for the tweed background &amp; color scheme. I also experimented with SASS on this one and found it much smoother to work with than LESS, so I might stick with it for those cases where I need a preprocessor.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Smooth state animations with animation-play-state</title>
  <link>https://lea.verou.me/2014/01/smooth-state-animations-with-animation-play-state/</link>
  <pubDate>Sun, 17 May 2026 02:25:08 +0200</pubDate>
  <description>When a CSS animation is applied from the beginning of the page load, things are easy. You just use the animation property with appropriate parameters, and youre done. However, what if the animation is applied on a certain state, e.g. :hover, :active, :focus or a JS-triggered class change? A naïve approach would be to try something like this: However, this means that when you hover out of the element, it abruptly snaps to its original state (no rotation). In many cases, it would be a more desirable to have it freeze in the last shown frame, until we hover over it again. To achieve that, we can apply the animation from the beginning, with animation-play-state: paused; and just change it on :hover to animation-play-state: running; . This is what happens then: I figured this out when I was recently helping my good friend Julian with his one page website *. When you hover over the figure, it starts scrolling, but when you hover out of it, it doesnt snap back to its original position, which wouldve looked awful. *Beware its still a bit rough around the edges, e.g. the result has some rendering bugs on Firefox &amp; IE plus some unsupported features messing it up (e.g. baseline-shift in SVG), but those are for another day as I had work to do and this ended up taking longer than the few hours I expected. Beyond the animation, you might want to explore the CSS-only buttons (see what I did there?) or the leather figure frame. Credits to Laura Kalbag for the tweed background &amp; color scheme. I also experimented with SASS on this one and found it much smoother to work with than LESS, so I might stick with it for those cases where I need a preprocessor.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Simple script: Automatic talks list</title>
  <link>https://lea.verou.me/2013/12/simple-script-automatic-talks-list/</link>
  <pubDate>Sun, 17 May 2026 02:25:07 +0200</pubDate>
  <description>I guess this will not be useful to many, but thought I’d open source it anyway, in case someone else finds it useful. I mostly wrote it because after 50+ conferences, I got seriously fed up updating the list by editing the HTML manually. Now I will only have to edit a JSON file manually! :P Admittedly, it’s not my best code (more like a quick hack), but it should be pretty easy to adapt to your needs, even to adapt it to lists of different things instead of talks. Using it for talks is very straight forward: Include the CSS and JS, add a list with a class of “talks” where you want it, done. Hope you enjoy it :) Links: Live demo | Github repo | Script page</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Simple script: Automatic talks list</title>
  <link>https://lea.verou.me/2013/12/simple-script-automatic-talks-list/</link>
  <pubDate>Sun, 17 May 2026 02:25:07 +0200</pubDate>
  <description>I guess this will not be useful to many, but thought Id open source it anyway, in case someone else finds it useful. I mostly wrote it because after 50+ conferences, I got seriously fed up updating the list by editing the HTML manually. Now I will only have to edit a JSON file manually! :P Admittedly, its not my best code (more like a quick hack), but it should be pretty easy to adapt to your needs, even to adapt it to lists of different things instead of talks. Using it for talks is very straight forward: Include the CSS and JS, add a list with a class of talks where you want it, done. Hope you enjoy it :) Links: Live demo | Github repo | Script page</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS is for developers</title>
  <link>https://lea.verou.me/2013/12/css-is-for-developers/</link>
  <pubDate>Sun, 17 May 2026 02:25:06 +0200</pubDate>
  <description>Quite often people assume that because the language I focus on is CSS, I must be a web designer. Don’t get me wrong, I love visual design with a passion. I have studied it a lot over the years and I’ve worked on several design projects for clients. Heck, I even have a dribbble profile ! However, if I had to pick one role, I would definitely consider myself more of a developer than a designer. I discovered coding on my own when I was 12 and so far it has been the most long lasting love of my life. Although I lost my coding virginity to Visual Basic (something I’m still embarrassed about), over the years I’ve coded in Java, C, C++, C#, PHP, JavaScript before I even got to CSS. I’ve actually studied Computer Science at university, graduated 4th in my class and I’m gonna be doing research at MIT towards a PhD, starting fall 2014 . Regarding design, I’m completely self-taught. My personality is more similar to the developers I know than the designers I know. Coding comes naturally, but I have to struggle to get better at design. I’m a better developer than I will ever be a designer. Still, the assumption often is that I can’t possibly be a developer and interested in CSS, when there are all these amazing programming languages to focus my energy on instead. Therefore I must be a designer …right? There are even people who know about my open source projects , and still think that I can’t code in JavaScript or any other programming language (not sure how you can make most of these tools with pure CSS, but since CSS is Turing complete , I guess there must be a way!). If you think I’m an exception, you’re mistaken. Everyone else in the W3C CSS Working Group , the group which defines the future of CSS, fits the profile of a developer much more than that of a designer. In fact, I might be the most designer-y person in it! Even outside the WG, the people I know who are really good at CSS, are either developers or hybrids (designers &amp; developers). This is no coincidence. The skills required to write good CSS code are by and large the same skills required to write good code in general. CSS code also needs to be DRY , maintainable, flexible etc. CSS might have a visual output, but is still code, just like SVG, WebGL/OpenGL or the JavaScript Canvas API. It still requires the same kind of analytical thinking that programming does. Especially these days that most people use preprocessors for their CSS, with variables, math, conditionals and loops, it’s almost starting to look like programming! I find it counter-productive that CSS in most jobs is assigned to designers. Designers should be doing what they do best and love: Design. Sure, they should be aware of the practical limitations of the medium and should be able to read and lightly edit CSS or hack together a prototype to show how their design behaves in different conditions, but it shouldn’t be their job to write CSS for production. The talents required to be a good designer and a good coder are very different and it’s unreasonable to expect both from everyone. Also, when you know you’re gonna have to implement the design you’re working on, it’s tempting to produce designs that can be easily converted to CSS, instead of pushing the boundaries. We don’t usually expect developers to design, even though it’s an added bonus when they have an eye for design as well. It should be the same for designers. And if you’re a designer who writes amazing CSS and is about to tell me off in the comments, hold your horses. I’m not saying you shouldn’t be coding CSS. I’m saying that if you’re good at it, it means you’re both a designer AND a developer. Own it! :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Cleanest CSS spinner, ever</title>
  <link>https://lea.verou.me/2013/11/cleanest-css-spinner-ever/</link>
  <pubDate>Sun, 17 May 2026 02:25:06 +0200</pubDate>
  <description>For some reason, I seem to have a fascination with CSS loaders these days. After recreating the Google loader with clean CSS recently, I set off to recreate the classic spinner with CSS. Yes, I know this has been done zillions of times, but I wanted a clean, maintainable, reusable solution, not just a proof of concept. Something with not tons of CSS and/or HTML elements. I managed to recreate it with only 2 elements. I’m still not completely satisfied, as I was hoping to come up with a solution with just one element, but it’s still much better than all those solutions out there that use tons of elements and code. So, how did I do it? I use the ::before and ::after pseudoelements of the parent and child div to create the 4 first bars I use box-shadow with no blur on all four of the above to create the remaining 4 bars I rotate the whole element with a steps(8) timing function to create the animation As with the Google-style loader, just changing the font-size on this scales the whole element, as everything is sized with ems. Also, there is fallback text, to make it accessible to screen readers. Tested in Chrome, Firefox, Safari, IE10. Should degrade gracefully on IE9 (spinner should look fine, just no animation). Using a preprocessor for variables and calculations should simplify the code even further. Enjoy :) Ideas for further improvement are welcome. Remember that it’s not just the size of the code that matters, but also its simplicity.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS is for developers</title>
  <link>https://lea.verou.me/2013/12/css-is-for-developers/</link>
  <pubDate>Sun, 17 May 2026 02:25:06 +0200</pubDate>
  <description>Quite often people assume that because the language I focus on is CSS, I must be a web designer. Dont get me wrong, I love visual design with a passion. I have studied it a lot over the years and Ive worked on several design projects for clients. Heck, I even have a dribbble profile ! However, if I had to pick one role, I would definitely consider myself more of a developer than a designer. I discovered coding on my own when I was 12 and so far it has been the most long lasting love of my life. Although I lost my coding virginity to Visual Basic (something Im still embarrassed about), over the years Ive coded in Java, C, C++, C#, PHP, JavaScript before I even got to CSS. Ive actually studied Computer Science at university, graduated 4th in my class and Im gonna be doing research at MIT towards a PhD, starting fall 2014 . Regarding design, Im completely self-taught. My personality is more similar to the developers I know than the designers I know. Coding comes naturally, but I have to struggle to get better at design. Im a better developer than I will ever be a designer. Still, the assumption often is that I cant possibly be a developer and interested in CSS, when there are all these amazing programming languages to focus my energy on instead. Therefore I must be a designer right? There are even people who know about my open source projects , and still think that I cant code in JavaScript or any other programming language (not sure how you can make most of these tools with pure CSS, but since CSS is Turing complete , I guess there must be a way!). If you think Im an exception, youre mistaken. Everyone else in the W3C CSS Working Group , the group which defines the future of CSS, fits the profile of a developer much more than that of a designer. In fact, I might be the most designer-y person in it! Even outside the WG, the people I know who are really good at CSS, are either developers or hybrids (designers &amp; developers). This is no coincidence. The skills required to write good CSS code are by and large the same skills required to write good code in general. CSS code also needs to be DRY , maintainable, flexible etc. CSS might have a visual output, but is still code, just like SVG, WebGL/OpenGL or the JavaScript Canvas API. It still requires the same kind of analytical thinking that programming does. Especially these days that most people use preprocessors for their CSS, with variables, math, conditionals and loops, its almost starting to look like programming! I find it counter-productive that CSS in most jobs is assigned to designers. Designers should be doing what they do best and love: Design. Sure, they should be aware of the practical limitations of the medium and should be able to read and lightly edit CSS or hack together a prototype to show how their design behaves in different conditions, but it shouldnt be their job to write CSS for production. The talents required to be a good designer and a good coder are very different and its unreasonable to expect both from everyone. Also, when you know youre gonna have to implement the design youre working on, its tempting to produce designs that can be easily converted to CSS, instead of pushing the boundaries. We dont usually expect developers to design, even though its an added bonus when they have an eye for design as well. It should be the same for designers. And if youre a designer who writes amazing CSS and is about to tell me off in the comments, hold your horses. Im not saying you shouldnt be coding CSS. Im saying that if youre good at it, it means youre both a designer AND a developer. Own it! :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Cleanest CSS spinner, ever</title>
  <link>https://lea.verou.me/2013/11/cleanest-css-spinner-ever/</link>
  <pubDate>Sun, 17 May 2026 02:25:06 +0200</pubDate>
  <description>For some reason, I seem to have a fascination with CSS loaders these days. After recreating the Google loader with clean CSS recently, I set off to recreate the classic spinner with CSS. Yes, I know this has been done zillions of times, but I wanted a clean, maintainable, reusable solution, not just a proof of concept. Something with not tons of CSS and/or HTML elements. I managed to recreate it with only 2 elements. Im still not completely satisfied, as I was hoping to come up with a solution with just one element, but its still much better than all those solutions out there that use tons of elements and code. So, how did I do it? I use the ::before and ::after pseudoelements of the parent and child div to create the 4 first bars I use box-shadow with no blur on all four of the above to create the remaining 4 bars I rotate the whole element with a steps(8) timing function to create the animation As with the Google-style loader, just changing the font-size on this scales the whole element, as everything is sized with ems. Also, there is fallback text, to make it accessible to screen readers. Tested in Chrome, Firefox, Safari, IE10. Should degrade gracefully on IE9 (spinner should look fine, just no animation). Using a preprocessor for variables and calculations should simplify the code even further. Enjoy :) Ideas for further improvement are welcome. Remember that its not just the size of the code that matters, but also its simplicity.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Flexible Google-style loader with CSS</title>
  <link>https://lea.verou.me/2013/11/flexible-google-style-loader-with-css/</link>
  <pubDate>Sun, 17 May 2026 02:25:04 +0200</pubDate>
  <description>So, for a while I had noticed the nice sutble loader Google apps use and I was wondering if it would be easy to make with CSS and CSS animations: Yesterday, I realised that you can get this effect by increasing border size until about the middle of the element, as long as the total width stays the same (by using box-sizing: border-box ): However, as you can see above, after the midpoint, the border is not curved any more, so does not produce the desired effect. However, what if we split the background colour in half, and animated border-left until 50% of the width and then border-right from 50% of the width? That worked, but only gave us 25% of the effect. I could recreate the whole effect by then animating border-top/bottom instead etc, but it’s easier to apply animation-direction: alternate to alternate between showing and hiding the circle and and simultaneously rotate the loader by 90deg each time, by applying animation-timing-function: steps(4) to a rotate animation that runs over 4x the duration of the border animation. This is the finished result: The dimensions are all set in ems so that you can change the size in one place: Just change the font-size and the loader scales perfectly. It’s also accessible to screen reader users, as there is still text there. And yes, it’s not super useful as-is, there are tons of spinners on the Web that you can use instead. However, I decided to post it (instead of just tweeting it) as I thought the techniques involved in making it might be interesting for some of you :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Flexible Google-style loader with CSS</title>
  <link>https://lea.verou.me/2013/11/flexible-google-style-loader-with-css/</link>
  <pubDate>Sun, 17 May 2026 02:25:04 +0200</pubDate>
  <description>So, for a while I had noticed the nice sutble loader Google apps use and I was wondering if it would be easy to make with CSS and CSS animations: Yesterday, I realised that you can get this effect by increasing border size until about the middle of the element, as long as the total width stays the same (by using box-sizing: border-box ): However, as you can see above, after the midpoint, the border is not curved any more, so does not produce the desired effect. However, what if we split the background colour in half, and animated border-left until 50% of the width and then border-right from 50% of the width? That worked, but only gave us 25% of the effect. I could recreate the whole effect by then animating border-top/bottom instead etc, but its easier to apply animation-direction: alternate to alternate between showing and hiding the circle and and simultaneously rotate the loader by 90deg each time, by applying animation-timing-function: steps(4) to a rotate animation that runs over 4x the duration of the border animation. This is the finished result: The dimensions are all set in ems so that you can change the size in one place: Just change the font-size and the loader scales perfectly. Its also accessible to screen reader users, as there is still text there. And yes, its not super useful as-is, there are tons of spinners on the Web that you can use instead. However, I decided to post it (instead of just tweeting it) as I thought the techniques involved in making it might be interesting for some of you :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Slanted tabs with CSS 3D transforms</title>
  <link>https://lea.verou.me/2013/10/slanted-tabs-with-css-3d-transforms/</link>
  <pubDate>Sun, 17 May 2026 02:25:03 +0200</pubDate>
  <description>Not sure if I’m the first to come up with this idea, but I searched and didn’t find anything. So, for a long time, I was wondering if there’s an easy way to create trapezoid shapes in CSS, especially with borders etc. Eventually, I realized that I could use a pseudo-element for the background and 3D rotate it, so that it appears like a trapezoid. Then @krofdrakula suggested on twitter that I could even add border-radius so that it looks like a tab, so I added that as well: Eventually I thought, why not actually turn this into a tab demo? So I made a dabblet with that. And then I realized that if you change the transform-origin, other interesting tab shapes appear! Enjoy: The best part? It degrades pretty gracefully on browsers that don’t support transforms! You get nice rounded tabs that just aren’t slanted (although they have a pretty large top padding, but you can use Modernizr for that. Try it for yourself by commenting the transform out in the dabblet and see the result. Another issue is that the angled lines look a bit aliased in Firefox, but that’s a bug that will eventually get fixed. In general, it’s a bit rough around the edges, so treat it more as a proof of concept. But with a little more work, it could totally work in production. Tested in Chrome, Safari, Firefox, IE9 (fallback) and IE10.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Slanted tabs with CSS 3D transforms</title>
  <link>https://lea.verou.me/2013/10/slanted-tabs-with-css-3d-transforms/</link>
  <pubDate>Sun, 17 May 2026 02:25:03 +0200</pubDate>
  <description>Not sure if Im the first to come up with this idea, but I searched and didnt find anything. So, for a long time, I was wondering if theres an easy way to create trapezoid shapes in CSS, especially with borders etc. Eventually, I realized that I could use a pseudo-element for the background and 3D rotate it, so that it appears like a trapezoid. Then @krofdrakula suggested on twitter that I could even add border-radius so that it looks like a tab, so I added that as well: Eventually I thought, why not actually turn this into a tab demo? So I made a dabblet with that. And then I realized that if you change the transform-origin, other interesting tab shapes appear! Enjoy: The best part? It degrades pretty gracefully on browsers that dont support transforms! You get nice rounded tabs that just arent slanted (although they have a pretty large top padding, but you can use Modernizr for that. Try it for yourself by commenting the transform out in the dabblet and see the result. Another issue is that the angled lines look a bit aliased in Firefox, but thats a bug that will eventually get fixed. In general, its a bit rough around the edges, so treat it more as a proof of concept. But with a little more work, it could totally work in production. Tested in Chrome, Safari, Firefox, IE9 (fallback) and IE10.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>What makes speakers happy</title>
  <link>https://lea.verou.me/2013/07/what-makes-speakers-happy/</link>
  <pubDate>Sun, 17 May 2026 02:25:02 +0200</pubDate>
  <description>I wish I could speak at CSSConf.eu , but unfortunately I had to decline the invitation, as it collided with a prior speaking engagement I had agreed on. I recently got another email from the organizers with an interesting question: We want to make this event as stress-free for our speakers as possible. Since you spoke at a bunch of events, can you share a tip or two about what will make a speakers’ life easier, and their stay more pleasant? Any typical mistakes we can avoid? I thought it was lovely that they care about their speakers enough to ask this, this already places them above average. I started writing a reply, but I soon realized this is information that could be useful for other conference organizers as well, so I decided to post it here instead. So, what makes speakers happy? The baseline These are things every good conference is doing for their speakers, although they often miss one or two. They keep speakers happy, but they &#39;re not out of the ordinary. Cover their flights, accommodation for the entire conference and ground transportation from/to the airport (with a car, not public transport!). Do not expect them to go through the hassle of booking all those themselves and then sending you receipts. Offer it as an option, but book them yourself by default. Do not book flights without confirming the itinerary and personal info with them first. Also, this sounds obvious, but it’s surprising how many conferences have made this mistake with me: Type their name correctly when booking flights! If hotel WiFi is not free, make sure it’s covered and included in their reservation. Same goes for breakfast. Offer a honorarium, at least to those who have to take time off work to speak at your event (e.g. freelancers). Even if your budget is small and can only give a tiny honorarium, it will at least cover their meals, cabs etc while there. If the honorarium is small and mainly intended to cover miscellaneous expenses of the trip, don’t ask them to submit an invoice to claim it. Have a speakers dinner before the event, where they can meet and socialize with the other speakers. This is also good for the conference, as they get the chance to catch up with their speaker friends (there aren’t that many people on the conference circuit, so we often know each other and want to catch up) so they will talk more to the attendees during the conference. Make sure the speakers dinner does not overlap with the pre-party, if you have one. Do a tech check before their talk to make sure everything is smooth. Have dongles for Mac laptops. Have clickers they could use. Use wireless lapel microphones. Have a reliable private wifi network for speakers to use if they need an internet connection for their talk. Have breaks between talks so they have some margin of going overtime without impacting the schedule. If they are too stressed about going through their talk fast, it won’t be a very good talk. Going the extra mile These are all things one or more conferences have done for me, but they are not generally common so they are a positive surprise when they happen, not something expected. Book Business class flights, especially for longer flights where passengers are expected to sleep. It’s so much more comfortable to sleep in a seat that fully reclines! I was incredibly grateful to the one conference that did this. Cover incidentals in the hotel. Yes, it’s a bit risky but come on, we’re not rockstars. We won’t screw you over. In most cases it will be a pretty small extra cost and it looks really good, it tells speakers you trust them and want them to have a good time. Offer a speaker gift bag. It can contain all kinds of things: Stuff that will make their stay more comfortable (stain remover, travel toothbrush etc), souvenirs from the place since we rarely have time to do touristy stuff, alcohol for impromptu get togethers with other speakers, snacks to eat during a late night craving in the hotel room, anything goes and I’ve seen conferences put all kinds of stuff in there. It’s a nice welcome gesture. Bonus points if they’re personalized based on what you’ve researched about the speaker. Send out a survey to the audience after the conference and let the speakers know how they did . Let them know what comments their talk got and how well they did compared to other speakers. Also, make sure you read PPK’s excellent Conference Organizer’s Handbook .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>What makes speakers happy</title>
  <link>https://lea.verou.me/2013/07/what-makes-speakers-happy/</link>
  <pubDate>Sun, 17 May 2026 02:25:02 +0200</pubDate>
  <description>I wish I could speak at CSSConf.eu , but unfortunately I had to decline the invitation, as it collided with a prior speaking engagement I had agreed on. I recently got another email from the organizers with an interesting question: We want to make this event as stress-free for our speakers as possible. Since you spoke at a bunch of events, can you share a tip or two about what will make a speakers life easier, and their stay more pleasant? Any typical mistakes we can avoid? I thought it was lovely that they care about their speakers enough to ask this, this already places them above average. I started writing a reply, but I soon realized this is information that could be useful for other conference organizers as well, so I decided to post it here instead. So, what makes speakers happy? The baseline These are things every good conference is doing for their speakers, although they often miss one or two. They keep speakers happy, but they &#39;re not out of the ordinary. Cover their flights, accommodation for the entire conference and ground transportation from/to the airport (with a car, not public transport!). Do not expect them to go through the hassle of booking all those themselves and then sending you receipts. Offer it as an option, but book them yourself by default. Do not book flights without confirming the itinerary and personal info with them first. Also, this sounds obvious, but its surprising how many conferences have made this mistake with me: Type their name correctly when booking flights! If hotel WiFi is not free, make sure its covered and included in their reservation. Same goes for breakfast. Offer a honorarium, at least to those who have to take time off work to speak at your event (e.g. freelancers). Even if your budget is small and can only give a tiny honorarium, it will at least cover their meals, cabs etc while there. If the honorarium is small and mainly intended to cover miscellaneous expenses of the trip, dont ask them to submit an invoice to claim it. Have a speakers dinner before the event, where they can meet and socialize with the other speakers. This is also good for the conference, as they get the chance to catch up with their speaker friends (there arent that many people on the conference circuit, so we often know each other and want to catch up) so they will talk more to the attendees during the conference. Make sure the speakers dinner does not overlap with the pre-party, if you have one. Do a tech check before their talk to make sure everything is smooth. Have dongles for Mac laptops. Have clickers they could use. Use wireless lapel microphones. Have a reliable private wifi network for speakers to use if they need an internet connection for their talk. Have breaks between talks so they have some margin of going overtime without impacting the schedule. If they are too stressed about going through their talk fast, it wont be a very good talk. Going the extra mile These are all things one or more conferences have done for me, but they are not generally common so they are a positive surprise when they happen, not something expected. Book Business class flights, especially for longer flights where passengers are expected to sleep. Its so much more comfortable to sleep in a seat that fully reclines! I was incredibly grateful to the one conference that did this. Cover incidentals in the hotel. Yes, its a bit risky but come on, were not rockstars. We wont screw you over. In most cases it will be a pretty small extra cost and it looks really good, it tells speakers you trust them and want them to have a good time. Offer a speaker gift bag. It can contain all kinds of things: Stuff that will make their stay more comfortable (stain remover, travel toothbrush etc), souvenirs from the place since we rarely have time to do touristy stuff, alcohol for impromptu get togethers with other speakers, snacks to eat during a late night craving in the hotel room, anything goes and Ive seen conferences put all kinds of stuff in there. Its a nice welcome gesture. Bonus points if theyre personalized based on what youve researched about the speaker. Send out a survey to the audience after the conference and let the speakers know how they did . Let them know what comments their talk got and how well they did compared to other speakers. Also, make sure you read PPKs excellent Conference Organizers Handbook .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Leaving W3C</title>
  <link>https://lea.verou.me/2013/07/leaving-w3c/</link>
  <pubDate>Sun, 17 May 2026 02:05:18 +0200</pubDate>
  <description>About a year ago, I announced I was joining W3C as a full-time staff member, to work on Developer Relations and education. Working at W3C was a dream come true and I can’t say I was disappointed. Over the past year I’ve worked with some amazingly brilliant people, hopefully increased awareness for web standards in the developer community and helped materialize the vision behind WebPlatform.org . It’s been a fun ride and working for a non-profit was very fulfilling. If somebody told me a year ago that I would decide to leave W3C on my own free will, I would’ve asked them what they were smoking. However, our future selves often surprise us and although it was the most difficult decision of my life, I recently decided to leave. July 31st will be my last day at W3C. I will attempt to describe the reasons below for anyone interested, but in no way does me leaving mean that I don’t deeply appreciate W3C or that I regretted joining. If I could go a year back, I would make the same choice. Reason #1: I want to focus on other projects I didn’t have much time to work on my pet projects, as my job was consuming pretty much the entire me. This is absolutely not W3C’s fault, it’s mine and a pretty common side effect of working from home. Pull requests kept piling up on Github, I didn’t have many ideas for new side projects or time for research &amp; to come up with new techniques. I was able to work a bit on Dabblet and a WPD Prism plugin , as they were useful for WebPlatform.org , but for the most part, I wanted to work more on open source projects, do more research, blog more etc. I also recently signed a book deal with O’Reilly for a book on advanced CSS techniques (“CSS Secrets”, ETA Spring 2014) and I wanted to take some time off and write a great inaugural book, not just a decent one (and design it too!). I also kinda missed doing workshops or even client work, who knew! Having more time will also mean I will be able to focus more on standards work, which is a huge passion of mine. I know it sounds odd to leave W3C to work more on …standards, but standards work was never a part of my job at W3C. If I wanted to devote time to actively participate in the CSS WG beyond the weekly telcon, or to the specification I edit , I would have to do it outside work hours. Obviously, I will still have to do it in my free time, but I recall having more of that when I was self-employed. Reason #2: I want to grow I want to be in a job that’s a challenge, that helps me grow and become a better professional. While I appreciate WebPlatform.org , I didn’t feel that doing front-end development &amp; design on it made me particularly better at what I do, at least compared to other things I could have been doing in the past year. It could be a perfect opportunity to grow for someone else, but it wasn’t for me. I did become a better public speaker over the past year, but I would likely be doing as many talks anyway. I got some valuable conference organizing experience from W3Conf , which I thoroughly enjoyed working on, but that was only a small part of my work. Reason #3: Different direction Had I stayed, my job description for the upcoming year would have a slightly different focus. Since W3C Developer Relations was a new activity, neither Doug (my manager) nor I were quite sure how we could make the biggest impact, so we were experimenting to some degree. A few months after I joined, WebPlatform.org launched and we slowly concentrated our efforts on that. If I had stayed for another year, my job would have an even stronger WebPlatform.org focus. Half of it would be front-end design &amp; development and even writing documentation for a day per week. That meant I would have to cut down many parts of my job that I enjoyed and wanted to concentrate more on, such as public speaking and event planning, and though it includes some public-facing activities like gathering feedback from developers, I’d like to do even more of that. This was not a bad decision on W3C’s part — WebPlatform.org needs somebody concentrating on those aspects of it. However, although I strongly believe in the vision behind the project, this was not what I would personally enjoy doing. Thank you, W3C Even though I’m leaving W3C, it will always have a very special place in my heart. I met &amp; worked with the most brilliant people I have ever met. Special mention to Amy , who did not just prove to be an intelligent, interesting and kind person, but also a great friend in the past couple of weeks that I got to know her better. I got to visit MIT and work from there for a while, which was an incredible experience. I got to contribute to WebPlatform.org which is a very ambitious and honorable project that I strongly believe in. I got to co-organize W3Conf , which turned out to a successful and fun conference. Me leaving is a personal decision that has less to do with W3C and more to do with what I want out of life. But I’m going to sorely miss the W3C Team, the culture, the technical discussions. It’s been a fun ride and I’m grateful for the chance and the trust W3C placed in me. In fact, I wouldn’t be surprised to find myself working for W3C again at some point in the future, in some way or in a different role. But for now, here’s to the future! I’m thrilled. Want to work at W3C? As you can imagine, there is one more opening now. :) Are you a great designer with front-end development skills? Are you passionate about creating the best open web platform documentation on the Web? Apply now! You will be able to work from wherever in the world you want, whatever hours in the day you want, you will have great autonomy and a pretty cool boss . Sweet, huh?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Meet dpi.lv: More than you probably wanted to know about screen DPI</title>
  <link>https://lea.verou.me/2013/06/meet-dpi-lv-more-than-you-probably-wanted-to-know-about-screen-dpi/</link>
  <pubDate>Sun, 17 May 2026 02:05:18 +0200</pubDate>
  <description>Yesterday (Sunday) I was on a 9.5 hour flight from Canada with no inflight entertainment (well, thanks Air Canada), so I did what every bored human being would do instead of watching movies: I decided to code an app! And out of the infinite set of possible apps somebody can make, I decided to make an app to calculate screen DPI/PPI. You might be wondering if I’m still (?) sane, but you might be surprised to hear I found myself calculating screen PPIs quite frequently and wanted to save myself the hassle of doing the math every time. I’m a curious person and I wanted to know, even about products I would never buy and even when it wasn’t listed in the tech specs. Yes, my hobbies are somewhat weird. :o I first thought about doing such an app a while ago, but never found the time to code it. The last time I had thought about it was a few days ago at the SF Apple Store with a friend. We were looking at the 27&quot; Apple Thunderbolt displays in awe and thought they must have huge pixel density. After a few calculations in the console (which ironically produced a result faster than the Apple guy my friend asked), it turned out it was only …102. “I need to code an app to make this sort of calculation easy! People are being misled by marketing!” I thought. Fast forward to my flight. You didn’t expect my laptop battery to last for 9.5 hours, right? Yeah, MacBook Air batteries are good, but not *that* good. Of course it eventually died so I had to find other ways to pass my time (I ended up sleeping — or trying to). However, by the time it died, I had gone over the threshold of being able to give it up, so I spent the rest of the day finishing it, despite my obvious jetlag and sleepiness. I was in the zone — You don’t just go sleeping when you’re in the zone, right? Besides the DPI/PPI calculator, I added a few other fun things too: A list of devices with pre-calculated data (stored in a separate JSON file, which makes it easy to update — *hint, hint*) Wrote a few FAQ items about DPI/PPI. Like many of my apps, it supports link sharing through URL hashes (for examples, check the screens section). I even bought a proper domain for it ( dpi.lv ) and drew a logo ! The logo took hours by itself. Not just to draw it, but to simplify Illustrator’s ugly, repetitive SVG output (which is still better than what most other tools spit out). Hand-simplifying SVG is a meditative experience that I thoroughly enjoy, to the bewilderment of everyone who read my tweet about it . Just for the lulz, here’s the before and the 66% smaller after (the small design tweaks were intentional) The screen that displays the result resizes to reflect the aspect ratio of the resolution you’ve selected. It even animates to it, with CSS transitions! Oh, and it also uses FlexBox to center the text vertically. Enjoy ! Of course it’s open source (under an MIT license, as usual), and you can fork it on Github , as usual. The JS is a bit of a mess, but I’m too tired to refactor it now. Same goes for the lack of favicon and tagline. Oh well. I still like it. :) Important: If you are on a display with multiple dots per pixel (e.g. Retina), the resolution (pixel width × pixel height) it tries to guess will be incorrect, so you’ll have to actually input the right one. The default resolution in there is just a hint, it doesn’t mean it’s “broken” if it doesn’t guess right, they’re editable fields. That said, it would be nice to guess right in those cases too, and I will look into it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Leaving W3C</title>
  <link>https://lea.verou.me/2013/07/leaving-w3c/</link>
  <pubDate>Sun, 17 May 2026 02:05:18 +0200</pubDate>
  <description>About a year ago, I announced I was joining W3C as a full-time staff member, to work on Developer Relations and education. Working at W3C was a dream come true and I cant say I was disappointed. Over the past year Ive worked with some amazingly brilliant people, hopefully increased awareness for web standards in the developer community and helped materialize the vision behind WebPlatform.org . Its been a fun ride and working for a non-profit was very fulfilling. If somebody told me a year ago that I would decide to leave W3C on my own free will, I wouldve asked them what they were smoking. However, our future selves often surprise us and although it was the most difficult decision of my life, I recently decided to leave. July 31st will be my last day at W3C. I will attempt to describe the reasons below for anyone interested, but in no way does me leaving mean that I dont deeply appreciate W3C or that I regretted joining. If I could go a year back, I would make the same choice. Reason #1: I want to focus on other projects I didnt have much time to work on my pet projects, as my job was consuming pretty much the entire me. This is absolutely not W3Cs fault, its mine and a pretty common side effect of working from home. Pull requests kept piling up on Github, I didnt have many ideas for new side projects or time for research &amp; to come up with new techniques. I was able to work a bit on Dabblet and a WPD Prism plugin , as they were useful for WebPlatform.org , but for the most part, I wanted to work more on open source projects, do more research, blog more etc. I also recently signed a book deal with OReilly for a book on advanced CSS techniques (CSS Secrets, ETA Spring 2014) and I wanted to take some time off and write a great inaugural book, not just a decent one (and design it too!). I also kinda missed doing workshops or even client work, who knew! Having more time will also mean I will be able to focus more on standards work, which is a huge passion of mine. I know it sounds odd to leave W3C to work more on standards, but standards work was never a part of my job at W3C. If I wanted to devote time to actively participate in the CSS WG beyond the weekly telcon, or to the specification I edit , I would have to do it outside work hours. Obviously, I will still have to do it in my free time, but I recall having more of that when I was self-employed. Reason #2: I want to grow I want to be in a job thats a challenge, that helps me grow and become a better professional. While I appreciate WebPlatform.org , I didnt feel that doing front-end development &amp; design on it made me particularly better at what I do, at least compared to other things I could have been doing in the past year. It could be a perfect opportunity to grow for someone else, but it wasnt for me. I did become a better public speaker over the past year, but I would likely be doing as many talks anyway. I got some valuable conference organizing experience from W3Conf , which I thoroughly enjoyed working on, but that was only a small part of my work. Reason #3: Different direction Had I stayed, my job description for the upcoming year would have a slightly different focus. Since W3C Developer Relations was a new activity, neither Doug (my manager) nor I were quite sure how we could make the biggest impact, so we were experimenting to some degree. A few months after I joined, WebPlatform.org launched and we slowly concentrated our efforts on that. If I had stayed for another year, my job would have an even stronger WebPlatform.org focus. Half of it would be front-end design &amp; development and even writing documentation for a day per week. That meant I would have to cut down many parts of my job that I enjoyed and wanted to concentrate more on, such as public speaking and event planning, and though it includes some public-facing activities like gathering feedback from developers, Id like to do even more of that. This was not a bad decision on W3Cs part WebPlatform.org needs somebody concentrating on those aspects of it. However, although I strongly believe in the vision behind the project, this was not what I would personally enjoy doing. Thank you, W3C Even though Im leaving W3C, it will always have a very special place in my heart. I met &amp; worked with the most brilliant people I have ever met. Special mention to Amy , who did not just prove to be an intelligent, interesting and kind person, but also a great friend in the past couple of weeks that I got to know her better. I got to visit MIT and work from there for a while, which was an incredible experience. I got to contribute to WebPlatform.org which is a very ambitious and honorable project that I strongly believe in. I got to co-organize W3Conf , which turned out to a successful and fun conference. Me leaving is a personal decision that has less to do with W3C and more to do with what I want out of life. But Im going to sorely miss the W3C Team, the culture, the technical discussions. Its been a fun ride and Im grateful for the chance and the trust W3C placed in me. In fact, I wouldnt be surprised to find myself working for W3C again at some point in the future, in some way or in a different role. But for now, heres to the future! Im thrilled. Want to work at W3C? As you can imagine, there is one more opening now. :) Are you a great designer with front-end development skills? Are you passionate about creating the best open web platform documentation on the Web? Apply now! You will be able to work from wherever in the world you want, whatever hours in the day you want, you will have great autonomy and a pretty cool boss . Sweet, huh?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Meet dpi.lv: More than you probably wanted to know about screen DPI</title>
  <link>https://lea.verou.me/2013/06/meet-dpi-lv-more-than-you-probably-wanted-to-know-about-screen-dpi/</link>
  <pubDate>Sun, 17 May 2026 02:05:18 +0200</pubDate>
  <description>Yesterday (Sunday) I was on a 9.5 hour flight from Canada with no inflight entertainment (well, thanks Air Canada), so I did what every bored human being would do instead of watching movies: I decided to code an app! And out of the infinite set of possible apps somebody can make, I decided to make an app to calculate screen DPI/PPI. You might be wondering if Im still (?) sane, but you might be surprised to hear I found myself calculating screen PPIs quite frequently and wanted to save myself the hassle of doing the math every time. Im a curious person and I wanted to know, even about products I would never buy and even when it wasnt listed in the tech specs. Yes, my hobbies are somewhat weird. :o I first thought about doing such an app a while ago, but never found the time to code it. The last time I had thought about it was a few days ago at the SF Apple Store with a friend. We were looking at the 27&quot; Apple Thunderbolt displays in awe and thought they must have huge pixel density. After a few calculations in the console (which ironically produced a result faster than the Apple guy my friend asked), it turned out it was only 102. I need to code an app to make this sort of calculation easy! People are being misled by marketing! I thought. Fast forward to my flight. You didnt expect my laptop battery to last for 9.5 hours, right? Yeah, MacBook Air batteries are good, but not *that* good. Of course it eventually died so I had to find other ways to pass my time (I ended up sleeping or trying to). However, by the time it died, I had gone over the threshold of being able to give it up, so I spent the rest of the day finishing it, despite my obvious jetlag and sleepiness. I was in the zone You dont just go sleeping when youre in the zone, right? Besides the DPI/PPI calculator, I added a few other fun things too: A list of devices with pre-calculated data (stored in a separate JSON file, which makes it easy to update *hint, hint*) Wrote a few FAQ items about DPI/PPI. Like many of my apps, it supports link sharing through URL hashes (for examples, check the screens section). I even bought a proper domain for it ( dpi.lv ) and drew a logo ! The logo took hours by itself. Not just to draw it, but to simplify Illustrators ugly, repetitive SVG output (which is still better than what most other tools spit out). Hand-simplifying SVG is a meditative experience that I thoroughly enjoy, to the bewilderment of everyone who read my tweet about it . Just for the lulz, heres the before and the 66% smaller after (the small design tweaks were intentional) The screen that displays the result resizes to reflect the aspect ratio of the resolution youve selected. It even animates to it, with CSS transitions! Oh, and it also uses FlexBox to center the text vertically. Enjoy ! Of course its open source (under an MIT license, as usual), and you can fork it on Github , as usual. The JS is a bit of a mess, but Im too tired to refactor it now. Same goes for the lack of favicon and tagline. Oh well. I still like it. :) Important: If you are on a display with multiple dots per pixel (e.g. Retina), the resolution (pixel width pixel height) it tries to guess will be incorrect, so youll have to actually input the right one. The default resolution in there is just a hint, it doesnt mean its broken if it doesnt guess right, theyre editable fields. That said, it would be nice to guess right in those cases too, and I will look into it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Can we get rid of gradient prefixes?</title>
  <link>https://lea.verou.me/2013/04/can-we-get-rid-of-gradient-prefixes/</link>
  <pubDate>Sun, 17 May 2026 02:05:16 +0200</pubDate>
  <description>I recently realized that unprefixed gradients finally propagated to stable Chrome, and after tweeting about it , I decided to do some research on which browsers support unprefixed gradients, and what percentage of users needs them. Currently, unprefixed gradients are supported in: Chrome 26+ Firefox 16+ Opera 12.10+ IE10+ Lets have a look at which prefixes we actually need to use for gradients today. -ms- There was never a stable release of IE that supported -ms- prefixed gradients, those were only in preview versions (stable IE10 supports both prefixed and unprefixed gradients). So, -ms- is most definitely not required. -moz- Firefox versions &gt;= 3.6 and 4% of the global user base*. This might or might not be significant, depending on how good the fallback is that these users will see. If the gradient only adds a subtle shadow or something like that, I’d say ditch -moz-. If it’s more crucial to the design &amp; branding, it might be wise to still keep it. More tech-focused websites probably have a much lower percentage than 4%, so it might be a good idea to drop it there completely. -o- Opera unprefixed gradients in 12.10. Opera Mini never supported them. Opera versions 0.25% of the global user base*. I’d say it’s safe to ditch -o- in gradients in most cases. -webkit- Chrome only very recently unprefixed gradients and Safari is a long way from doing so. Not to mention all the mobile browsers using WebKit. Unfortunately, we can’t ditch -webkit- in CSS gradients just yet. My opinion Don’t use -ms- prefixed gradients, there’s absolutely zero point in doing so. Include -moz- for the less subtle gradients. No significant need for -o- gradients. -webkit- is still needed and probably will be at least until the end of 2013. Or, of course, just use -prefix-free and don’t bother. :P Keep in mind that your stats might differ from global stats, so which prefixes you need to include might differ on a case by case basis. The purpose of this post is to alert you that maybe you don’t need all these prefixes, not to prescriptively tell you which ones to keep. Except -ms-, please don’t use that. There’s absolutely zero reason whatsoever. Last but not least, no matter which prefixes you include, always have a good solid color fallback! * Global market share statistics from StatCounter , for a 3 month period of January 2013 - March 2013. The graph on the website only displays the most popular browser versions, but downloading the CSV file gives you all of them.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>border-corner-shape is in danger, and you can help!</title>
  <link>https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/</link>
  <pubDate>Sun, 17 May 2026 02:05:16 +0200</pubDate>
  <description>Remember my previous post about an app I made to preview border-corner-shape ? It stirred a lot of discussion in the CSS WG, and David Baron posted this : http://dev.w3.org/csswg/css4-background/#border-corner-shape appears to me to be an example of a feature that’s addressing a problem that we don’t have – or at least that we don’t have enough to be worth adding such a feature. I think it should be removed. In particular, if there were demand for the bevel | curve | notch values, we’d be seeing authors using the equivalents of such values on significant numbers of Web sites. So before agreeing to accept this new feature, I’d like to see examples of Web sites that are doing what these values would do. Not something similar to what these values would do, but exactly what these values would do, or at least close enough that the author wouldn’t care about the difference. You can read the whole discussion in the thread I linked to, above. I might be wrong, but I believe border-corner-shape would greatly simplify many common effects, especially its “bevel” value, which can even create triangles and other polygons, that we go to great lengths to make with CSS today, and it would degrade much more nicely than border-image or backgrounds. I think it was one of fantasai ’s many great ideas and I’m glad she added it to the Editor’s Draft of Backgrounds &amp; Borders 4 . I posted a list of tutorials and questions from web designers &amp; developers , to illustrate that these effects are needed. However, David argued that “Questions from authors don’t give you enough information to be sure that the feature being added is sufficient for the author’s needs ” . He did have a point, so with some help from the community, I posted a few links to websites using such effects, and use cases . Nicole Sullivan , Liam Quin , fantasai and Lev Solntsev posted a couple more. However, the more real examples we have, the more likely it is to retain the feature in some form. This is where you come in: If you think border-corner-shape has merit, provide use cases, either by links to websites whose design elements it would simplify, screenshots of websites or descriptions of cases where you needed such a thing (in that order of preference). You can either post to the thread directly, or comment here and I’ll post them to the list in batches. If you think it has merit but it could be improved, feel free to post about that as well. If you don’t think it’s a good idea, any alternatives you can think of are welcome as well. Or, if you don’t think it’s useful, say that too (but make sure you first fully understand what it can do). If you’re not sure how it can be used, play around with the demo app I made and be creative! Below are a few examples of shapes: I wanted to demo triangles and trapezoids as well, but it seems there’s a bug in my app, so I’ll have to debug it first :( If we allow border-corner-shape to have different values for all four corners, even more possibilites open (e.g. arrows). Spend a few minutes to help the CSS WG help you. Thanks!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Can we get rid of gradient prefixes?</title>
  <link>https://lea.verou.me/2013/04/can-we-get-rid-of-gradient-prefixes/</link>
  <pubDate>Sun, 17 May 2026 02:05:16 +0200</pubDate>
  <description>I recently realized that unprefixed gradients finally propagated to stable Chrome, and after tweeting about it , I decided to do some research on which browsers support unprefixed gradients, and what percentage of users needs them. Currently, unprefixed gradients are supported in: Chrome 26+ Firefox 16+ Opera 12.10+ IE10+ Lets have a look at which prefixes we actually need to use for gradients today. -ms- There was never a stable release of IE that supported -ms- prefixed gradients, those were only in preview versions (stable IE10 supports both prefixed and unprefixed gradients). So, -ms- is most definitely not required. -moz- Firefox versions &gt;= 3.6 and 4% of the global user base*. This might or might not be significant, depending on how good the fallback is that these users will see. If the gradient only adds a subtle shadow or something like that, Id say ditch -moz-. If its more crucial to the design &amp; branding, it might be wise to still keep it. More tech-focused websites probably have a much lower percentage than 4%, so it might be a good idea to drop it there completely. -o- Opera unprefixed gradients in 12.10. Opera Mini never supported them. Opera versions 0.25% of the global user base*. Id say its safe to ditch -o- in gradients in most cases. -webkit- Chrome only very recently unprefixed gradients and Safari is a long way from doing so. Not to mention all the mobile browsers using WebKit. Unfortunately, we cant ditch -webkit- in CSS gradients just yet. My opinion Dont use -ms- prefixed gradients, theres absolutely zero point in doing so. Include -moz- for the less subtle gradients. No significant need for -o- gradients. -webkit- is still needed and probably will be at least until the end of 2013. Or, of course, just use -prefix-free and dont bother. :P Keep in mind that your stats might differ from global stats, so which prefixes you need to include might differ on a case by case basis. The purpose of this post is to alert you that maybe you dont need all these prefixes, not to prescriptively tell you which ones to keep. Except -ms-, please dont use that. Theres absolutely zero reason whatsoever. Last but not least, no matter which prefixes you include, always have a good solid color fallback! * Global market share statistics from StatCounter , for a 3 month period of January 2013 - March 2013. The graph on the website only displays the most popular browser versions, but downloading the CSV file gives you all of them.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>border-corner-shape is in danger, and you can help!</title>
  <link>https://lea.verou.me/2013/03/border-corner-shape-is-in-danger-and-you-can-help/</link>
  <pubDate>Sun, 17 May 2026 02:05:16 +0200</pubDate>
  <description>Remember my previous post about an app I made to preview border-corner-shape ? It stirred a lot of discussion in the CSS WG, and David Baron posted this : http://dev.w3.org/csswg/css4-background/#border-corner-shape appears to me to be an example of a feature thats addressing a problem that we dont have or at least that we dont have enough to be worth adding such a feature. I think it should be removed. In particular, if there were demand for the bevel | curve | notch values, wed be seeing authors using the equivalents of such values on significant numbers of Web sites. So before agreeing to accept this new feature, Id like to see examples of Web sites that are doing what these values would do. Not something similar to what these values would do, but exactly what these values would do, or at least close enough that the author wouldnt care about the difference. You can read the whole discussion in the thread I linked to, above. I might be wrong, but I believe border-corner-shape would greatly simplify many common effects, especially its bevel value, which can even create triangles and other polygons, that we go to great lengths to make with CSS today, and it would degrade much more nicely than border-image or backgrounds. I think it was one of fantasai s many great ideas and Im glad she added it to the Editors Draft of Backgrounds &amp; Borders 4 . I posted a list of tutorials and questions from web designers &amp; developers , to illustrate that these effects are needed. However, David argued that Questions from authors dont give you enough information to be sure that the feature being added is sufficient for the authors needs . He did have a point, so with some help from the community, I posted a few links to websites using such effects, and use cases . Nicole Sullivan , Liam Quin , fantasai and Lev Solntsev posted a couple more. However, the more real examples we have, the more likely it is to retain the feature in some form. This is where you come in: If you think border-corner-shape has merit, provide use cases, either by links to websites whose design elements it would simplify, screenshots of websites or descriptions of cases where you needed such a thing (in that order of preference). You can either post to the thread directly, or comment here and Ill post them to the list in batches. If you think it has merit but it could be improved, feel free to post about that as well. If you dont think its a good idea, any alternatives you can think of are welcome as well. Or, if you dont think its useful, say that too (but make sure you first fully understand what it can do). If youre not sure how it can be used, play around with the demo app I made and be creative! Below are a few examples of shapes: I wanted to demo triangles and trapezoids as well, but it seems theres a bug in my app, so Ill have to debug it first :( If we allow border-corner-shape to have different values for all four corners, even more possibilites open (e.g. arrows). Spend a few minutes to help the CSS WG help you. Thanks!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Easily center text vertically, with SVG!</title>
  <link>https://lea.verou.me/2013/03/easily-center-text-vertically-with-svg/</link>
  <pubDate>Sun, 17 May 2026 02:05:15 +0200</pubDate>
  <description>These days, we have a number of different ways to vertically align text in a container of variable dimensions: Table display modes Flexbox inline-block hacks Wrapping the text in an extra element and absolutely positioning it …and probably many others I’m forgetting However, often comes a time when neither is suitable, so here I am, adding yet another option to the list. Of course, it comes with its own set of drawbacks, but there are cases where it might be better than the existing solutions. It all started when I discovered the text-anchor SVG property. It determines where the x and y attributes on elements refer to. The magic starts when you set it to “middle”, then the x and y attributes refer to the center of the text. So, if you set those to 50%, they refer to the center of the SVG graphic itself, and if you set the SVG width and height to 100%, the text basically sits in the center of the ’s container, which could be any HTML element! One issue was that this centered the baseline of the text, so I tried to find a way to shift the baseline appropriately. Setting dominant-baseline : middle; on the element seemed to fix it, but it looks like IE doesn’t support that. I ended up adding dy =“.3em” to the element, which fixes it but might need to be adjusted if you change the line-height. In addition, this method has the following drawbacks I can think of: Extra markup (namely 2 elements: and ) If the text is more than one line, it won’t automatically wrap, you have to do it manually. Some new-ish CSS text properties may not be applied. For example, text-shadow is applied in Chrome but not in Firefox, since technically, it’s still not a part of the SVG spec. You need to duplicate the text color as a fill property, since SVG does not understand the color CSS property. No need to duplicate anything, just use fill: currentColor; ( thanks GreLI! ) However, it has a few advantages too: You don’t need to change anything on the parent HTML element Degrades gracefully in non-SVG browsers Should be perfectly accessible and won’t break SEO Works perfectly in IE9, unlike Flexbox You can include any kind of SVG styling on the text. For example, strokes! You can see and play with the result in the dabblet below: Verified to work in at least Chrome, Firefox, IE9+. Hope it’s useful, even though it won’t be a good fit in every single use case.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Easily center text vertically, with SVG!</title>
  <link>https://lea.verou.me/2013/03/easily-center-text-vertically-with-svg/</link>
  <pubDate>Sun, 17 May 2026 02:05:15 +0200</pubDate>
  <description>These days, we have a number of different ways to vertically align text in a container of variable dimensions: Table display modes Flexbox inline-block hacks Wrapping the text in an extra element and absolutely positioning it and probably many others Im forgetting However, often comes a time when neither is suitable, so here I am, adding yet another option to the list. Of course, it comes with its own set of drawbacks, but there are cases where it might be better than the existing solutions. It all started when I discovered the text-anchor SVG property. It determines where the x and y attributes on elements refer to. The magic starts when you set it to middle, then the x and y attributes refer to the center of the text. So, if you set those to 50%, they refer to the center of the SVG graphic itself, and if you set the SVG width and height to 100%, the text basically sits in the center of the s container, which could be any HTML element! One issue was that this centered the baseline of the text, so I tried to find a way to shift the baseline appropriately. Setting dominant-baseline : middle; on the element seemed to fix it, but it looks like IE doesnt support that. I ended up adding dy =.3em to the element, which fixes it but might need to be adjusted if you change the line-height. In addition, this method has the following drawbacks I can think of: Extra markup (namely 2 elements: and ) If the text is more than one line, it wont automatically wrap, you have to do it manually. Some new-ish CSS text properties may not be applied. For example, text-shadow is applied in Chrome but not in Firefox, since technically, its still not a part of the SVG spec. You need to duplicate the text color as a fill property, since SVG does not understand the color CSS property. No need to duplicate anything, just use fill: currentColor; ( thanks GreLI! ) However, it has a few advantages too: You dont need to change anything on the parent HTML element Degrades gracefully in non-SVG browsers Should be perfectly accessible and wont break SEO Works perfectly in IE9, unlike Flexbox You can include any kind of SVG styling on the text. For example, strokes! You can see and play with the result in the dabblet below: Verified to work in at least Chrome, Firefox, IE9+. Hope its useful, even though it wont be a good fit in every single use case.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Preview corner-shape, before implementations!</title>
  <link>https://lea.verou.me/2013/03/preview-border-corner-shape-before-implementations/</link>
  <pubDate>Sun, 17 May 2026 02:05:14 +0200</pubDate>
  <description>As an editor of the Backgrounds &amp; Borders Level 4 spec, I am naturally a bit more interested in the cool features it will bring, once implementations start (it’s currently too early for that). One of the coolest features in it is corner-shape . While in Backgrounds &amp; Borders 3 , border-radius was only used for rounded (actually, elliptical) corners, with the help of corner-shape, it will be able to do so much more! Beveled corners, scoop-style corners (informally known as “negative border-radius”), even rectangular notches. Unfortunately, until it’s implemented in browsers, it’s hard to play with it. Or, is it? I spent the weekend creating an app in which you can enter values for corner-shape, border-radius, width, and height, and see the result, simulated through SVG, as well as the fallback in browsers that don’t support border-corner-radius (which is currently all browsers). Obviously, it’s not a full preview, since you can only play with a limited subset of CSS properties, but it should be good for seeing the kinds of shapes that will be possible.You could also copy the generated SVG from the Developer tools of your browser, and use it as a background in any website! Use it here: corner-shape preview Tested to work in at least Chrome, IE9, Firefox, Safari and theoretically, should work in any SVG-enabled browser. Enjoy! Hope you like it. Important: Please note that corner-shape is still at a very early stage and might completely change before implementations. You can also help to make it better: Play with it and comment on what you think about its naming and functionality!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Preview corner-shape, before implementations!</title>
  <link>https://lea.verou.me/2013/03/preview-border-corner-shape-before-implementations/</link>
  <pubDate>Sun, 17 May 2026 02:05:14 +0200</pubDate>
  <description>As an editor of the Backgrounds &amp; Borders Level 4 spec, I am naturally a bit more interested in the cool features it will bring, once implementations start (its currently too early for that). One of the coolest features in it is corner-shape . While in Backgrounds &amp; Borders 3 , border-radius was only used for rounded (actually, elliptical) corners, with the help of corner-shape, it will be able to do so much more! Beveled corners, scoop-style corners (informally known as negative border-radius), even rectangular notches. Unfortunately, until its implemented in browsers, its hard to play with it. Or, is it? I spent the weekend creating an app in which you can enter values for corner-shape, border-radius, width, and height, and see the result, simulated through SVG, as well as the fallback in browsers that dont support border-corner-radius (which is currently all browsers). Obviously, its not a full preview, since you can only play with a limited subset of CSS properties, but it should be good for seeing the kinds of shapes that will be possible.You could also copy the generated SVG from the Developer tools of your browser, and use it as a background in any website! Use it here: corner-shape preview Tested to work in at least Chrome, IE9, Firefox, Safari and theoretically, should work in any SVG-enabled browser. Enjoy! Hope you like it. Important: Please note that corner-shape is still at a very early stage and might completely change before implementations. You can also help to make it better: Play with it and comment on what you think about its naming and functionality!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Use MathML today, with CSS fallback!</title>
  <link>https://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/</link>
  <pubDate>Sun, 17 May 2026 02:05:13 +0200</pubDate>
  <description>These days, I’m working on the slides for my next talk , “The humble border-radius”. It will be about how much work is put into CSS features that superficially look as simple as border-radius, as well as what advances are in store for it in CSS Backgrounds &amp; Borders 4 (of which I’m an editor). It will be fantastic and you should come, but this post is not about my talk. As you may know, my slides are made with HTML, CSS &amp; JavaScript . At some point, I wanted to insert an equation to show how border-top-left-radius (as an example) shrinks proportionally when the sum of radii on the top side exceeds the width of the element. I don’t like LaTeX because it produces bitmap images that don’t scale and is inaccessible. The obvious open standard to use was MathML , and it can even be directly embedded in HTML5 without all the XML cruft, just like SVG. I had never written MathML before, but after a bit of reading and poking around existing samples, I managed to write the following MathML code: r′ top-left = min ( r top-left , width × r top-left r top-left + r top-right ) I was very proud of myself. My first MathML equation! It’s actually pretty simple when you get the hang of it: is for identifiers, for operators and those are used everywhere. For more complex stuff, there’s for fractions (along with to denote the rows), for square roots and so on. It looked very nice on Firefox, especially after I applied Cambria Math to it instead of the default Times-ish font: However, I soon realized that as awesome as MathML might be, not not all browsers had seen the light . IE10 and Chrome are the most notable offenders. It looked like an unreadable mess in Chrome: There are libraries to make it work cross-browser, the most popular of which is MathJax . However, this was pretty big for my needs, I just wanted one simple equation in one goddamn slide. It would be like using a chainsaw to cut a slice of bread! The solution I decided to go with was to use Modernizr to detect MathML support, since apparently it’s not simple at all . Then, I used the .no-mathml class in conjunction with selectors that target the MathML elements, to mimic proper styling with simple CSS. It’s not a complete CSS library by any means, I just covered what I needed for that particular equation and tried to write it in a generic way, so that if I need it in future equations, I only have to add rules. Here’s a screenshot of the result in Chrome: It doesn’t look as good as Firefox, but it’s decent. You can see the CSS rules I used in the following Dabblet: Obviously it’s not a complete MathML-to-CSS library, if one is even possible, but it works well for my use case. If I have to use more MathML features, I’d write more CSS rules. The intention of this post is not to provide a CSS framework to use as a MathML fallback, but to show you a solution you could adapt to your needs. Hope it helps!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>iOS 6 switch style checkboxes with pure CSS</title>
  <link>https://lea.verou.me/2013/03/ios-6-switch-style-checkboxes-with-pure-css/</link>
  <pubDate>Sun, 17 May 2026 02:05:13 +0200</pubDate>
  <description>I recently found myself looking at the Tools switch in Espresso: Not because I was going to use it (I rarely do), but because I started wondering what would be the best way to replicate this effect in CSS. I set on to create something that adhered to the following rules: It should be keyboard accessible It should work in as many browsers as possible and degrade gracefully to a plain checkbox in the rest It shouldn’t depend on pseudo-elements in replaced elements (such as checkboxes), since that’s non-standard so not very dependable It shouldn’t require any extra HTML elements It shouldn’t use JS, unless perhaps to generate HTML that could be written by hand if the author wishes to do so. Why you may ask? Some of them are good practices in general, and the rest make it easier to reuse the component (and they made it more challenging too!). The best idea I came up with was to use a radial gradient for the knob and animate its background-position. All that on a checkbox. After a lot of tweaking, I settled on something that looked decent (although not as good as the Espresso one) in the browser I was using (Chrome) and went ahead to test it in others. The result was disappointing: I had forgotten that not all browsers allow that kind of customization on checkboxes. And who can blame them? This is what happens when you’re wandering in Undefined Behavior Land. They are not violating any spec, because there is no spec mandating or forbidding checkboxes from being stylable with CSS and to what extent, so every browser does its thing there. Here you can see my failed attempt, which only works as intended in Chrome: I realized I had to lift one of the restrictions if I wanted to solve this, so I picked the 4th (no extra HTML elements), as it was the least important one. I could have done it as a pseudoelements on s, but I decided to use a instead, for maximum flexibility. The is added through script in the Dabblet below, but it could be added by hand instead. To get around the limitation of pseudo-elements not being animatable in current and older versions of WebKit, I animate the padding of the instead. And then I thought, why not make iOS-style switches? Even more challenging! I turned on my iPhone and tried to replicate the look. Adding the ON/OFF text was very painful, as it needs to both animate and be styled differently for “ON” and “OFF”. Eventually, I ended up doing it with text-indent in such a way that it depends on the knob’s position, so that when the knob animates, the text moves too. Another challenge with this was the different backgrounds. Changing the background color upon :checked was not enough, since it needs to slide as well, not just abruptly change or fade in. I ended up doing it with a gradient and animating its background-position. Naturally, this makes it not look as good in IE9. So, without further ado, here is the final result: Yes, I know there are other efforts on the web to replicate this effect with pure CSS, but none of them seems to come as close to the original, without images and with such minimal HTML. Why bother, you may ask? Well, it was a fun pastime during SXSW breaks or sessions that turned out to be less interesting than expected or in the plane on the way home. Besides, I think that it could be useful in some cases, perhaps if the styling is tweaked to not resemble iOS too obviously or maybe in iOS app mockups or something. Enjoy! Credits to Ryan Seddon for paving the way for custom form elements through CSS, a couple years ago</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Use MathML today, with CSS fallback!</title>
  <link>https://lea.verou.me/2013/03/use-mathml-today-with-css-fallback/</link>
  <pubDate>Sun, 17 May 2026 02:05:13 +0200</pubDate>
  <description>These days, Im working on the slides for my next talk , The humble border-radius. It will be about how much work is put into CSS features that superficially look as simple as border-radius, as well as what advances are in store for it in CSS Backgrounds &amp; Borders 4 (of which Im an editor). It will be fantastic and you should come, but this post is not about my talk. As you may know, my slides are made with HTML, CSS &amp; JavaScript . At some point, I wanted to insert an equation to show how border-top-left-radius (as an example) shrinks proportionally when the sum of radii on the top side exceeds the width of the element. I dont like LaTeX because it produces bitmap images that dont scale and is inaccessible. The obvious open standard to use was MathML , and it can even be directly embedded in HTML5 without all the XML cruft, just like SVG. I had never written MathML before, but after a bit of reading and poking around existing samples, I managed to write the following MathML code: r top-left = min ( r top-left , width r top-left r top-left + r top-right ) I was very proud of myself. My first MathML equation! Its actually pretty simple when you get the hang of it: is for identifiers, for operators and those are used everywhere. For more complex stuff, theres for fractions (along with to denote the rows), for square roots and so on. It looked very nice on Firefox, especially after I applied Cambria Math to it instead of the default Times-ish font: However, I soon realized that as awesome as MathML might be, not not all browsers had seen the light . IE10 and Chrome are the most notable offenders. It looked like an unreadable mess in Chrome: There are libraries to make it work cross-browser, the most popular of which is MathJax . However, this was pretty big for my needs, I just wanted one simple equation in one goddamn slide. It would be like using a chainsaw to cut a slice of bread! The solution I decided to go with was to use Modernizr to detect MathML support, since apparently its not simple at all . Then, I used the .no-mathml class in conjunction with selectors that target the MathML elements, to mimic proper styling with simple CSS. Its not a complete CSS library by any means, I just covered what I needed for that particular equation and tried to write it in a generic way, so that if I need it in future equations, I only have to add rules. Heres a screenshot of the result in Chrome: It doesnt look as good as Firefox, but its decent. You can see the CSS rules I used in the following Dabblet: Obviously its not a complete MathML-to-CSS library, if one is even possible, but it works well for my use case. If I have to use more MathML features, Id write more CSS rules. The intention of this post is not to provide a CSS framework to use as a MathML fallback, but to show you a solution you could adapt to your needs. Hope it helps!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>iOS 6 switch style checkboxes with pure CSS</title>
  <link>https://lea.verou.me/2013/03/ios-6-switch-style-checkboxes-with-pure-css/</link>
  <pubDate>Sun, 17 May 2026 02:05:13 +0200</pubDate>
  <description>I recently found myself looking at the Tools switch in Espresso: Not because I was going to use it (I rarely do), but because I started wondering what would be the best way to replicate this effect in CSS. I set on to create something that adhered to the following rules: It should be keyboard accessible It should work in as many browsers as possible and degrade gracefully to a plain checkbox in the rest It shouldnt depend on pseudo-elements in replaced elements (such as checkboxes), since thats non-standard so not very dependable It shouldnt require any extra HTML elements It shouldnt use JS, unless perhaps to generate HTML that could be written by hand if the author wishes to do so. Why you may ask? Some of them are good practices in general, and the rest make it easier to reuse the component (and they made it more challenging too!). The best idea I came up with was to use a radial gradient for the knob and animate its background-position. All that on a checkbox. After a lot of tweaking, I settled on something that looked decent (although not as good as the Espresso one) in the browser I was using (Chrome) and went ahead to test it in others. The result was disappointing: I had forgotten that not all browsers allow that kind of customization on checkboxes. And who can blame them? This is what happens when youre wandering in Undefined Behavior Land. They are not violating any spec, because there is no spec mandating or forbidding checkboxes from being stylable with CSS and to what extent, so every browser does its thing there. Here you can see my failed attempt, which only works as intended in Chrome: I realized I had to lift one of the restrictions if I wanted to solve this, so I picked the 4th (no extra HTML elements), as it was the least important one. I could have done it as a pseudoelements on s, but I decided to use a instead, for maximum flexibility. The is added through script in the Dabblet below, but it could be added by hand instead. To get around the limitation of pseudo-elements not being animatable in current and older versions of WebKit, I animate the padding of the instead. And then I thought, why not make iOS-style switches? Even more challenging! I turned on my iPhone and tried to replicate the look. Adding the ON/OFF text was very painful, as it needs to both animate and be styled differently for ON and OFF. Eventually, I ended up doing it with text-indent in such a way that it depends on the knobs position, so that when the knob animates, the text moves too. Another challenge with this was the different backgrounds. Changing the background color upon :checked was not enough, since it needs to slide as well, not just abruptly change or fade in. I ended up doing it with a gradient and animating its background-position. Naturally, this makes it not look as good in IE9. So, without further ado, here is the final result: Yes, I know there are other efforts on the web to replicate this effect with pure CSS, but none of them seems to come as close to the original, without images and with such minimal HTML. Why bother, you may ask? Well, it was a fun pastime during SXSW breaks or sessions that turned out to be less interesting than expected or in the plane on the way home. Besides, I think that it could be useful in some cases, perhaps if the styling is tweaked to not resemble iOS too obviously or maybe in iOS app mockups or something. Enjoy! Credits to Ryan Seddon for paving the way for custom form elements through CSS, a couple years ago</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>W3Conf in San Francisco, February 21-22</title>
  <link>https://lea.verou.me/2013/01/w3conf-in-san-francisco-february-21-22/</link>
  <pubDate>Sun, 17 May 2026 02:05:12 +0200</pubDate>
  <description>You might have heard about W3Conf , W3C ’s conference for web designers and developers. This year, I have the pleasure of not only speaking there but also organizing it, along with Doug Schepers and designing the website for it. Alongside with yours truly, it features an excellent lineup of amazing speakers like Eric Meyer , Alexis Deveria of caniuse.com fame, Nicolas Gallagher and many others. You can use coupon code VEROU to get $100 off the already affordable Early Bird price of $300 . But hurry up, cause Early Bird prices are only valid until January 31st ! Hope to see you there!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>W3Conf in San Francisco, February 21-22</title>
  <link>https://lea.verou.me/2013/01/w3conf-in-san-francisco-february-21-22/</link>
  <pubDate>Sun, 17 May 2026 02:05:12 +0200</pubDate>
  <description>You might have heard about W3Conf , W3C s conference for web designers and developers. This year, I have the pleasure of not only speaking there but also organizing it, along with Doug Schepers and designing the website for it. Alongside with yours truly, it features an excellent lineup of amazing speakers like Eric Meyer , Alexis Deveria of caniuse.com fame, Nicolas Gallagher and many others. You can use coupon code VEROU to get $100 off the already affordable Early Bird price of $300 . But hurry up, cause Early Bird prices are only valid until January 31st ! Hope to see you there!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>One year of pastries</title>
  <link>https://lea.verou.me/2012/12/one-year-of-pastries/</link>
  <pubDate>Sun, 17 May 2026 02:05:11 +0200</pubDate>
  <description>Last September, I was approached by Alex Duloz , who invited me to take part in his ambitious new venture, The Pastry Box Project . Its goal was to gather 30 people (“bakers”) every year who are influential in their field and ask them to share twelve thoughts — one per month. For 2012, that field would be the Web. I was honored by the invitation and accepted without a second thought (no pun intended). The project was quite successful and recently we all ( almost ) agreed for The Pastry Box Project to become a book, whose profits will be donated to charity. The initial goal of the project was to gather thoughts somehow related to the bakers’ work. Although many stuck to that topic, for many others it quickly drifted away from that, with them often sending thoughts that were general musings about their lives or life in general. For me …well lets just say I was never good at sticking to the topic at hand. ;) The Pastry Box showed me that I want a personal blog so I made one today . I will still publish personal stuff here, as long as it’s even remotely web-related, so not much will change. However, my interests range to more than the Web, so I will now have another medium to express myself in. :) Since 2012 is now over, I decided to gather all my “pastries” and publish them in two blog posts: I will post the more techy/professional ones below and the more general/personal ones in my personal blog . Since most of them were somewhere in the middle, it wasn’t easy to pick which ones to publish where. I figured the best solution is to allow for some overlap and publish most of them in both blogs. January Often people ask me how I come up with the new ideas I publish. I think my main “differentiator” is that I try not to be restricted by my knowledge about what’s possible and what is not. I first think about what I want to make (for example “I want to do a rating widget with pure CSS”) and then I investigate how it could be done. And I don’t give up easily. Sometimes it even takes months having the question in the back of my head before I come up with a solution. People push the boundaries of what’s achievable with web technologies every day. Do you want to be one of them, or do you want to be stuck repeating what’s been done over and over again until you get sick of it? Don’t be afraid to try new things. If a voice inside you screams “That isn’t possible!”, ignore it. In most cases, this voice is wrong. February You may catch more flies with honey than with vinegar, but you catch even more with a little audacity. Being polite is a good rule of thumb, but like everything, it also needs moderation. Don’t say “share my content pleeeeeeaaaase”. It makes people think your content isn’t worthy of sharing if you have to grovel. In a long email, don’t write a paragraph apologizing for its length (true story!). Being overly polite when meeting someone, categorizes yourself as inferior in the other person’s subconscious. Treat yourself with the respect and admiration you expect from other people. If you don’t think highly of yourself, nobody will. When meeting someone you admire, treat them as an equal and they’re more likely to do the same. However, be careful not to cross the fine distinction between treating yourself with respect and being a cocky jackass. Treat others as equals, not as inferiors, otherwise your attitude will get you nowhere — and will piss everyone off along the way. March You can get quite far by putting cool stuff out there and expecting everything to come to you. Yes, you will eventually get job offers, conference invitations and various distinctions. However, sometimes, just asking will get you what you want much faster. I used to avoid asking like the plague, and thought that if my work is good enough, what I want will naturally come to me. Which makes sense, to a certain extent: When someone keeps asking for stuff all the time, you can’t help but think that they merely see you as means to an end. However, when you really want something, it never hurts to approach it yourself. Lately, I’ve been experiencing how much easier this makes things, and I’d strongly recommend you try it too. Turns out that quite often you don’t have what you want not because you aren’t good enough, but because the parties involved have no idea you’re interested. April The best argument against conventional wisdom is the fate of everyone following it. If you aspire beyond mediocrity, conventional wisdom is recipe for failure. Think out of the box. What can you do to achieve your goals, that others are not already doing? The least popular paths are the most successful. The trick isn’t doing better than the others, it’s minimizing the number of “others”. Find unexplored territory and make it yours. It’s much easier than trying to claim your stake on someone else’s land. May I never make long-term plans. Life is an unpredictable adventure. Concrete plans restrict this amazing journey. Stressing over a series of mental checkboxes you need to check until a certain date shifts your focus away from making awesomeness. I have long-term dreams instead, and they are all the compass I need. They give me the drive to constantly strive to improve, while still allowing room for surprises. I learned to trust chaos, and so far, I was never disappointed. June Contrary to popular belief, the defining characteristic of a good professional, in any discipline, is not the ability to blurt out good ideas off the top of their head. It’s perseverance and not being easily satisfied. Where the others would stop, they keep going. For example, when writing CSS, they won’t stop after they’ve achieved a certain style. They will also try to make it more flexible, more maintainable, simpler. Next week, try this: When you’re about to give up and proclaim that something is “done”, try to spend five more minutes on the task, thinking how you can improve it further, how to make it more elegant. I think it will help you be much more satisfied and proud of your work. July Before you start complaining about what you don’t like in CSS, HTML or JavaScript, ask yourself: How would I do it better? Sometimes, the things that bother us are just unavoidably subpar solutions to very hard problems. It sounds obvious, but many people I’ve spoken with get a completely new perspective when they ask themselves this question. Also, there are many other factors affecting design choices, beyond syntactical elegance and ease of understanding. For example, making implementations easier, maintaining backwards compatibility or matching what browsers already do. Sometimes that “obvious better solution” is just not possible in practice. August We all teach from time to time, whether it’s explaining something to a colleague, writing a blog post about the cool CSS technique we discovered, or giving a technical talk. If you are serious about becoming better at it, I’d strongly recommend reading up on psychology and neuroscience. If you don’t have the time to, here’s one fact that I’ve found most useful: Humans have incredibly impressive pattern recognition skills. We use them in pretty much everything we do, from learning our native language as kids, to escaping predators in the wild. How does that help you teach more effectively? In one word: Examples, examples, examples. No matter how good you are at explaining the rules, nothing beats a few good examples of their application in practice. Our abstract thinking is not nearly as good as our pattern recognition skills. However, don’t be fooled into thinking that theory is useless. Often, multiple explanations fit a given example. The theory helps us pick the one that fits, which might not be the one we initially recognized. I’ve found that this principle applies to pretty much everything I’ve taught or have been taught, from mathematics to natural and programming languages. You can forget the theory, but you should never forget the examples. September People often think it’s hard to change my mind, that I’m too fixated on my own opinions. The reason I give this impression is that I will fiercely defend them. However, I will only do so until I see compelling arguments for the other side. I always try to keep an open mind to being wrong, and it has only made me better. In the past few months I’ve been witnessing myself slowly change my views regarding yet another major life issue: The place I want to live in. Moving to the US has been a life goal for me ever since I first visited, almost fifteen years ago. However, as I spend more time there and get closer to moving, I’ve started noticing things that I don’t like so much. I’ve tried to ignore them, but they keep being there, giving me the finger like dead pixels on a brand new screen. I might go forward with it anyway, or I might pick another country, but this is yet another experience that has taught me to avoid being dogmatic. We are all, and should be, subject to change. Whoever insists in their rigid convictions reminds me of software whose bugs never get fixed. You are the only maintainer of that software. Be vigilant enough to discover and fix your own bugs. Be open-minded enough to listen to other people’s bug reports about it. Most people forget to do this after a certain age. They become so arrogant that they think they don’t have any more bugs to fix, or so insecure that they believe they can’t fix any more. That’s the turning point where the years that pass by start to become “aging”, instead of “growing up”. Aging doesn’t have to do with how long you’re on this planet, it has to do with giving up on yourself. To stop being subject to change is to start being stagnant. October These days, we almost all unequivocally embrace graceful degradation and progressive enhancement. It’s the extent that people disagree on, since everyone has a different definition of what is “graceful” and what is “enhancement”. Is a solid color an acceptable fallback for a pattern? What if your lightbox has no overlay? What if your stripes become a solid color? What if your transitions are not there? What if your code has no syntax highlighting? That’s the true challenge: How different can they look? Is it sufficient if the content is accessible in IE8 or does it also have to be pretty? How pretty? Those are the questions you need to agree on with your team to ensure you’re all on the same page. An agreement on the basic premise that websites don’t have to look the same in every browser is far from enough. Graceful degradation is not black &amp; white, it’s a spectrum. You need to find where you lie on that spectrum and where your colleagues lie on it too, otherwise expect a lot of tension every time decisions need to be made.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>One year of pastries</title>
  <link>https://lea.verou.me/2012/12/one-year-of-pastries/</link>
  <pubDate>Sun, 17 May 2026 02:05:11 +0200</pubDate>
  <description>Last September, I was approached by Alex Duloz , who invited me to take part in his ambitious new venture, The Pastry Box Project . Its goal was to gather 30 people (bakers) every year who are influential in their field and ask them to share twelve thoughts one per month. For 2012, that field would be the Web. I was honored by the invitation and accepted without a second thought (no pun intended). The project was quite successful and recently we all ( almost ) agreed for The Pastry Box Project to become a book, whose profits will be donated to charity. The initial goal of the project was to gather thoughts somehow related to the bakers work. Although many stuck to that topic, for many others it quickly drifted away from that, with them often sending thoughts that were general musings about their lives or life in general. For me well lets just say I was never good at sticking to the topic at hand. ;) The Pastry Box showed me that I want a personal blog so I made one today . I will still publish personal stuff here, as long as its even remotely web-related, so not much will change. However, my interests range to more than the Web, so I will now have another medium to express myself in. :) Since 2012 is now over, I decided to gather all my pastries and publish them in two blog posts: I will post the more techy/professional ones below and the more general/personal ones in my personal blog . Since most of them were somewhere in the middle, it wasnt easy to pick which ones to publish where. I figured the best solution is to allow for some overlap and publish most of them in both blogs. January Often people ask me how I come up with the new ideas I publish. I think my main differentiator is that I try not to be restricted by my knowledge about whats possible and what is not. I first think about what I want to make (for example I want to do a rating widget with pure CSS) and then I investigate how it could be done. And I dont give up easily. Sometimes it even takes months having the question in the back of my head before I come up with a solution. People push the boundaries of whats achievable with web technologies every day. Do you want to be one of them, or do you want to be stuck repeating whats been done over and over again until you get sick of it? Dont be afraid to try new things. If a voice inside you screams That isnt possible!, ignore it. In most cases, this voice is wrong. February You may catch more flies with honey than with vinegar, but you catch even more with a little audacity. Being polite is a good rule of thumb, but like everything, it also needs moderation. Dont say share my content pleeeeeeaaaase. It makes people think your content isnt worthy of sharing if you have to grovel. In a long email, dont write a paragraph apologizing for its length (true story!). Being overly polite when meeting someone, categorizes yourself as inferior in the other persons subconscious. Treat yourself with the respect and admiration you expect from other people. If you dont think highly of yourself, nobody will. When meeting someone you admire, treat them as an equal and theyre more likely to do the same. However, be careful not to cross the fine distinction between treating yourself with respect and being a cocky jackass. Treat others as equals, not as inferiors, otherwise your attitude will get you nowhere and will piss everyone off along the way. March You can get quite far by putting cool stuff out there and expecting everything to come to you. Yes, you will eventually get job offers, conference invitations and various distinctions. However, sometimes, just asking will get you what you want much faster. I used to avoid asking like the plague, and thought that if my work is good enough, what I want will naturally come to me. Which makes sense, to a certain extent: When someone keeps asking for stuff all the time, you cant help but think that they merely see you as means to an end. However, when you really want something, it never hurts to approach it yourself. Lately, Ive been experiencing how much easier this makes things, and Id strongly recommend you try it too. Turns out that quite often you dont have what you want not because you arent good enough, but because the parties involved have no idea youre interested. April The best argument against conventional wisdom is the fate of everyone following it. If you aspire beyond mediocrity, conventional wisdom is recipe for failure. Think out of the box. What can you do to achieve your goals, that others are not already doing? The least popular paths are the most successful. The trick isnt doing better than the others, its minimizing the number of others. Find unexplored territory and make it yours. Its much easier than trying to claim your stake on someone elses land. May I never make long-term plans. Life is an unpredictable adventure. Concrete plans restrict this amazing journey. Stressing over a series of mental checkboxes you need to check until a certain date shifts your focus away from making awesomeness. I have long-term dreams instead, and they are all the compass I need. They give me the drive to constantly strive to improve, while still allowing room for surprises. I learned to trust chaos, and so far, I was never disappointed. June Contrary to popular belief, the defining characteristic of a good professional, in any discipline, is not the ability to blurt out good ideas off the top of their head. Its perseverance and not being easily satisfied. Where the others would stop, they keep going. For example, when writing CSS, they wont stop after theyve achieved a certain style. They will also try to make it more flexible, more maintainable, simpler. Next week, try this: When youre about to give up and proclaim that something is done, try to spend five more minutes on the task, thinking how you can improve it further, how to make it more elegant. I think it will help you be much more satisfied and proud of your work. July Before you start complaining about what you dont like in CSS, HTML or JavaScript, ask yourself: How would I do it better? Sometimes, the things that bother us are just unavoidably subpar solutions to very hard problems. It sounds obvious, but many people Ive spoken with get a completely new perspective when they ask themselves this question. Also, there are many other factors affecting design choices, beyond syntactical elegance and ease of understanding. For example, making implementations easier, maintaining backwards compatibility or matching what browsers already do. Sometimes that obvious better solution is just not possible in practice. August We all teach from time to time, whether its explaining something to a colleague, writing a blog post about the cool CSS technique we discovered, or giving a technical talk. If you are serious about becoming better at it, Id strongly recommend reading up on psychology and neuroscience. If you dont have the time to, heres one fact that Ive found most useful: Humans have incredibly impressive pattern recognition skills. We use them in pretty much everything we do, from learning our native language as kids, to escaping predators in the wild. How does that help you teach more effectively? In one word: Examples, examples, examples. No matter how good you are at explaining the rules, nothing beats a few good examples of their application in practice. Our abstract thinking is not nearly as good as our pattern recognition skills. However, dont be fooled into thinking that theory is useless. Often, multiple explanations fit a given example. The theory helps us pick the one that fits, which might not be the one we initially recognized. Ive found that this principle applies to pretty much everything Ive taught or have been taught, from mathematics to natural and programming languages. You can forget the theory, but you should never forget the examples. September People often think its hard to change my mind, that Im too fixated on my own opinions. The reason I give this impression is that I will fiercely defend them. However, I will only do so until I see compelling arguments for the other side. I always try to keep an open mind to being wrong, and it has only made me better. In the past few months Ive been witnessing myself slowly change my views regarding yet another major life issue: The place I want to live in. Moving to the US has been a life goal for me ever since I first visited, almost fifteen years ago. However, as I spend more time there and get closer to moving, Ive started noticing things that I dont like so much. Ive tried to ignore them, but they keep being there, giving me the finger like dead pixels on a brand new screen. I might go forward with it anyway, or I might pick another country, but this is yet another experience that has taught me to avoid being dogmatic. We are all, and should be, subject to change. Whoever insists in their rigid convictions reminds me of software whose bugs never get fixed. You are the only maintainer of that software. Be vigilant enough to discover and fix your own bugs. Be open-minded enough to listen to other peoples bug reports about it. Most people forget to do this after a certain age. They become so arrogant that they think they dont have any more bugs to fix, or so insecure that they believe they cant fix any more. Thats the turning point where the years that pass by start to become aging, instead of growing up. Aging doesnt have to do with how long youre on this planet, it has to do with giving up on yourself. To stop being subject to change is to start being stagnant. October These days, we almost all unequivocally embrace graceful degradation and progressive enhancement. Its the extent that people disagree on, since everyone has a different definition of what is graceful and what is enhancement. Is a solid color an acceptable fallback for a pattern? What if your lightbox has no overlay? What if your stripes become a solid color? What if your transitions are not there? What if your code has no syntax highlighting? Thats the true challenge: How different can they look? Is it sufficient if the content is accessible in IE8 or does it also have to be pretty? How pretty? Those are the questions you need to agree on with your team to ensure youre all on the same page. An agreement on the basic premise that websites dont have to look the same in every browser is far from enough. Graceful degradation is not black &amp; white, its a spectrum. You need to find where you lie on that spectrum and where your colleagues lie on it too, otherwise expect a lot of tension every time decisions need to be made.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS Animations with only one keyframe</title>
  <link>https://lea.verou.me/2012/12/animations-with-one-keyframe/</link>
  <pubDate>Sun, 17 May 2026 02:05:10 +0200</pubDate>
  <description>This is a very quick tip, about a pet peeve of mine in almost every CSS animation I see. As you may know, I’m a sucker for reducing the amount of code (as long as it remains human readable of course). I demonstrated a very similar example in my “CSS in the 4th dimension” talk, but I recently realized I never blogged about it (or seen anyone else do so). Lets assume you have a simple animation of a pounding heart, like so: @keyframes pound { from { transform: none; } 50% { transform: scale(1.4); } to { transform: none; } } .heart { /* ... */ animation: pound .5s infinite; } You can see the problem already: the shrunk heart state is repeated twice in the keyframes ( from and to ). You probably know you can combine them into one rule, like so: @keyframes pound { from, to { transform: none; } 50% { transform: scale(1.4); } } What many don’t know, is that you don’t need these two keyframes at all, since they basically replicate the same state as the one in the .heart rule. To quote the CSS Animations spec : If a 0% or “from” keyframe is not specified, then the user agent constructs a 0% keyframe using the computed values of the properties being animated. If a 100% or “to” keyframe is not specified, then the user agent constructs a 100% keyframe using the computed values of the properties being animated. Therefore, the code could actually be as simple as: @keyframes pound { 50% { transform: scale(1.4); } } This trick is very useful for providing fallbacks that are the same as the first or last keyframe, without having to repeat them in the @keyframes rule. Of course it doesn’t only apply to animations where you only have one keyframe beyond from and/or to . You can omit the from and to keyframes in every animation, when you want them to be the same as the styles that are applied to the element anyway. Of course, to make this particular animation appear more natural, it would be much more wise to do something like this, still with only one keyframe (the from state is dynamically generated by the browser): @keyframes pound { to { transform: scale(1.4); } } .heart { /* ... */ animation: pound .25s infinite alternate; } which just reverses every even iteration, instead of trying to have both states (shrinking and growing) in the animation. The reason this looks more natural is that animation-direction: alternate; (which is what the alternate keyword does in the animation shorthand) also reverses the timing (easing) function for the reversed iterations. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Lots of improvements coming to dabblet</title>
  <link>https://lea.verou.me/2012/11/lots-of-improvements-coming-to-dabblet/</link>
  <pubDate>Sun, 17 May 2026 02:05:10 +0200</pubDate>
  <description>I posted about this in both the WebPlatform.org blog and Dabblet’s blog , but I thought it might be interesting to many readers of this blog as well: As many of you probably know, I ’ve started working for W3C Developer Relations since this August. Half of my time is devoted to WebPlatform.org , a very promising project to document the web with the help of all major players, in a vendor-neutral way. Even before I joined W3C , we discussed using a hosted, customized version of dabblet in WebPlatform.org , as a platform for live code examples. I recently started working towards making this happen. A lot of changes and improvements need to be made to achieve this, but the good news is, most of these will be pushed to dabblet.com as well! In a nutshell, this is what I’m currently working on: Adding JavaScript support — This will be a challenge UX-wise, as it shouldn’t run on every keystroke, like the HTML and CSS, but it should run on startup and it should be straight-forward how to get it to run. Perhaps it will also run after a significant pause in typing. Dabblets that are not stored in Github, but get their data through POST requests. Improving cross-browser support Strengthening security Integrating Prism . Dabblet’s syntax highlighter might have been Prism’s precursor, but currently Prism has solved many of its bugs, and these fixes need to be pushed to dabblet at some point. General bug fixing These will probably be gradually rolled out in dabblet.com and tested by the community, before we integrate dabblet into WebPlatform.org . If a new feature is significant enough, there will be a new blog post about it here, but don’t expect blog posts about bugfixes. I’m really excited to see dabblet flourish, and I believe you will be too, once these updates are out!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS Animations with only one keyframe</title>
  <link>https://lea.verou.me/2012/12/animations-with-one-keyframe/</link>
  <pubDate>Sun, 17 May 2026 02:05:10 +0200</pubDate>
  <description>This is a very quick tip, about a pet peeve of mine in almost every CSS animation I see. As you may know, Im a sucker for reducing the amount of code (as long as it remains human readable of course). I demonstrated a very similar example in my CSS in the 4th dimension talk, but I recently realized I never blogged about it (or seen anyone else do so). Lets assume you have a simple animation of a pounding heart, like so: @keyframes pound { from { transform: none; } 50% { transform: scale(1.4); } to { transform: none; } } .heart { /* ... */ animation: pound .5s infinite; } You can see the problem already: the shrunk heart state is repeated twice in the keyframes ( from and to ). You probably know you can combine them into one rule, like so: @keyframes pound { from, to { transform: none; } 50% { transform: scale(1.4); } } What many dont know, is that you dont need these two keyframes at all, since they basically replicate the same state as the one in the .heart rule. To quote the CSS Animations spec : If a 0% or from keyframe is not specified, then the user agent constructs a 0% keyframe using the computed values of the properties being animated. If a 100% or to keyframe is not specified, then the user agent constructs a 100% keyframe using the computed values of the properties being animated. Therefore, the code could actually be as simple as: @keyframes pound { 50% { transform: scale(1.4); } } This trick is very useful for providing fallbacks that are the same as the first or last keyframe, without having to repeat them in the @keyframes rule. Of course it doesnt only apply to animations where you only have one keyframe beyond from and/or to . You can omit the from and to keyframes in every animation, when you want them to be the same as the styles that are applied to the element anyway. Of course, to make this particular animation appear more natural, it would be much more wise to do something like this, still with only one keyframe (the from state is dynamically generated by the browser): @keyframes pound { to { transform: scale(1.4); } } .heart { /* ... */ animation: pound .25s infinite alternate; } which just reverses every even iteration, instead of trying to have both states (shrinking and growing) in the animation. The reason this looks more natural is that animation-direction: alternate; (which is what the alternate keyword does in the animation shorthand) also reverses the timing (easing) function for the reversed iterations. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Lots of improvements coming to dabblet</title>
  <link>https://lea.verou.me/2012/11/lots-of-improvements-coming-to-dabblet/</link>
  <pubDate>Sun, 17 May 2026 02:05:10 +0200</pubDate>
  <description>I posted about this in both the WebPlatform.org blog and Dabblets blog , but I thought it might be interesting to many readers of this blog as well: As many of you probably know, I ve started working for W3C Developer Relations since this August. Half of my time is devoted to WebPlatform.org , a very promising project to document the web with the help of all major players, in a vendor-neutral way. Even before I joined W3C , we discussed using a hosted, customized version of dabblet in WebPlatform.org , as a platform for live code examples. I recently started working towards making this happen. A lot of changes and improvements need to be made to achieve this, but the good news is, most of these will be pushed to dabblet.com as well! In a nutshell, this is what Im currently working on: Adding JavaScript support This will be a challenge UX-wise, as it shouldnt run on every keystroke, like the HTML and CSS, but it should run on startup and it should be straight-forward how to get it to run. Perhaps it will also run after a significant pause in typing. Dabblets that are not stored in Github, but get their data through POST requests. Improving cross-browser support Strengthening security Integrating Prism . Dabblets syntax highlighter might have been Prisms precursor, but currently Prism has solved many of its bugs, and these fixes need to be pushed to dabblet at some point. General bug fixing These will probably be gradually rolled out in dabblet.com and tested by the community, before we integrate dabblet into WebPlatform.org . If a new feature is significant enough, there will be a new blog post about it here, but dont expect blog posts about bugfixes. Im really excited to see dabblet flourish, and I believe you will be too, once these updates are out!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Easy color contrast ratios</title>
  <link>https://lea.verou.me/2012/10/easy-color-contrast-ratios/</link>
  <pubDate>Sun, 17 May 2026 02:05:09 +0200</pubDate>
  <description>I was always interested in accessibility, but I never had to comply with any guidelines before. At W3C , accessibility is considered very important, so everything we make needs to pass WCAG 2.0 AA level. Therefore, I found myself calculating color contrast ratios very frequently. It was a very enlightening experience. I used to think that WCAG-mandated contrast ratios were too restrictive and basically tried to force you to use black and white, a sentiment shared by many designers I’ve spoken to. Surprisingly, in practice, I found that in most cases they are very reasonable: When a color combination doesn’t pass WCAG, it usually *is* hard to read. After all, the possible range for a contrast ratio is 1-21 but only ratios lower than 3 don’t pass WCAG AA (4.5 if you have smaller, non-bold text). So, effectively 90% of combinations will pass (82.5% for smaller, non-bold text). There are plenty of tools out there for this. However, I found that my workflow for checking a contrast ratio with them was far from ideal. I had to convert my CSS colors to hex notation (which I don’t often use myself anymore), check the contrast ratio, then adjust the colors as necessary, covert again etc. In addition, I had to adjust the lightness of the colors with a blindfold, without being able to see the difference my adjustments would make to the contrast ratio. When using semi-transparent colors, it was even worse: Since WCAG only describes an algorithm for opaque colors, all contrast tools only expect that. So, I had to calculate the resulting opaque colors after alpha blending had taken place. After doing that for a few days, I got so fed up that I decided to make my own tool . In addition, I discovered that there was no documented way of calculating the contrast ratio range that can be produced with a semi-transparent background, so I came up with an algorithm (after many successive failures to find the range intuitively), published it in the w3c-wai-ig mailing list and used the algorithm in my app, effectively making it the first one that can accept semi-transparent colors. If your math is less rusty than mine, I’d appreciate any feedback on my reasoning there. Below is a list of features that make this tool unique for calculating color contrast ratios: Accepts any CSS color the browser does, not just hex colors. To do this, it defers parsing of the color to the browser, and queries the computed style, which is always rgb() or rgba() with 0-255 ranges which be parsed much more easily than the multitude of different formats than modern browsers accept (and the even more that are coming in the future). Updates as you type, when what you’ve typed can be parsed as a valid CSS color. Accepts semi transparent colors. For semi-transparent backgrounds, the contrast ratio is presented with an error margin, since it can vary depending on the backdrop. In that case, the result circle will not have a solid background, but a visualization of the different possible results and their likelihood (see screenshot ). You can share your results by sharing the URL. The URL hashes have a reasonable structure of the form #foreground-on-background, e.g. #black-on-yellow so you can even adjust the URL as a form of input. You can adjust the color by incrementing or decrementing its components with the keyboard arrow keys until you get the contrast right. This is achieved by including my Incrementable library. Browser support is IE10 and modern versions of Firefox, Safari, Chrome, Opera. Basic support for IE9. No responsive version yet, sorry (but you can always send pull requests !) Save the link: contrast-ratio.com Edit 2022: Link updated to reflect current one. Original link was leaverou.github.com/contrast-ratio</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Easy color contrast ratios</title>
  <link>https://lea.verou.me/2012/10/easy-color-contrast-ratios/</link>
  <pubDate>Sun, 17 May 2026 02:05:09 +0200</pubDate>
  <description>I was always interested in accessibility, but I never had to comply with any guidelines before. At W3C , accessibility is considered very important, so everything we make needs to pass WCAG 2.0 AA level. Therefore, I found myself calculating color contrast ratios very frequently. It was a very enlightening experience. I used to think that WCAG-mandated contrast ratios were too restrictive and basically tried to force you to use black and white, a sentiment shared by many designers Ive spoken to. Surprisingly, in practice, I found that in most cases they are very reasonable: When a color combination doesnt pass WCAG, it usually *is* hard to read. After all, the possible range for a contrast ratio is 1-21 but only ratios lower than 3 dont pass WCAG AA (4.5 if you have smaller, non-bold text). So, effectively 90% of combinations will pass (82.5% for smaller, non-bold text). There are plenty of tools out there for this. However, I found that my workflow for checking a contrast ratio with them was far from ideal. I had to convert my CSS colors to hex notation (which I dont often use myself anymore), check the contrast ratio, then adjust the colors as necessary, covert again etc. In addition, I had to adjust the lightness of the colors with a blindfold, without being able to see the difference my adjustments would make to the contrast ratio. When using semi-transparent colors, it was even worse: Since WCAG only describes an algorithm for opaque colors, all contrast tools only expect that. So, I had to calculate the resulting opaque colors after alpha blending had taken place. After doing that for a few days, I got so fed up that I decided to make my own tool . In addition, I discovered that there was no documented way of calculating the contrast ratio range that can be produced with a semi-transparent background, so I came up with an algorithm (after many successive failures to find the range intuitively), published it in the w3c-wai-ig mailing list and used the algorithm in my app, effectively making it the first one that can accept semi-transparent colors. If your math is less rusty than mine, Id appreciate any feedback on my reasoning there. Below is a list of features that make this tool unique for calculating color contrast ratios: Accepts any CSS color the browser does, not just hex colors. To do this, it defers parsing of the color to the browser, and queries the computed style, which is always rgb() or rgba() with 0-255 ranges which be parsed much more easily than the multitude of different formats than modern browsers accept (and the even more that are coming in the future). Updates as you type, when what youve typed can be parsed as a valid CSS color. Accepts semi transparent colors. For semi-transparent backgrounds, the contrast ratio is presented with an error margin, since it can vary depending on the backdrop. In that case, the result circle will not have a solid background, but a visualization of the different possible results and their likelihood (see screenshot ). You can share your results by sharing the URL. The URL hashes have a reasonable structure of the form #foreground-on-background, e.g. #black-on-yellow so you can even adjust the URL as a form of input. You can adjust the color by incrementing or decrementing its components with the keyboard arrow keys until you get the contrast right. This is achieved by including my Incrementable library. Browser support is IE10 and modern versions of Firefox, Safari, Chrome, Opera. Basic support for IE9. No responsive version yet, sorry (but you can always send pull requests !) Save the link: contrast-ratio.com Edit 2022: Link updated to reflect current one. Original link was leaverou.github.com/contrast-ratio</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Dive deep into CSS3 (and Bolognese!) in Bologna, Italy</title>
  <link>https://lea.verou.me/2012/09/dive-deep-into-css3-in-bologna-italy/</link>
  <pubDate>Sun, 17 May 2026 02:05:08 +0200</pubDate>
  <description>I don’t usually like to advertise my talks or workshops through blog posts, and even though I’ve given a lot, I’ve only posted about less a handful. However, this one is special: It might be my last . Don’t get me wrong: I LOVE giving workshops, teaching people new things and seeing them put them in use right away is fantastic. However, I also find them incredibly exhausting. Speaking for an entire day (or sometimes two!) is pretty much the most tiring thing I’ve done. So, given my new job at W3C , I’m not sure if I will do one again. Of course, it goes without saying that I will still do plenty of talks! :) The last workshop on my schedule is in FromTheFront conference in Bologna, Italy on September 20th (in 7 days!). There are still some spots available, so grab yours while you still can ! It only costs €329.00 . It will be very hands-on, with interactive exercises that help you gain first-hand experience with small but advanced use cases. It will not be your usual CSS3-overview kind of workshop. Instead, we will dive really deep into a handful of CSS3 aspects that I think are most useful for your everyday work. While you’re at it, I’d also recommend getting a conference ticket as well. The line-up has some excellent speakers and it’s only €110 more, so totally worth it! Apologies that my last two blog posts were personal, the next one will be more technical: I have a very useful tool in the pipeline that I’m gonna release soon ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Dive deep into CSS3 (and Bolognese!) in Bologna, Italy</title>
  <link>https://lea.verou.me/2012/09/dive-deep-into-css3-in-bologna-italy/</link>
  <pubDate>Sun, 17 May 2026 02:05:08 +0200</pubDate>
  <description>I dont usually like to advertise my talks or workshops through blog posts, and even though Ive given a lot, Ive only posted about less a handful. However, this one is special: It might be my last . Dont get me wrong: I LOVE giving workshops, teaching people new things and seeing them put them in use right away is fantastic. However, I also find them incredibly exhausting. Speaking for an entire day (or sometimes two!) is pretty much the most tiring thing Ive done. So, given my new job at W3C , Im not sure if I will do one again. Of course, it goes without saying that I will still do plenty of talks! :) The last workshop on my schedule is in FromTheFront conference in Bologna, Italy on September 20th (in 7 days!). There are still some spots available, so grab yours while you still can ! It only costs 329.00 . It will be very hands-on, with interactive exercises that help you gain first-hand experience with small but advanced use cases. It will not be your usual CSS3-overview kind of workshop. Instead, we will dive really deep into a handful of CSS3 aspects that I think are most useful for your everyday work. While youre at it, Id also recommend getting a conference ticket as well. The line-up has some excellent speakers and its only 110 more, so totally worth it! Apologies that my last two blog posts were personal, the next one will be more technical: I have a very useful tool in the pipeline that Im gonna release soon ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>lea＠w3․org</title>
  <link>https://lea.verou.me/2012/08/lea-at-w3-org/</link>
  <pubDate>Sun, 17 May 2026 02:05:07 +0200</pubDate>
  <description>In my recent post describing how I got into web development I wrote that I’m in the verge of some big changes in my life. The main one of them starts tomorrow. As of tomorrow, the above will be my professional email. Yes, you guessed it right; I’m joining the W3C team ! Yes, the same W3C you all know and love :) I decided to title this blog post with it, as I like how a 10 letter string manages to neatly summarize so much. Working at W3C had been a dream of mine ever since I learned what a web standard is. As you probably know if you’ve been following my work, I’m a strong believer in open web standards. Even though proprietary technology might offer some short term benefits, in the long run only open standards can allow the Web to reach its full potential. I’d like to especially thank the two people below (in chronological order). If it wasn’t for them, this dream would have never materialized: Oli Studholme : I still remember our IRC conversation back in January. I was telling him how much I’d love to work for W3C, but “I’m not that good”. He repeatedly encouraged me to contact W3C and express my interest, despite my strong reluctance to do so. “Don’t be like the 15 year old boy that is too shy to ask the girl out” was the argument that finally convinced me. He even asked around to find which person I should contact. Doug Schepers : If it wasn’t for Doug’s heroic efforts, this would have never happened. He believed in me from the start and did everything he could to for this to go through. He spent an incredible amount of time trying to help me, although I repeatedly bombarded him with a cornucopia of silly questions. :) Over the course of these 6 months, he didn’t just become a colleague, but also a friend. Thank you both. I’m deeply grateful. I will be part of the W3C developer relations and web education efforts, working a lot with Doug (aka @shepazu ). In practice, this means: Help developers understand where standards are headed, and solicit early feedback on upcoming features. Help Working Groups understand what developers need. Help plan W3C developer events, including conferences Speaking about open web technologies at conferences and other events Writing articles and documentation about open web technologies Making demos and tools that demonstrate and help authors understand web standards In addition, I will be helping with the design of many W3C-related things, as I will be the only designer at W3C. As you can see I’ll be wearing many hats, which is exactly what I love about this role! I had many tempting offers from big US companies that offered salaries with more digits and a lot of perks. However, my heart wanted W3C and this role was practically tailored to my talents and interests. I’m honored to be a part of W3C and I’m looking forward to helping out. I have to admit I’m also really looking forward to meeting Sir Tim Berners-Lee in person! :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Prism: An awesome new syntax highlighter</title>
  <link>https://lea.verou.me/2012/07/introducing-prism-an-awesome-new-syntax-highlighter/</link>
  <pubDate>Sun, 17 May 2026 02:05:07 +0200</pubDate>
  <description>For the past three weeks, on and off, I’ve been working on releasing Dabblet ’s syntax highlighter as standalone, since many people had requested it. Zachary Forrest suggested the name “Prism” and I liked it so much I decided to go with it, even though there is an abandoned Mozilla project with the same name . I ended up refactoring and extending it so much that I will need to backport it to Dabblet one of these days! This doesn’t mean I bloated it, the core is still a tiny 1.5KB minified &amp; gzipped. It just means it’s more awesome. :) Seriously? The world needs another syntax highlighter? In certain ways, Prism is better than any other syntax highlighter I’ve seen: It’s tiny. The core is only 1.5KB minified &amp; gzipped, which can go up to 2KB with the currently available language definitions (CSS, Markup and JS). But many other highlighters are also small, so read on. It’s incredibly extensible . Not only it’s easy to add new languages (that’s a given with every syntax highlighter these days), but also to extend existing ones . Most importantly, it supports plugins , through hooks strategically placed in its source . There are a few plugins already available , and it’s really easy to write your own . I haven’t seen any other highlighter that supports plugins. It encourages good author practices . Other highlighters encourage or even force you to use elements that are semantically wrong, like (on its own) or . Prism forces you to use the only semantically correct element for marking up code: . On its own for inline code, or inside a for blocks of code. In addition, the code language is declared through the way recommended in the HTML5 draft : through a language-xxxx class. One of its best features: The language definition is inherited . This means that if multiple code snippets have the same language, you can just define it once, in one of their common ancestors. Obviously, if you define a language on the element, you’ve essentially declared a default language for the entire document. It looks good . All three of its existing themes. Most people wanted me to release Dabblet’s highlighter because they found other highlighters (including their themes) quite ugly. It supports parallelism through Web Workers , for better performance in certain cases. It doesn’t force you to use any Prism-specific markup , not even a Prism-specific class name, only standard markup you should be using anyway. So, you can just try it for a while, remove it if you don’t like it and leave no traces behind. However, there are some limitations too: Pre-existing HTML in the code block will be stripped off. However, there are plugins for links and highlighting certain lines . I decided not to support IE8. Prism won’t break on IE8, it just won’t work. I don’t think many people using IE8 and below are able to read code, so I don’t see the point. Enjoy: prismjs.com If you like this project, don’t forget to follow @prismjs on Twitter ! I’ll soon update this blog to use Prism in the code examples as well.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>leaw3org</title>
  <link>https://lea.verou.me/2012/08/lea-at-w3-org/</link>
  <pubDate>Sun, 17 May 2026 02:05:07 +0200</pubDate>
  <description>In my recent post describing how I got into web development I wrote that Im in the verge of some big changes in my life. The main one of them starts tomorrow. As of tomorrow, the above will be my professional email. Yes, you guessed it right; Im joining the W3C team ! Yes, the same W3C you all know and love :) I decided to title this blog post with it, as I like how a 10 letter string manages to neatly summarize so much. Working at W3C had been a dream of mine ever since I learned what a web standard is. As you probably know if youve been following my work, Im a strong believer in open web standards. Even though proprietary technology might offer some short term benefits, in the long run only open standards can allow the Web to reach its full potential. Id like to especially thank the two people below (in chronological order). If it wasnt for them, this dream would have never materialized: Oli Studholme : I still remember our IRC conversation back in January. I was telling him how much Id love to work for W3C, but Im not that good. He repeatedly encouraged me to contact W3C and express my interest, despite my strong reluctance to do so. Dont be like the 15 year old boy that is too shy to ask the girl out was the argument that finally convinced me. He even asked around to find which person I should contact. Doug Schepers : If it wasnt for Dougs heroic efforts, this would have never happened. He believed in me from the start and did everything he could to for this to go through. He spent an incredible amount of time trying to help me, although I repeatedly bombarded him with a cornucopia of silly questions. :) Over the course of these 6 months, he didnt just become a colleague, but also a friend. Thank you both. Im deeply grateful. I will be part of the W3C developer relations and web education efforts, working a lot with Doug (aka @shepazu ). In practice, this means: Help developers understand where standards are headed, and solicit early feedback on upcoming features. Help Working Groups understand what developers need. Help plan W3C developer events, including conferences Speaking about open web technologies at conferences and other events Writing articles and documentation about open web technologies Making demos and tools that demonstrate and help authors understand web standards In addition, I will be helping with the design of many W3C-related things, as I will be the only designer at W3C. As you can see Ill be wearing many hats, which is exactly what I love about this role! I had many tempting offers from big US companies that offered salaries with more digits and a lot of perks. However, my heart wanted W3C and this role was practically tailored to my talents and interests. Im honored to be a part of W3C and Im looking forward to helping out. I have to admit Im also really looking forward to meeting Sir Tim Berners-Lee in person! :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing Prism: An awesome new syntax highlighter</title>
  <link>https://lea.verou.me/2012/07/introducing-prism-an-awesome-new-syntax-highlighter/</link>
  <pubDate>Sun, 17 May 2026 02:05:07 +0200</pubDate>
  <description>For the past three weeks, on and off, Ive been working on releasing Dabblet s syntax highlighter as standalone, since many people had requested it. Zachary Forrest suggested the name Prism and I liked it so much I decided to go with it, even though there is an abandoned Mozilla project with the same name . I ended up refactoring and extending it so much that I will need to backport it to Dabblet one of these days! This doesnt mean I bloated it, the core is still a tiny 1.5KB minified &amp; gzipped. It just means its more awesome. :) Seriously? The world needs another syntax highlighter? In certain ways, Prism is better than any other syntax highlighter Ive seen: Its tiny. The core is only 1.5KB minified &amp; gzipped, which can go up to 2KB with the currently available language definitions (CSS, Markup and JS). But many other highlighters are also small, so read on. Its incredibly extensible . Not only its easy to add new languages (thats a given with every syntax highlighter these days), but also to extend existing ones . Most importantly, it supports plugins , through hooks strategically placed in its source . There are a few plugins already available , and its really easy to write your own . I havent seen any other highlighter that supports plugins. It encourages good author practices . Other highlighters encourage or even force you to use elements that are semantically wrong, like (on its own) or . Prism forces you to use the only semantically correct element for marking up code: . On its own for inline code, or inside a for blocks of code. In addition, the code language is declared through the way recommended in the HTML5 draft : through a language-xxxx class. One of its best features: The language definition is inherited . This means that if multiple code snippets have the same language, you can just define it once, in one of their common ancestors. Obviously, if you define a language on the element, youve essentially declared a default language for the entire document. It looks good . All three of its existing themes. Most people wanted me to release Dabblets highlighter because they found other highlighters (including their themes) quite ugly. It supports parallelism through Web Workers , for better performance in certain cases. It doesnt force you to use any Prism-specific markup , not even a Prism-specific class name, only standard markup you should be using anyway. So, you can just try it for a while, remove it if you dont like it and leave no traces behind. However, there are some limitations too: Pre-existing HTML in the code block will be stripped off. However, there are plugins for links and highlighting certain lines . I decided not to support IE8. Prism wont break on IE8, it just wont work. I dont think many people using IE8 and below are able to read code, so I dont see the point. Enjoy: prismjs.com If you like this project, dont forget to follow @prismjs on Twitter ! Ill soon update this blog to use Prism in the code examples as well.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Important -prefix-free update</title>
  <link>https://lea.verou.me/2012/07/important-prefix-free-update/</link>
  <pubDate>Sun, 17 May 2026 02:05:06 +0200</pubDate>
  <description>Those of you that have been following and/or using my work, are surely familiar with -prefix-free . Its promise was to let you write DRY code, without all the messy prefixes, that would be standards-compliant in the future (which is why I’m always against adding proprietary features in it, regardless of their popularity). The way -prefix-free works is that it feature tests which CSS features are available only with a prefix, and then adds the prefix in front of their occurences in the code. Nothing will happen if the feature is supported both with and without a prefix or if it’s not supported at all. This worked well when browsers implementations aren’t significantly different from the unprefixed, standard version. It also works fine when the newer and the older version use incompatible syntaxes. For example, direction keywords in gradients. The old version uses top whereas the new version uses to bottom . If you include both versions, the cascade does its job and ignores the latter version if it’s not supported: background: linear-gradient(top, white, black); background: linear-gradient(to bottom, white, black); However, when the same syntax means different things in the older and the newer version, things can go horribly wrong. Thankfully, this case is quite rare. A prime example of this is linear gradient angles. 0deg means a horizontal (left to right) gradient in prefixed linear-gradients and a vertical (bottom to top) gradient in unprefixed implementations, since they follow the newer Candidate Recommendation rather than the old draft. This wasn’t a problem when every browser supported only prefixed gradients. However, now that IE10 and Firefox 16 are unprefixing their gradients implementations, it was time for me to face the issue I was avoiding ever since I wrote -prefix-free . The solution I decided on is consistent with -prefix-free ’s original promise of allowing you to write mostly standards-compliant code that will not even need -prefix-free in the future. Therefore, it will assume that your gradients use the newer syntax , and if only a prefixed implementation is available, it will convert the angles to the legacy definition. This means that if you update -prefix-free on a page that includes gradients coded with the older definition, they might break . However, they would break anyway in modern browsers, so the sooner the better. Even if you weren’t using -prefix-free at all, and had written all the declarations by hand before the angles changed, you would still have to update your code. Unfortunately, that’s the risk we all take when using experimental features like CSS gradients and I think it’s worth it. -prefix-free will not take care of any other syntax changes, since when the syntaxes are incompatible, you can easily include both declarations. The angles hotfix was included out of necessity because there is no other way to deal with it. Here’s a handy JS function that converts older angles to newer ones: function fromLegacy(deg) { return Math.abs(deg-450) % 360 } You can read more about the changes in gradient syntax in this excellent IEblog article . In addition to this change, a new feature was added to -prefix-free . If you ONLY want to use the prefixed version of a feature, but still don’t want to write out of all the prefixes, you can just use -*- as a prefix placeholder and it will be replaced with the current browser’s prefix on runtime. So, if you don’t want to change your angles, you can just prepend -*- to your linear-gradients, like so: background: -*-linear-gradient(0deg, white, black); However, it’s a much more futureproof and standards compatible solution to just update your angles to the new definition. You know you’ll have to do it at some point anyway. ;) Edit: Although -prefix-free doesn’t handle syntax changes in radial gradients, since the syntaxes are mutually incompatible, you may use this little PrefixFree plugin I wrote for the CSS Patterns Gallery , which converts the standard syntax to legacy syntax when needed: StyleFix.register(function(css, raw) { if (PrefixFree.functions.indexOf(&#39;radial-gradient&#39;) &gt; -1) { css = css.replace(/radial-gradient\(([a-z-\s]+\s+)?at ([^,]+)(?=,)/g, function($0, shape, center){ return &#39;radial-gradient(&#39; + center + (shape? &#39;, &#39; + shape : &#39;&#39;); }); } return css; }); Keep in mind however that it’s very crude and not very well tested.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Important -prefix-free update</title>
  <link>https://lea.verou.me/2012/07/important-prefix-free-update/</link>
  <pubDate>Sun, 17 May 2026 02:05:06 +0200</pubDate>
  <description>Those of you that have been following and/or using my work, are surely familiar with -prefix-free . Its promise was to let you write DRY code, without all the messy prefixes, that would be standards-compliant in the future (which is why Im always against adding proprietary features in it, regardless of their popularity). The way -prefix-free works is that it feature tests which CSS features are available only with a prefix, and then adds the prefix in front of their occurences in the code. Nothing will happen if the feature is supported both with and without a prefix or if its not supported at all. This worked well when browsers implementations arent significantly different from the unprefixed, standard version. It also works fine when the newer and the older version use incompatible syntaxes. For example, direction keywords in gradients. The old version uses top whereas the new version uses to bottom . If you include both versions, the cascade does its job and ignores the latter version if its not supported: background: linear-gradient(top, white, black); background: linear-gradient(to bottom, white, black); However, when the same syntax means different things in the older and the newer version, things can go horribly wrong. Thankfully, this case is quite rare. A prime example of this is linear gradient angles. 0deg means a horizontal (left to right) gradient in prefixed linear-gradients and a vertical (bottom to top) gradient in unprefixed implementations, since they follow the newer Candidate Recommendation rather than the old draft. This wasnt a problem when every browser supported only prefixed gradients. However, now that IE10 and Firefox 16 are unprefixing their gradients implementations, it was time for me to face the issue I was avoiding ever since I wrote -prefix-free . The solution I decided on is consistent with -prefix-free s original promise of allowing you to write mostly standards-compliant code that will not even need -prefix-free in the future. Therefore, it will assume that your gradients use the newer syntax , and if only a prefixed implementation is available, it will convert the angles to the legacy definition. This means that if you update -prefix-free on a page that includes gradients coded with the older definition, they might break . However, they would break anyway in modern browsers, so the sooner the better. Even if you werent using -prefix-free at all, and had written all the declarations by hand before the angles changed, you would still have to update your code. Unfortunately, thats the risk we all take when using experimental features like CSS gradients and I think its worth it. -prefix-free will not take care of any other syntax changes, since when the syntaxes are incompatible, you can easily include both declarations. The angles hotfix was included out of necessity because there is no other way to deal with it. Heres a handy JS function that converts older angles to newer ones: function fromLegacy(deg) { return Math.abs(deg-450) % 360 } You can read more about the changes in gradient syntax in this excellent IEblog article . In addition to this change, a new feature was added to -prefix-free . If you ONLY want to use the prefixed version of a feature, but still dont want to write out of all the prefixes, you can just use -*- as a prefix placeholder and it will be replaced with the current browsers prefix on runtime. So, if you dont want to change your angles, you can just prepend -*- to your linear-gradients, like so: background: -*-linear-gradient(0deg, white, black); However, its a much more futureproof and standards compatible solution to just update your angles to the new definition. You know youll have to do it at some point anyway. ;) Edit: Although -prefix-free doesnt handle syntax changes in radial gradients, since the syntaxes are mutually incompatible, you may use this little PrefixFree plugin I wrote for the CSS Patterns Gallery , which converts the standard syntax to legacy syntax when needed: StyleFix.register(function(css, raw) { if (PrefixFree.functions.indexOf(&#39;radial-gradient&#39;) &gt; -1) { css = css.replace(/radial-gradient\(([a-z-\s]+\s+)?at ([^,]+)(?=,)/g, function($0, shape, center){ return &#39;radial-gradient(&#39; + center + (shape? &#39;, &#39; + shape : &#39;&#39;); }); } return css; }); Keep in mind however that its very crude and not very well tested.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>So, you’ve been invited to speak</title>
  <link>https://lea.verou.me/2012/06/so-youve-been-invited-to-speak/</link>
  <pubDate>Sun, 17 May 2026 02:05:04 +0200</pubDate>
  <description>I’ve been lucky enough to be invited to do about 25 talks over the course of the past few years and I have quite a few upcoming gigs as well, most of them at international conferences around Europe and the US. Despite my speaking experience, I’m still very reluctant to call myself a “professional speaker” or even a “speaker” at all. In case you follow me on twitter , you might have noticed that my bio says “Often pretends to be a speaker”, and that captures exactly how I feel. I’m not one of those confident performers that don’t just present interesting stuff, but also can blurt jokes one after the other, almost like stand-up comedians and never backtrack or go “ummm”. I greatly admire these people and I aspire to become as confident as them on stage one day. People like Aral Balkan , Christian Heilmann , Nicole Sullivan , Jake Archibald and many others. Unlike them, I often backtrack mid-sentence, say a lot of &quot;ummmm&quot;s and sometimes talk about stuff that was going to be later in my slides, all of which are very awkward. However, I’ve reached the conclusion that I must be doing something right. I do get a lot of overwhelmingly positive feedback after almost every talk, even by people I admire in the industry. I don’t think I’ve ever gotten a negative comment for a talk, even in cases that I thought I had screwed up. Naturally, after all these conferences, I’ve attended a lot of technical talks myself, and I’ve gathered some insight on what constitutes a technical talk the audience will enjoy. I’ve been pondering to write a post with advice about this for a long time, but my lack of confidence about my speaking abilities put me off the task. However, since people seem to consider me good , I figured it might help others doing technical talks as well. All of the following are rules of thumb. You have to keep in mind that there are exceptions to every single one, but it’s often quicker and more interesting to talk in absolutes. I will try to stay away from what’s already been said in other similar articles, such as “tell a story” or “be funny” etc, not because it’s bad advice, but because a) I’m not really good at those so I prefer to let others discuss them and b) I don’t like repeating stuff that’s already been said numerous times before. I will try to focus on what I do differently, and why I think it works. It might not fit your style and that’s ok. Audiences like a wide range of presentation styles, otherwise I’d be screwed, as I don’t fit the traditional “good speaker” profile. Also, it goes without saying that some of my advice might be flat out wrong. I’m just trying to do pattern recognition to figure out why people like my talks. That’s bound to be error-prone. My talks might be succeeding in spite of X and not because of it. 1. Do something unique There are many nice talks with good minimal slides (almost everyone has read Presentation Zen by now), funny pictures, good content and confident presenters. In fact, they dominate almost every conference I’ve been at. You can always become a good speaker by playing it safe, and many famous speakers in the industry have earned their fame by doing so. There is absolutely nothing wrong with that. However, to stand out doing that kind of talk, you need to be really, really good . Hats off to the speakers that managed to stand out doing talks like that, because it means they are truly amazing. However, if you, like me, fear that your speaking skills are not yet close to that caliber, you need to figure out something else that sets you apart. Something that will make your talk memorable. We see a lot of innovation in our discipline, but it’s limited to the scripts and apps we write. Why not to our presentations as well? Do something different, and make it your thing, your “trademark” way of presenting. For me, that was the embedded demos in my slides. I usually have a small text field where I write code, and something (often the entire slide or text field itself) that displays the outcome. This lets the attendees see not just the end result, but also the intermediate states until we get there, which often proves out to be enlightening. It also makes the slide quite flexible, as I can always show something extra if I have the time. Of course, it also means that things might (and if you talk often enough, will at some point) go wrong. To mitigate this to a degree, I try to keep demos small, with a sensible starting state, so that I won’t have to write a lot of code. Which brings us to the next point. 2. Never show a lot of code on a slide I have a theory: Attendees’ understanding of code decreases exponentially as the lines of simultaneously displayed code increase. Avoid showing many lines of code at once like the plague. Although I’ve shown up to 10 lines of code on a single slide (maybe even more), I usually try to keep it well below five. Ideally less than three even. If you absolutely must present more code, try to use a technique to make the audience understand it by chunks, so that they still only have to process very little code at any given time. One technique I use for that is showing little code at first, and writing the rest on stage, gradually, explaining the steps as I go. When that isn’t possible or it doesn’t make sense (for example when there is no visual result to see), I try to show parts of the code step by step, explaining what everything does as it appears. This doesn’t necessarily mean showing one line at a time. For example, if you are showing a JS function, it makes sense to show the closing brace at the same time as the opening brace and not at the end. Show the elements of the code in the order you would write them in a top-down implementation, not by pure source order (although in some cases those two may coincide). To make this more clear, here’s an example slide where I used this technique. It’s from my “Polyfilling the gaps” talk at JSConf EU 2011, one of the very few talks of mine that had no live coding. Also, it goes without saying that if you have to present a lot of code at once, syntax highlighting is a must. Comments aren’t: That’s what you are there for. Comments just add visual clutter and make it harder for people to interpret the actual code. Also, while explaining the code, try to somehow highlight the part you’re currently describing, even if your method is as rudimentary as selecting the text. Otherwise, if someone misses a sentence, they will completely lose track. 3. IDEs are not good presentation tools I’ve seen this so many times: Someone wants to live demo a technology and fires up their IDE or text editor and starts coding. The audience tries to follow along at first, but at some point almost always gets lost. While these tools are great for programming, they are not presentation tools. They have a lot of distracting clutter, very small text and require you to show parts of the code that aren’t really relevant. They also require you to switch applications to toggle between the code and result, which disrupts the flow of your presentation. In addition, the probability you will make a mistake increases exponentially with the amount of code you write, both in real life and especially in presentations where you are also nervous. Then the audience is stuck with an embarrassed presenter trying to find what’s wrong for five minutes, until someone from the audience decides to put them out of their misery and shout that a closing parenthesis is missing on line 25. That’s why live coding has gotten a bad reputation over the years. As you’ve probably figured from tip #1, I’m not against live coding. Done well, it can really help the audience learn. However, if not done properly, it can end up completely wrecking a talk. Even if you absolutely have to use an external tool, try to make the experience as smooth as possible: Hide any toolbars, sidebars, panels. If your editor doesn’t allow you to hide everything that isn’t relevant, use another editor. Make the text BIG. If possible, as big as the text in your slides. Remember: Text in slides is big, because you need even the attendees sitting in the back rows to still be able to read it. Why is it that this simple consideration seems to escape so many presenter minds when they switch from slides to code? If parts of the code are needed but not relevant (e.g. CSS files in a JavaScript talk), put them in separate files and reference them. Try to minimize the code you will actually show as much as possible, and then even more. If applicable, use LiveReload and have the browser window and code editor side by side. 4. Don’t aim to beginners (only) Some of the nastiest criticism I’ve seen against people’s talks was that they were too elementary. Getting feedback like that has almost become a phobia of mine. Of course, it’s always better if your entire audience is at the same level, and you are fully aware what that level is. However, that’s almost never the case, so you will have to err on one side. Do your best to cater to the median, but when you have to err, err on the side of more advanced content. A somewhat selfish reason would be that when people find your talk too elementary, they will blame you; when they find it too advanced, they will blame themselves. However, it’s not just covering your ass, it’s better for your audience as well. Someone who didn’t learn anything new gets absolutely nothing out of your talk (unless it’s an interesting performance on its own, e.g. so funny it could have been stand up comedy for geeks). A person that learned many things but didn’t understand some of the more advanced concepts will still have gotten a lot out of it. If someone learns a useful thing or two from your talk, that’s what they’ll remember. Even if the rest of the talk was elementary or too advanced for them, they will walk out with a positive impression, thinking “I learned something today!”. Even if most of your talk is elementary, try to sneak in some more advanced or obscure bits, that not many people know. My favorite approach to cater for a diverse audience with very different levels of experience, is to pick a narrow topic, start from the very basics and work my way up to more advanced concepts. This way everyone learns something, and nobody feels intimidated. On the flip side, if you are in a multi-track conference, this also limits the potential audience that might come to your talk. 5. Eat your own dog food I’m a huge fan of HTML-based (or SVG-based) slideshows. I’ve always been, since my first talk. It’s a technology you’re already accustomed to, so you can do amazing things with it. You can write scripts that demonstrate the concepts you describe in some visual way, you can do live demos, you can embed iframes of other people’s demos, you know how to style it much better than you likely know how to use Keynote. Yes, if you’re used to traditional presentation tools, it might be hard at first. Many features you’ve been taking for granted will be missing. Styling is not visual, there are no presenter notes, no next slide preview, no automatic adjustment to the projector resolution to name a few. But what you gain in potential and expressiveness, are totally worth the trade-off. Also, rather than having the talk prep keep you from writing code and becoming better at what you do, it will now actually contribute to it! It’s also a great chance to try experimental stuff, as it’s going to be run in a very controlled environment. You don’t even need to write your own presentation framework if you don’t want to. There are a ton available now, such as my own CSSS , impress.js and many others . 6. Involve the audience There is an old Chinese proverb that goes like: Tell me, and I’ll forget Show me, and I’ll remember Involve me, and I’ll understand I’ve noticed that audiences respond extremely well to talks that attempt to involve them. Seb-Lee Delisle gave a talk at Fronteers 2011 where he involved the audience by ideas like demonstrating Web Sockets through making their iPhones flash in such a way that he could create light patterns with the audience. Even though some of the demos failed (I think something crashed, don’t remember very well), the audience loved every bit . I’ve rarely seen people that excited about a talk. Involving the audience was something I wanted to do for a while. In my recent Regular Expressions talks, I had a series of small “challenges” where the audience tried to write a regexp to match a certain set of strings as quickly as possible and tweet it. I provided a link to an app I made especially for that . The person who got most regexes right (or more right than others) won a book. This was also very well received and lots of positive feedback mentioned it. When it feels like a game, learning is much more fun.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>So, youve been invited to speak</title>
  <link>https://lea.verou.me/2012/06/so-youve-been-invited-to-speak/</link>
  <pubDate>Sun, 17 May 2026 02:05:04 +0200</pubDate>
  <description>Ive been lucky enough to be invited to do about 25 talks over the course of the past few years and I have quite a few upcoming gigs as well, most of them at international conferences around Europe and the US. Despite my speaking experience, Im still very reluctant to call myself a professional speaker or even a speaker at all. In case you follow me on twitter , you might have noticed that my bio says Often pretends to be a speaker, and that captures exactly how I feel. Im not one of those confident performers that dont just present interesting stuff, but also can blurt jokes one after the other, almost like stand-up comedians and never backtrack or go ummm. I greatly admire these people and I aspire to become as confident as them on stage one day. People like Aral Balkan , Christian Heilmann , Nicole Sullivan , Jake Archibald and many others. Unlike them, I often backtrack mid-sentence, say a lot of &quot;ummmm&quot;s and sometimes talk about stuff that was going to be later in my slides, all of which are very awkward. However, Ive reached the conclusion that I must be doing something right. I do get a lot of overwhelmingly positive feedback after almost every talk, even by people I admire in the industry. I dont think Ive ever gotten a negative comment for a talk, even in cases that I thought I had screwed up. Naturally, after all these conferences, Ive attended a lot of technical talks myself, and Ive gathered some insight on what constitutes a technical talk the audience will enjoy. Ive been pondering to write a post with advice about this for a long time, but my lack of confidence about my speaking abilities put me off the task. However, since people seem to consider me good , I figured it might help others doing technical talks as well. All of the following are rules of thumb. You have to keep in mind that there are exceptions to every single one, but its often quicker and more interesting to talk in absolutes. I will try to stay away from whats already been said in other similar articles, such as tell a story or be funny etc, not because its bad advice, but because a) Im not really good at those so I prefer to let others discuss them and b) I dont like repeating stuff thats already been said numerous times before. I will try to focus on what I do differently, and why I think it works. It might not fit your style and thats ok. Audiences like a wide range of presentation styles, otherwise Id be screwed, as I dont fit the traditional good speaker profile. Also, it goes without saying that some of my advice might be flat out wrong. Im just trying to do pattern recognition to figure out why people like my talks. Thats bound to be error-prone. My talks might be succeeding in spite of X and not because of it. 1. Do something unique There are many nice talks with good minimal slides (almost everyone has read Presentation Zen by now), funny pictures, good content and confident presenters. In fact, they dominate almost every conference Ive been at. You can always become a good speaker by playing it safe, and many famous speakers in the industry have earned their fame by doing so. There is absolutely nothing wrong with that. However, to stand out doing that kind of talk, you need to be really, really good . Hats off to the speakers that managed to stand out doing talks like that, because it means they are truly amazing. However, if you, like me, fear that your speaking skills are not yet close to that caliber, you need to figure out something else that sets you apart. Something that will make your talk memorable. We see a lot of innovation in our discipline, but its limited to the scripts and apps we write. Why not to our presentations as well? Do something different, and make it your thing, your trademark way of presenting. For me, that was the embedded demos in my slides. I usually have a small text field where I write code, and something (often the entire slide or text field itself) that displays the outcome. This lets the attendees see not just the end result, but also the intermediate states until we get there, which often proves out to be enlightening. It also makes the slide quite flexible, as I can always show something extra if I have the time. Of course, it also means that things might (and if you talk often enough, will at some point) go wrong. To mitigate this to a degree, I try to keep demos small, with a sensible starting state, so that I wont have to write a lot of code. Which brings us to the next point. 2. Never show a lot of code on a slide I have a theory: Attendees understanding of code decreases exponentially as the lines of simultaneously displayed code increase. Avoid showing many lines of code at once like the plague. Although Ive shown up to 10 lines of code on a single slide (maybe even more), I usually try to keep it well below five. Ideally less than three even. If you absolutely must present more code, try to use a technique to make the audience understand it by chunks, so that they still only have to process very little code at any given time. One technique I use for that is showing little code at first, and writing the rest on stage, gradually, explaining the steps as I go. When that isnt possible or it doesnt make sense (for example when there is no visual result to see), I try to show parts of the code step by step, explaining what everything does as it appears. This doesnt necessarily mean showing one line at a time. For example, if you are showing a JS function, it makes sense to show the closing brace at the same time as the opening brace and not at the end. Show the elements of the code in the order you would write them in a top-down implementation, not by pure source order (although in some cases those two may coincide). To make this more clear, heres an example slide where I used this technique. Its from my Polyfilling the gaps talk at JSConf EU 2011, one of the very few talks of mine that had no live coding. Also, it goes without saying that if you have to present a lot of code at once, syntax highlighting is a must. Comments arent: Thats what you are there for. Comments just add visual clutter and make it harder for people to interpret the actual code. Also, while explaining the code, try to somehow highlight the part youre currently describing, even if your method is as rudimentary as selecting the text. Otherwise, if someone misses a sentence, they will completely lose track. 3. IDEs are not good presentation tools Ive seen this so many times: Someone wants to live demo a technology and fires up their IDE or text editor and starts coding. The audience tries to follow along at first, but at some point almost always gets lost. While these tools are great for programming, they are not presentation tools. They have a lot of distracting clutter, very small text and require you to show parts of the code that arent really relevant. They also require you to switch applications to toggle between the code and result, which disrupts the flow of your presentation. In addition, the probability you will make a mistake increases exponentially with the amount of code you write, both in real life and especially in presentations where you are also nervous. Then the audience is stuck with an embarrassed presenter trying to find whats wrong for five minutes, until someone from the audience decides to put them out of their misery and shout that a closing parenthesis is missing on line 25. Thats why live coding has gotten a bad reputation over the years. As youve probably figured from tip #1, Im not against live coding. Done well, it can really help the audience learn. However, if not done properly, it can end up completely wrecking a talk. Even if you absolutely have to use an external tool, try to make the experience as smooth as possible: Hide any toolbars, sidebars, panels. If your editor doesnt allow you to hide everything that isnt relevant, use another editor. Make the text BIG. If possible, as big as the text in your slides. Remember: Text in slides is big, because you need even the attendees sitting in the back rows to still be able to read it. Why is it that this simple consideration seems to escape so many presenter minds when they switch from slides to code? If parts of the code are needed but not relevant (e.g. CSS files in a JavaScript talk), put them in separate files and reference them. Try to minimize the code you will actually show as much as possible, and then even more. If applicable, use LiveReload and have the browser window and code editor side by side. 4. Dont aim to beginners (only) Some of the nastiest criticism Ive seen against peoples talks was that they were too elementary. Getting feedback like that has almost become a phobia of mine. Of course, its always better if your entire audience is at the same level, and you are fully aware what that level is. However, thats almost never the case, so you will have to err on one side. Do your best to cater to the median, but when you have to err, err on the side of more advanced content. A somewhat selfish reason would be that when people find your talk too elementary, they will blame you; when they find it too advanced, they will blame themselves. However, its not just covering your ass, its better for your audience as well. Someone who didnt learn anything new gets absolutely nothing out of your talk (unless its an interesting performance on its own, e.g. so funny it could have been stand up comedy for geeks). A person that learned many things but didnt understand some of the more advanced concepts will still have gotten a lot out of it. If someone learns a useful thing or two from your talk, thats what theyll remember. Even if the rest of the talk was elementary or too advanced for them, they will walk out with a positive impression, thinking I learned something today!. Even if most of your talk is elementary, try to sneak in some more advanced or obscure bits, that not many people know. My favorite approach to cater for a diverse audience with very different levels of experience, is to pick a narrow topic, start from the very basics and work my way up to more advanced concepts. This way everyone learns something, and nobody feels intimidated. On the flip side, if you are in a multi-track conference, this also limits the potential audience that might come to your talk. 5. Eat your own dog food Im a huge fan of HTML-based (or SVG-based) slideshows. Ive always been, since my first talk. Its a technology youre already accustomed to, so you can do amazing things with it. You can write scripts that demonstrate the concepts you describe in some visual way, you can do live demos, you can embed iframes of other peoples demos, you know how to style it much better than you likely know how to use Keynote. Yes, if youre used to traditional presentation tools, it might be hard at first. Many features youve been taking for granted will be missing. Styling is not visual, there are no presenter notes, no next slide preview, no automatic adjustment to the projector resolution to name a few. But what you gain in potential and expressiveness, are totally worth the trade-off. Also, rather than having the talk prep keep you from writing code and becoming better at what you do, it will now actually contribute to it! Its also a great chance to try experimental stuff, as its going to be run in a very controlled environment. You dont even need to write your own presentation framework if you dont want to. There are a ton available now, such as my own CSSS , impress.js and many others . 6. Involve the audience There is an old Chinese proverb that goes like: Tell me, and Ill forget Show me, and Ill remember Involve me, and Ill understand Ive noticed that audiences respond extremely well to talks that attempt to involve them. Seb-Lee Delisle gave a talk at Fronteers 2011 where he involved the audience by ideas like demonstrating Web Sockets through making their iPhones flash in such a way that he could create light patterns with the audience. Even though some of the demos failed (I think something crashed, dont remember very well), the audience loved every bit . Ive rarely seen people that excited about a talk. Involving the audience was something I wanted to do for a while. In my recent Regular Expressions talks, I had a series of small challenges where the audience tried to write a regexp to match a certain set of strings as quickly as possible and tweet it. I provided a link to an app I made especially for that . The person who got most regexes right (or more right than others) won a book. This was also very well received and lots of positive feedback mentioned it. When it feels like a game, learning is much more fun.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Why I bought a high-end MacBook Air instead of the Retina MBP</title>
  <link>https://lea.verou.me/2012/06/why-i-bought-a-high-end-macbook-air-instead-of-the-retina-mbp/</link>
  <pubDate>Sun, 17 May 2026 02:05:03 +0200</pubDate>
  <description>After the WWDC keynote, I was convinced I would buy a new MacBook Air. I was looking forward to any announcements about new hardware during the event, as my 13&quot; 2010 MacBook Pro (Henceforth abbreviated as MBP) was becoming increasingly slow and dated. Also, I wanted to gift my MBP to my mother, who is currently using a horrible tiny Windows XP Netbook and every time I see her struggling to work on it, my insides hurt. All tweets about my shopping plans, or, later, about my new toy (I bought it yesterday) were met with surprise and bewilderment: I was repeatedly bombarded with questions asking why I’m not getting a Retina MacBook Pro, over and over again. The fact that I paid about $2200 + tax for it (it’s the best 13&quot; Air you can currently get) made it even more weird: If you could afford that, why wouldn’t you possibly get the Retina MBP at the exact same price? At first, I tried to reply with individual tweets to everyone that asked. Then I got tired of that and started replying with links to the first tweets, then I decided to write a blog post. So, here are my reasons: Portability I travel a lot. For me, it’s very important to be able to use my laptop in a cramped airplane seat, or while standing in a line. You can’t really do that with a 15&quot; MacBook Pro, even with the new slimmer design. I wanted to be able to quickly pull it out of my tote bag with one hand, hold it with said hand and quickly look up something with the other hand. Usage scenarios of that sort are just unthinkable for big laptops. Of course, portability is not the only thing that matters, as I only use one laptop as my main work machine. Having two machines, one for portability and one for “real work”, always seemed to me like more hassle than it’s worth. So, a 11&quot; MacBook Air was also out of the question. Which brings us to the middle ground of a 13&quot; laptop. All my laptops had always been around 13&quot;. It’s a perfect trade-off between power and portability and I don’t wish to change that any time soon. It was quite simple: The 13&quot; Air is more portable than my MBP. The 15&quot; Retina MBP was less portable. I needed more portability than I had, not less. I saw the Retina MBP and wasn’t too impressed When I first went to the Apple Store to buy the MacBook Air, I saw the new Retina display. I even managed to use it a bit, despite the swarm of fellow geeks nerdgasming uncontrollably around it. I won’t lie: I was tempted at first. The display is very crisp indeed, although the difference between icons that were not updated for the Retina is quite obvious, especially next to their accompanying text (which is always crisp, since text is vector-based). I started being unsure about my decision, as Nicole Sullivan can attest (she was with me). And then it dawned on me: Hey, I should see the MacBook I was planning to buy in person too. Maybe its screen is also quite crisp. Maybe the difference won’t even be that noticeable. I was right: My simple, untrained eyes could not really tell the difference. MacBook Airs have a decently crisp screen. Of course, if you put them next to each other, I’d imagine the difference would be fairly obvious. But who does that? However, my impression still wasn’t sufficient to make a decision. I’ve learned not to trust my unreliable human senses too much. I needed numbers. Calculating the actual DPI of a monitor is actually fairly simple: All you need is the Pythagorean theorem you learned in school, to calculate the hypotenuse of the screen in pixels, and then divide that number by the length of the diagonal in inches. The result will be the number of pixels per inch, commonly (and slightly incorrectly) referred to as DPI (PPI is more correct). If you know basic JavaScript, you don’t even need a calculator, just your ol’ trusty console. I even wrote a handy function that does it for me: function dpi(w,h,inches) { return Math.round(Math.sqrt(w*w + h*h)/inches) } For the 13&quot; MacBook Air, the DPI is: &gt; dpi(1440, 900, 13.3) 128 For the Retina MBP, it’s: &gt; dpi(2880, 1800, 15.4) 221 220 DPI is certainly higher than 130, but it’s not the kind of eyegasm-inducing difference I experienced when I moved from an iPhone 3G to an iPhone 4 (the difference there was 163 DPI vs 326 DPI). I don’t want to distance myself too much from the average web user It happens more than we like to admit: We get cool new hardware, and eventually we’re carried away and think most web users are close to our level. We start designing for bigger and bigger resolutions, because it’s hard to imagine that some people are still on 1024x768. We code to better CPUs, because it’s hard to imagine how crappy computers many of our target users have. We don’t care about bandwidth and battery, because they aren’t a concern for most of us. Some of us will realize before launching, during a very painful testing session, some others will only realize after the complaints start pouring in. It’s the same reason a website always looks and works better in the browser its developers use, even though almost always it gets tested in many others. We like to think we’re better than that, that we always test, that we never get carried away, but in most cases we are lying to ourselves. So, even though I recognize that I probably have much better hardware than most web users, I consciously try to avoid huge resolutions as I know I’ll get carried away. I try to keep myself as close to the average user as I can tolerate. Using IE9 on a 1024x768 resolution would be over that threshold, but not using a Retina display is easily tolerable. That’s all folks Hope this makes sense. Hopefully, it might help others trying to decide between the two. I sure am very happy so far with my new Air :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Why I bought a high-end MacBook Air instead of the Retina MBP</title>
  <link>https://lea.verou.me/2012/06/why-i-bought-a-high-end-macbook-air-instead-of-the-retina-mbp/</link>
  <pubDate>Sun, 17 May 2026 02:05:03 +0200</pubDate>
  <description>After the WWDC keynote, I was convinced I would buy a new MacBook Air. I was looking forward to any announcements about new hardware during the event, as my 13&quot; 2010 MacBook Pro (Henceforth abbreviated as MBP) was becoming increasingly slow and dated. Also, I wanted to gift my MBP to my mother, who is currently using a horrible tiny Windows XP Netbook and every time I see her struggling to work on it, my insides hurt. All tweets about my shopping plans, or, later, about my new toy (I bought it yesterday) were met with surprise and bewilderment: I was repeatedly bombarded with questions asking why Im not getting a Retina MacBook Pro, over and over again. The fact that I paid about $2200 + tax for it (its the best 13&quot; Air you can currently get) made it even more weird: If you could afford that, why wouldnt you possibly get the Retina MBP at the exact same price? At first, I tried to reply with individual tweets to everyone that asked. Then I got tired of that and started replying with links to the first tweets, then I decided to write a blog post. So, here are my reasons: Portability I travel a lot. For me, its very important to be able to use my laptop in a cramped airplane seat, or while standing in a line. You cant really do that with a 15&quot; MacBook Pro, even with the new slimmer design. I wanted to be able to quickly pull it out of my tote bag with one hand, hold it with said hand and quickly look up something with the other hand. Usage scenarios of that sort are just unthinkable for big laptops. Of course, portability is not the only thing that matters, as I only use one laptop as my main work machine. Having two machines, one for portability and one for real work, always seemed to me like more hassle than its worth. So, a 11&quot; MacBook Air was also out of the question. Which brings us to the middle ground of a 13&quot; laptop. All my laptops had always been around 13&quot;. Its a perfect trade-off between power and portability and I dont wish to change that any time soon. It was quite simple: The 13&quot; Air is more portable than my MBP. The 15&quot; Retina MBP was less portable. I needed more portability than I had, not less. I saw the Retina MBP and wasnt too impressed When I first went to the Apple Store to buy the MacBook Air, I saw the new Retina display. I even managed to use it a bit, despite the swarm of fellow geeks nerdgasming uncontrollably around it. I wont lie: I was tempted at first. The display is very crisp indeed, although the difference between icons that were not updated for the Retina is quite obvious, especially next to their accompanying text (which is always crisp, since text is vector-based). I started being unsure about my decision, as Nicole Sullivan can attest (she was with me). And then it dawned on me: Hey, I should see the MacBook I was planning to buy in person too. Maybe its screen is also quite crisp. Maybe the difference wont even be that noticeable. I was right: My simple, untrained eyes could not really tell the difference. MacBook Airs have a decently crisp screen. Of course, if you put them next to each other, Id imagine the difference would be fairly obvious. But who does that? However, my impression still wasnt sufficient to make a decision. Ive learned not to trust my unreliable human senses too much. I needed numbers. Calculating the actual DPI of a monitor is actually fairly simple: All you need is the Pythagorean theorem you learned in school, to calculate the hypotenuse of the screen in pixels, and then divide that number by the length of the diagonal in inches. The result will be the number of pixels per inch, commonly (and slightly incorrectly) referred to as DPI (PPI is more correct). If you know basic JavaScript, you dont even need a calculator, just your ol trusty console. I even wrote a handy function that does it for me: function dpi(w,h,inches) { return Math.round(Math.sqrt(w*w + h*h)/inches) } For the 13&quot; MacBook Air, the DPI is: &gt; dpi(1440, 900, 13.3) 128 For the Retina MBP, its: &gt; dpi(2880, 1800, 15.4) 221 220 DPI is certainly higher than 130, but its not the kind of eyegasm-inducing difference I experienced when I moved from an iPhone 3G to an iPhone 4 (the difference there was 163 DPI vs 326 DPI). I dont want to distance myself too much from the average web user It happens more than we like to admit: We get cool new hardware, and eventually were carried away and think most web users are close to our level. We start designing for bigger and bigger resolutions, because its hard to imagine that some people are still on 1024x768. We code to better CPUs, because its hard to imagine how crappy computers many of our target users have. We dont care about bandwidth and battery, because they arent a concern for most of us. Some of us will realize before launching, during a very painful testing session, some others will only realize after the complaints start pouring in. Its the same reason a website always looks and works better in the browser its developers use, even though almost always it gets tested in many others. We like to think were better than that, that we always test, that we never get carried away, but in most cases we are lying to ourselves. So, even though I recognize that I probably have much better hardware than most web users, I consciously try to avoid huge resolutions as I know Ill get carried away. I try to keep myself as close to the average user as I can tolerate. Using IE9 on a 1024x768 resolution would be over that threshold, but not using a Retina display is easily tolerable. Thats all folks Hope this makes sense. Hopefully, it might help others trying to decide between the two. I sure am very happy so far with my new Air :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Hacking lookahead to mimic intersection, subtraction and negation</title>
  <link>https://lea.verou.me/2012/05/hacking-lookahead-to-mimic-intersection-subtraction-and-negation/</link>
  <pubDate>Sun, 17 May 2026 02:05:02 +0200</pubDate>
  <description>Note: To understand the following, I expect you to know how regex lookahead works. If you don’t, read about it first and return here after you’re done. I was quite excited to discover this, but to my dismay, Steven Levithan assured me it’s actually well known. However, I felt it’s so useful and underdocumented (the only references to the technique I could find was several StackOverflow replies) that I decided to blog about it anyway. If you’ve been using regular expressions for a while, you surely have stumbled on a variation of the following problems: Intersection : “I want to match something that matches pattern A AND pattern B” Example: A password of at least 6 characters that contains at least one digit, at least one letter and at least one symbol Subtraction : “I want to match something that matches pattern A but NOT pattern B” Example: Match any integer that is not divisible by 50 Negation : “I want to match anything that does NOT match pattern A” Example: Match anything that does NOT contain the string “foo” Even though in ECMAScript we can use the caret (^) to negate a character class, we cannot negate anything else. Furthermore, even though we have the pipe character to mean OR, we have nothing that means AND. And of course, we have nothing that means “except” (subtraction). All these are fairly easy to do for single characters, through character classes, but not for entire sequences. However, we can mimic all three operations by taking advantage of the fact that lookahead is zero length and therefore does not advance the matching position. We can just keep on matching to our heart’s content after it, and it will be matched against the same substring, since the lookahead itself consumed no characters. For a simple example, the regex /^(?!cat)\w{3}$/ will match any 3 letter word that is NOT “cat”. This was a very simple example of subtraction . Similarly, the solution to the subtraction problem above would look like /^(?!\d+[50]0)\d+$/ . For intersection (AND), we can just chain multiple positive lookaheads, and put the last pattern as the one that actually captures (if everything is within a lookahead, you’ll still get the same boolean result, but not the right matches). For example, the solution to the password problem above would look like /^(?=.*\d)(?=.*[a-z])(?=.*[\W_]).{6,}$/i . Note that if you want to support IE8, you have to take this bug into account and modify the pattern accordingly. Negation is the simplest: We just want a negative lookahead and a .+ to match anything (as long as it passes the lookahead test). For example, the solution to the negation problem above would look like /^(?!.*foo).+$/ . Admittedly, negation is also the least useful on its own. There are caveats to this technique, usually related to what actually matches in the end (make sure your actual capturing pattern, outside the lookaheads, captures the entire thing you’re interested in!), but it’s fairly simple for just testing whether something matches. Steven Levithan took lookahead hacking to the next level, by using something similar to mimic conditionals and atomic groups . Mind = blown.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Hacking lookahead to mimic intersection, subtraction and negation</title>
  <link>https://lea.verou.me/2012/05/hacking-lookahead-to-mimic-intersection-subtraction-and-negation/</link>
  <pubDate>Sun, 17 May 2026 02:05:02 +0200</pubDate>
  <description>Note: To understand the following, I expect you to know how regex lookahead works. If you dont, read about it first and return here after youre done. I was quite excited to discover this, but to my dismay, Steven Levithan assured me its actually well known. However, I felt its so useful and underdocumented (the only references to the technique I could find was several StackOverflow replies) that I decided to blog about it anyway. If youve been using regular expressions for a while, you surely have stumbled on a variation of the following problems: Intersection : I want to match something that matches pattern A AND pattern B Example: A password of at least 6 characters that contains at least one digit, at least one letter and at least one symbol Subtraction : I want to match something that matches pattern A but NOT pattern B Example: Match any integer that is not divisible by 50 Negation : I want to match anything that does NOT match pattern A Example: Match anything that does NOT contain the string foo Even though in ECMAScript we can use the caret (^) to negate a character class, we cannot negate anything else. Furthermore, even though we have the pipe character to mean OR, we have nothing that means AND. And of course, we have nothing that means except (subtraction). All these are fairly easy to do for single characters, through character classes, but not for entire sequences. However, we can mimic all three operations by taking advantage of the fact that lookahead is zero length and therefore does not advance the matching position. We can just keep on matching to our hearts content after it, and it will be matched against the same substring, since the lookahead itself consumed no characters. For a simple example, the regex /^(?!cat)\w{3}$/ will match any 3 letter word that is NOT cat. This was a very simple example of subtraction . Similarly, the solution to the subtraction problem above would look like /^(?!\d+[50]0)\d+$/ . For intersection (AND), we can just chain multiple positive lookaheads, and put the last pattern as the one that actually captures (if everything is within a lookahead, youll still get the same boolean result, but not the right matches). For example, the solution to the password problem above would look like /^(?=.*\d)(?=.*[a-z])(?=.*[\W_]).{6,}$/i . Note that if you want to support IE8, you have to take this bug into account and modify the pattern accordingly. Negation is the simplest: We just want a negative lookahead and a .+ to match anything (as long as it passes the lookahead test). For example, the solution to the negation problem above would look like /^(?!.*foo).+$/ . Admittedly, negation is also the least useful on its own. There are caveats to this technique, usually related to what actually matches in the end (make sure your actual capturing pattern, outside the lookaheads, captures the entire thing youre interested in!), but its fairly simple for just testing whether something matches. Steven Levithan took lookahead hacking to the next level, by using something similar to mimic conditionals and atomic groups . Mind = blown.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Teaching: General case first or special cases first?</title>
  <link>https://lea.verou.me/2012/05/teaching-general-case-first-or-special-cases-first/</link>
  <pubDate>Sun, 17 May 2026 01:45:20 +0200</pubDate>
  <description>A common dilemma while teaching (I’m not only talking about teaching in a school or university; talks and workshops are also teaching), is whether it’s better to first teach some easy special cases and then generalize, or first the general case and then present special cases as merely shortcuts. I’ve been revisiting this dilemma recently, while preparing the slides for my upcoming regular expressions talks . For example: Regex quantifiers. 1. General rule first, shortcuts after You can use {m,n} to control how many times the preceding group can repeat (m = minimum, n = maximum). If you omit n (like {m,}) it’s implied to be infinity (=“at least m times”, with no upper bound). {m, m} can also be written as {0,1} can also be written as ? {0,} can also be written as * {1,} can also be written as + Advantages &amp; disadvantages of this approach Harder to understand the general rule, so the student might lose interest before moving on to the shortcuts After understanding the general rule, all the shortcuts are then trivial. If they only remember one thing, it will be the general rule. That’s good. 2. Special cases first, general rule after You can add ? after a group to make it optional (it can appear, but it may also not). If you don’t care about how many times something appears (if at all), you can use *. If you want something to appear at least once, you can use + If you want something to be repeated exactly n times, you can use If you want to set specific upper and lower bounds, you can use {m,n}. Omit the n for no upper bound. Advantages &amp; disadvantages of this approach Easy to understand the simpler special cases, building up student interest More total effort required, as every shortcut seems like a separate new thing until you get to the general rule Special cases make it easier to understand the general rule when you get to it What usually happens In most cases, educators seem to favor the second approach. In the example of regex quantifiers, pretty much every regex book or talk explains the shortcuts first and the general rule afterwards. In other disciplines, such as Mathematics, I think both approaches are used just as often. What do you think? Which approach do you find easier to understand? Which approach do you usually employ while teaching?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Teaching: General case first or special cases first?</title>
  <link>https://lea.verou.me/2012/05/teaching-general-case-first-or-special-cases-first/</link>
  <pubDate>Sun, 17 May 2026 01:45:20 +0200</pubDate>
  <description>A common dilemma while teaching (Im not only talking about teaching in a school or university; talks and workshops are also teaching), is whether its better to first teach some easy special cases and then generalize, or first the general case and then present special cases as merely shortcuts. Ive been revisiting this dilemma recently, while preparing the slides for my upcoming regular expressions talks . For example: Regex quantifiers. 1. General rule first, shortcuts after You can use {m,n} to control how many times the preceding group can repeat (m = minimum, n = maximum). If you omit n (like {m,}) its implied to be infinity (=at least m times, with no upper bound). {m, m} can also be written as {0,1} can also be written as ? {0,} can also be written as * {1,} can also be written as + Advantages &amp; disadvantages of this approach Harder to understand the general rule, so the student might lose interest before moving on to the shortcuts After understanding the general rule, all the shortcuts are then trivial. If they only remember one thing, it will be the general rule. Thats good. 2. Special cases first, general rule after You can add ? after a group to make it optional (it can appear, but it may also not). If you dont care about how many times something appears (if at all), you can use *. If you want something to appear at least once, you can use + If you want something to be repeated exactly n times, you can use If you want to set specific upper and lower bounds, you can use {m,n}. Omit the n for no upper bound. Advantages &amp; disadvantages of this approach Easy to understand the simpler special cases, building up student interest More total effort required, as every shortcut seems like a separate new thing until you get to the general rule Special cases make it easier to understand the general rule when you get to it What usually happens In most cases, educators seem to favor the second approach. In the example of regex quantifiers, pretty much every regex book or talk explains the shortcuts first and the general rule afterwards. In other disciplines, such as Mathematics, I think both approaches are used just as often. What do you think? Which approach do you find easier to understand? Which approach do you usually employ while teaching?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Poll: ¿Is animation-direction a good idea?</title>
  <link>https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/</link>
  <pubDate>Sun, 17 May 2026 01:45:19 +0200</pubDate>
  <description>¿animation-direction? Lets assume you have a CSS animation for background-color that goes from a shade of yellow (#cc0) to a shade of blue (#079) and repeats indefinitely. The code could be something like this: @keyframes color { from { background: #cc0 } to { background: #079 } } div { animation: color 3s infinite; } If we linearize that animation, the progression over time goes like this (showing 3 iterations): As you can see, the change from the end state to the beginning state of a new iteration is quite abrupt. You could change your keyframes to mitigate this, but there’s a better way. A property with the name animation-direction gives a degree of control on the direction of the iterations to smooth this out. It also reverses your timing functions, which makes it even smoother. In early drafts, only the values normal and alternate were allowed. normal results in the behavior described above, whereas alternate flips every other iteration (the 2nd, the 4th, the 6th and so on), resulting in a progression like this (note how the 2nd iteration has been reversed): The latest draft also adds two more values: reverse which reverses every iteration and alternate-reverse , which is the combination of both reverse and alternate . Here is a summary of what kind of progression these four values would create for the animation above: The problem I was excited to see that reverse and alternate-reverse were finally added to the spec, but something in the syntax just didn’t click. I initially thought the reason was that these four values essentially set 2 toggles: ¿Reverse all iterations? yes/no ¿Reverse even iterations? yes/no so it’s pointless cognitive overhead to remember four distinct values. I proposed that they should be split in two keywords instead, which would even result to a simpler grammar too. The proposal was well received by one of the co-editors of the animations spec ( Sylvain Galineau ), but there was a dilemma as to whether mixing normal with alternate or reverse would make it easier to learn or more confusing. This is a point where your opinion would be quite useful. Would you expect the following to work, or would you find them confusing? animation-direction: normal alternate; /* Equivalent to animation-direction: alternate; */ animation-direction: alternate normal; /* Equivalent to animation-direction: alternate; */ animation-direction: normal reverse; /* Equivalent to animation-direction: reverse; */ animation-direction: reverse normal; /* Equivalent to animation-direction: reverse; */ A better (?) idea At some point, in the middle of writing this blog post (I started it yesterday), while gazing at the graphic above, I had a lightbulb moment. ¡These values are not two toggles! All four of them control one thing: which iterations are reversed : normal reverses no iterations reverse reverses all iterations alternate reverses even iterations alternate-reverse reverses odd iterations The reason it’s so confusing and it took me so long to realize myself, is that the mental model suggested by these keywords is detached from the end result, especially in the case of alternate-reverse . You have to realize that it works as if both alternate and reverse were applied in sequence, so reverse first reverses all iterations and then alternate reverses the even ones. Even iterations are reversed twice , and are therefore equivalent to the original direction. This leaves the odd ones as being reversed. It’s basically a double negative, making it hard to visualize and understand. I thought that a property that would reflect this in a much more straightforward way, would be animation-reverse (or animation-iteration-reverse ), accepting the following values: none (equivalent to animation-direction: normal) all (equivalent to animation-direction: reverse) even (equivalent to animation-direction: alternate) odd (equivalent to animation-direction: alternate-reverse) Not only this communicates the end result much better, but it’s also more extensible. For example, if in the future it turns out that reversing every 3rd iteration is a common use case, it will be much easier to add expressions to it, similar to the ones :nth-child() accepts. I knew before proposing it that it’s too late for such drastic backwards-incompatible changes in the Animations module , however I thought it’s so much better that it’s worth fighting for. After all, animation-direction isn’t really used that much in the wild. Unfortunately, it seems that only me and Sylvain thought it’s better, and even he was reluctant to support the change , due to the backwards compatibility issues. So, I started wondering if it’s really as much better as I think. ¿What are your thoughts? ¿Would it make it simpler for you to understand and/or teach? Author feedback is immensely useful for standardization, so please, ¡voice your opinion! Even without justifying it if you don’t have the time or energy. Gathering opinions is incredibly useful. TL;DR ¿Is alternate reverse and reverse alternate (either would be allowed) a better value for animation-direction over alternate-reverse ? ¿If so, should redundant combinations of normal with alternate or reverse also be allowed, such as normal alternate ? ¿Or maybe we should ditch it altogether and replace it with animation-reverse , accepting values of none , all , even , odd ? Side note: If you’re wondering about the flipped question and exclamation marks (¿¡) it’s because I believe they improve the usability of the language if widely adopted, so I’m doing my part for it ;) And no, I don’t speak Spanish.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Text masking — The standards way</title>
  <link>https://lea.verou.me/2012/05/text-masking-the-standards-way/</link>
  <pubDate>Sun, 17 May 2026 01:45:19 +0200</pubDate>
  <description>As much as I like .net magazine, I was recently outraged by their “ Texturizing Web Type ” article. It features a way to apply a texture to text with -webkit-mask-image , presenting it as an experimental CSS property and misleading readers. There are even -moz-, -o- and -ms- prefixes for something that is not present in any specification, and is therefore unlikely to ever be supported by any non-WebKit browser, which further contributes to the misdirection. A while back, I wrote about how detrimental to our work and industry such proprietary features can be. A common response to such complaints is that they are merely philosophical and who cares if the feature works right now and degrades gracefully. This argument could be valid for some cases, when the style is just a minor, gracefully degrading enhancement and no standards compliant alternative is present (for example, I’ve used ::-webkit-scrollbar styles myself). However, this is not the case here. We have had a standards compliant alternative for this for the past 11 years and it’s called SVG. It can also do much more than masking, if you give it a chance. Here’s an example of texturized text with SVG: Edit: Thanks to @devongovett’s improvements , the code is now simpler &amp; shorter. Yes, the syntax might be more unwieldy but it works in a much wider range of browsers: Chrome, Safari, Firefox, IE9, Opera . Also, it’s trivial to make a script that generates the SVG markup from headings and applies the correct measurements for each one. When WebKit fixes this bug , we can even move the pattern to a separate SVG file and reference it from there. In case you’re wondering about semantics, the element is considered “flow content” and is therefore allowed in heading elements. Also, even if search engines don’t understand inline SVG, they will just ignore the tags and still see the content inside the element. Based on that, you could even make it degrade gracefully in IE8, as long as you include the HTML5 fix for the element. Then the CSS rules for the typography will still apply. You’ll just need to conditionally hide the , since IE8 displays a broken image there (a little known fact is that, in HTML, is basically equivalent to , so IE8 treats it as such) . Credits to David Storey ’s original example that inspired this.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Poll: Is animation-direction a good idea?</title>
  <link>https://lea.verou.me/2012/05/is-animation-direction-a-good-idea/</link>
  <pubDate>Sun, 17 May 2026 01:45:19 +0200</pubDate>
  <description>animation-direction? Lets assume you have a CSS animation for background-color that goes from a shade of yellow (#cc0) to a shade of blue (#079) and repeats indefinitely. The code could be something like this: @keyframes color { from { background: #cc0 } to { background: #079 } } div { animation: color 3s infinite; } If we linearize that animation, the progression over time goes like this (showing 3 iterations): As you can see, the change from the end state to the beginning state of a new iteration is quite abrupt. You could change your keyframes to mitigate this, but theres a better way. A property with the name animation-direction gives a degree of control on the direction of the iterations to smooth this out. It also reverses your timing functions, which makes it even smoother. In early drafts, only the values normal and alternate were allowed. normal results in the behavior described above, whereas alternate flips every other iteration (the 2nd, the 4th, the 6th and so on), resulting in a progression like this (note how the 2nd iteration has been reversed): The latest draft also adds two more values: reverse which reverses every iteration and alternate-reverse , which is the combination of both reverse and alternate . Here is a summary of what kind of progression these four values would create for the animation above: The problem I was excited to see that reverse and alternate-reverse were finally added to the spec, but something in the syntax just didnt click. I initially thought the reason was that these four values essentially set 2 toggles: Reverse all iterations? yes/no Reverse even iterations? yes/no so its pointless cognitive overhead to remember four distinct values. I proposed that they should be split in two keywords instead, which would even result to a simpler grammar too. The proposal was well received by one of the co-editors of the animations spec ( Sylvain Galineau ), but there was a dilemma as to whether mixing normal with alternate or reverse would make it easier to learn or more confusing. This is a point where your opinion would be quite useful. Would you expect the following to work, or would you find them confusing? animation-direction: normal alternate; /* Equivalent to animation-direction: alternate; */ animation-direction: alternate normal; /* Equivalent to animation-direction: alternate; */ animation-direction: normal reverse; /* Equivalent to animation-direction: reverse; */ animation-direction: reverse normal; /* Equivalent to animation-direction: reverse; */ A better (?) idea At some point, in the middle of writing this blog post (I started it yesterday), while gazing at the graphic above, I had a lightbulb moment. These values are not two toggles! All four of them control one thing: which iterations are reversed : normal reverses no iterations reverse reverses all iterations alternate reverses even iterations alternate-reverse reverses odd iterations The reason its so confusing and it took me so long to realize myself, is that the mental model suggested by these keywords is detached from the end result, especially in the case of alternate-reverse . You have to realize that it works as if both alternate and reverse were applied in sequence, so reverse first reverses all iterations and then alternate reverses the even ones. Even iterations are reversed twice , and are therefore equivalent to the original direction. This leaves the odd ones as being reversed. Its basically a double negative, making it hard to visualize and understand. I thought that a property that would reflect this in a much more straightforward way, would be animation-reverse (or animation-iteration-reverse ), accepting the following values: none (equivalent to animation-direction: normal) all (equivalent to animation-direction: reverse) even (equivalent to animation-direction: alternate) odd (equivalent to animation-direction: alternate-reverse) Not only this communicates the end result much better, but its also more extensible. For example, if in the future it turns out that reversing every 3rd iteration is a common use case, it will be much easier to add expressions to it, similar to the ones :nth-child() accepts. I knew before proposing it that its too late for such drastic backwards-incompatible changes in the Animations module , however I thought its so much better that its worth fighting for. After all, animation-direction isnt really used that much in the wild. Unfortunately, it seems that only me and Sylvain thought its better, and even he was reluctant to support the change , due to the backwards compatibility issues. So, I started wondering if its really as much better as I think. What are your thoughts? Would it make it simpler for you to understand and/or teach? Author feedback is immensely useful for standardization, so please, voice your opinion! Even without justifying it if you dont have the time or energy. Gathering opinions is incredibly useful. TL;DR Is alternate reverse and reverse alternate (either would be allowed) a better value for animation-direction over alternate-reverse ? If so, should redundant combinations of normal with alternate or reverse also be allowed, such as normal alternate ? Or maybe we should ditch it altogether and replace it with animation-reverse , accepting values of none , all , even , odd ? Side note: If youre wondering about the flipped question and exclamation marks () its because I believe they improve the usability of the language if widely adopted, so Im doing my part for it ;) And no, I dont speak Spanish.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Text masking The standards way</title>
  <link>https://lea.verou.me/2012/05/text-masking-the-standards-way/</link>
  <pubDate>Sun, 17 May 2026 01:45:19 +0200</pubDate>
  <description>As much as I like .net magazine, I was recently outraged by their Texturizing Web Type article. It features a way to apply a texture to text with -webkit-mask-image , presenting it as an experimental CSS property and misleading readers. There are even -moz-, -o- and -ms- prefixes for something that is not present in any specification, and is therefore unlikely to ever be supported by any non-WebKit browser, which further contributes to the misdirection. A while back, I wrote about how detrimental to our work and industry such proprietary features can be. A common response to such complaints is that they are merely philosophical and who cares if the feature works right now and degrades gracefully. This argument could be valid for some cases, when the style is just a minor, gracefully degrading enhancement and no standards compliant alternative is present (for example, Ive used ::-webkit-scrollbar styles myself). However, this is not the case here. We have had a standards compliant alternative for this for the past 11 years and its called SVG. It can also do much more than masking, if you give it a chance. Heres an example of texturized text with SVG: Edit: Thanks to @devongovetts improvements , the code is now simpler &amp; shorter. Yes, the syntax might be more unwieldy but it works in a much wider range of browsers: Chrome, Safari, Firefox, IE9, Opera . Also, its trivial to make a script that generates the SVG markup from headings and applies the correct measurements for each one. When WebKit fixes this bug , we can even move the pattern to a separate SVG file and reference it from there. In case youre wondering about semantics, the element is considered flow content and is therefore allowed in heading elements. Also, even if search engines dont understand inline SVG, they will just ignore the tags and still see the content inside the element. Based on that, you could even make it degrade gracefully in IE8, as long as you include the HTML5 fix for the element. Then the CSS rules for the typography will still apply. Youll just need to conditionally hide the , since IE8 displays a broken image there (a little known fact is that, in HTML, is basically equivalent to , so IE8 treats it as such) . Credits to David Storey s original example that inspired this.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>How I got into web development — the long version</title>
  <link>https://lea.verou.me/2012/05/how-i-got-into-web-development-the-long-version/</link>
  <pubDate>Sun, 17 May 2026 01:45:18 +0200</pubDate>
  <description>I’m often asked how I got into web development, especially from people that haven’t met many women in the field. Other times it’s people with little kids and they are asking for guidance about how to steer them into programming. I promised them that I would write a long post about it at some point, and now that I’m in the verge of some big changes in my life, I’ve started reflecting on the fascinating journey that got me here. Rebecca Murphey wrote something similar a while back (albeit much shorter and less detailed), and I think it would be nice if more people in the field started posting their stories, especially women. I sure would find them interesting and if you give it a shot, you’ll see it’s quite enjoyable too. I sure had a blast writing this, although it was a bit hard to hit the “Publish” button afterwards. Keep in mind that this is just my personal story (perhaps in excruciating detail). I’m not going to attempt to give any advice, and I’m not suggesting that my path was ideal. I’ve regretted some of my decisions myself, whereas some others proved to be great, although they seemed like failures at the time. I think I was quite lucky in how certain things turned out and I thank the Flying Spaghetti Monster daily for them. Warning: This is going to be a very long read (over 3000 words) and there is no tl;dr. Childhood (1986-1998) I was born on June 13th, 1986. I grew up in a Greek island called Lesbos (yes, the island where the word “lesbian” comes from , in case you were wondering), in the small town of Kalloni . I didn’t have a computer as a kid, but I always loved making things. I had no siblings, so my childhood was mostly spent playing solitarily with paper, fabric, staples, scissors and the like. I was making all kinds of stuff: Little books, wallets, bags, pillows, anything I could come up with that was doable with my limited set of tools and materials. I also loved drawing. I had typical toys as well (legos, dolls, playmobil, cars, teddy bears) but the prevailing tendency in my childhood was making stuff . I wasn’t particularly interested in taking things apart to see how they worked, I just liked making new things. I had never used a computer until I was around 10. We spent Christmas with an uncle of mine and his family in Athens. That uncle was working at Microsoft Hellas, and had a Windows 95 machine in his apartment. I got hooked from the first moment I used that computer. I didn’t do anything particularly interesting in it, just played around with MS Paint and some other equally mundane applications. However, for me it was so fascinating that I spent most of my Christmas vacation that year exploring Windows 95. After I returned to Lesbos, I knew I badly wanted a computer for myself. However, computers were quite expensive back then, so I didn’t get one immediately, even though my family was quite well off. My father started taking me to his job’s offices on weekends, and I spent hours every time on a Windows 3.1 machine, exploring it, mostly drawing on its paint app. In 1997, my mother finally bought me a computer. It cost around 700K drachmas (around €2000?) which was much more at the time than it is today. It was a Pentium MMX at 233MHz with 32MB of RAM and a 3.1GB hard drive, which was quite good at the time. I was so looking forward for it to arrive, and when it did, I spent every afternoon using it, from the moment I got back from school, until late at night. The only times I didn’t use my computer was when I was reading computer books or magazines or studying for school. In a year, I had become quite proficient about how its OS worked (Windows 95), editing the registry, trying to learn DOS (its command line). I also exercised my creativity by making magazines and newspapers in Microsoft Word. I’m quite surprised I didn’t break it, even though I was experimenting with anything I could get my cursor on. Unfortunately, my computer fascination was largely solitary. There were no other geeks in my little town I could relate to, which I guess made me even more of an introvert. The only people reading my MS Word-generated newspaper were me and a friend of mine. During my years in Lesbos, I only met 2 other kinda geeky kids, and we didn’t really hit it off. One of them was living too far, the other was kind of annoying. :P The former however gave me his fonts, which I was really grateful for. I loved fonts. I didn’t have any typographic sophistication, so I loved about every font, but I remember desperately wanting to make my own. Unfortunately, I never pursued that, as I couldn’t find any font creation software until very recently. In late 1997, we visited some relatives in a NYC suburb to spend Christmas there. It was my first time in the US and I fell in love with the place. My uncle, knowing my computer obsession took me to a big computer store called CompUSA. I was like a kid in a candy store! The software that caught my eye the most was called “ Mutimedia Fusion ”. It was a graphical IDE that allowed you to make applications (mostly games and screensavers, but you could potentially make anything) without writing any code. The thought processes involved were the same as in programming, but instead of typing commands, you picked them from menus or wrote mathematical expressions through a GUI. You could even go online and get new plugins that added functionality, but my access to the internet in my little town was very limited. I got super excited. The idea of being able to make my very own programs, was too good to be true. I convinced my mother to buy it for me and thankfully, she did. For the year that followed, my afternoons and weekends became way more creative. I wasn’t interested in making games, but more in utility applications. Things that were going to be useful for my imaginary users. My biggest app back then was something that allowed you to draw different kinds of grids (from horizontal and vertical grids to simple 3d-like kinds of grids), with different parameters, or even mix them together and overlay them over an image. Anything that combined programming with graphics was doubly fascinating for me. My access to the internet was limited, so I couldn’t share my creations with anybody. What kept me going was the idea that if I make something amazing, it will get popular and people will use it. I had no idea how that would happen, but it was useful as a carrot in front of me that made me constantly strive to improve. We had dial-up, but due to technical issues, I could only connect about 10% of the times I tried it, and even then I had to keep it short as it was quite expensive. I spent my limited time online downloading plugins for Multimedia Fusion, searching anything I could come up with in Altavista and perusing IRC chatrooms with Microsoft Comic Chat. Adolescence (1998-2004) After a year of making applications with Multimedia Fusion, I wanted something more flexible and powerful. I wanted to finally learn a programming language. My Microsoft uncle sent me a free copy of Visual Studio, so I was trying to decide which “Visual Whatever” language was best to start with. Having read that C++ was “teh pro stuff”, I got a book about Visual C++. Unfortunately, I couldn’t understand much. I decided that it was probably too early for me and C++, so I got a Visual Basic 6 book. It was about 10cm thick, detailing everything you could ever possibly want to learn about Visual Basic. Thankfully, Visual Basic didn’t prove so hard, so I started with it, making small apps and finally ported my grid application from Multimedia Fusion to Visual Basic 6. I had a very fun and creative 3 years, full of new knowledge and exercise for the mind. Unfortunately, when I reached 15, I realized that boys in my little town weren’t really into geeky girls. I decided that if I wanted a boyfriend, I should quit programming (if any geeky teenage girls are reading this: Just be patient. It gets better, you can’t imagine how much). It “helped” that my computer was broken during the summer and I had to wait for it to come back, so I had to find other things to do in the meantime. Unable to code, I pursued other geeky interests, such as mobile phones and mathematics, which I guess shows that no matter how much you try, you can’t escape who you are. In retrospect, this helped me, as I got some pretty good distinctions in the various stages of the national mathematical competitions, up to 2nd place nationally for two years in a row (these competitions had 4 stages. I failed the preliminary contest for the Balkan Mathematical Olympiad, so I never went there.). I was fascinated by Number Theory and started wanting to become a mathematician, rather than a programmer. Sometime around then I also moved from my small town to Athens, which I wanted to do since childhood. When the time of career decisions came, I chickened out. I knew that if I became a mathematician and failed at research, I would end up teaching mathematics in a high school. I didn’t want that, so I picked a “safer” career path. Since my grades were very good, I went to study Electrical and Computer Engineering, which is a profession held in very high esteem in Greece, about as much as lawyers and doctors. I told myself that I would probably find it interesting, as it would involve lots of mathematics and programming. I was wrong. Adulthood (2004-Today) I was away from Athens, in a city that most Greeks love (Thessaloniki). However, I found it cold, gray, old and with hordes of cockroaches. I hated it with a passion. I also hated my university. It involved little coding and little theoretical Mathematics, the kind that I loved. Most of it was physics and branches of Mathematics I didn’t like, such as linear algebra. It only had two coding courses, both of which were quite mundane and lacked any kind of creativity. Moreover, most of my fellow students had perviously wanted to become doctors and failed medical school so they just went for the next highly respected option. They had no interest in technology and their main life goals were job security, making money and be respected. I felt more lonely than ever. After the first semester, I slowly stopped going to lectures and eventually gave up socializing with them. Not going to lectures is not particularly unusual for a university student in Greece. Most Greeks do it after a while, since attendance is not compulsory and Greek universities are free (as in beer). As long as you pass your exams every semester and do your homework, you can still get a degree just fine. During my first summer as a university student, we decided with my then boyfriend to make an online forum. We were both big fans of online forums and we wanted to make something better. He set up the forum software in an afternoon (using SMF ) and then we started customizing it. I didn’t know much about web development back then, so I constrained myself to helping with images and settings. After 2 months, the forum grew to around 200 members, and we decided to switch to the more professional (and costly) forum software, vBulletin . It was probably too early, but the signs were positive, so we thought better earlier than later. The migration took 2-3 days of nonstop work, during which we took turns in sleeping and worked the entire time that we were awake. We wanted everything to be perfect, even the forum theme should be as similar to the old one as possible. I had a more involved role in this, and I even started learning a bit of PHP while trying to install some “mods” (modifications to the vBulletin source code that people posted). Due to my programming background, I caught up with it quite easily and after a few months, I was the only one fiddling with code on the website. I was learning more and more about PHP, HTML, CSS and (later) JavaScript. That online forum was my primary playground, where I put my newly acquired knowledge into practice. Throughout these years, I released quite a few of my own vBulletin mods , many of which are still in use in vBulletin forums worldwide. Having spent so many years making apps that nobody used, I found it fascinating that you can make something and have people use it only a few hours later. By the end of 2005, I started undertaking some very small scale client work, most (or all) of which doesn’t exist anymore. I was not only interested in code, but also in graphic design. I started buying lots of books, both about the languages involved and graphic design principles. The pace of learning new things back then was crazy, almost on par with my early adolescence years. In late 2006, I decided I couldn’t take it any more with my university. I had absolutely no interest in Electrical Engineering, and my web development work had consumed me entirely. I didn’t want to give up on higher education, so I tried to decide where I should switch to. Computer Science was the obvious choice, but having grown up with civil engineer parents, I didn’t want to give up on engineering just yet (strangely, CS is not considered engineering in Greece, it’s considered a science, kinda like Mathematics). I also loved graphic design, so I considered going to a graphic design school, but there are no respected graphic design universities in Greece and I wasn’t ready to study abroad. I was also in a long term relationship in Greece, which I didn’t want to give up on. I decided to go with Architecture, although I had no interest in buildings. The idea was that it bridges engineering and art, so it would satisfy both of my interests. Unfortunately, since I hadn’t taken drawing classes in high school, I had to take the entire national university placement exams (Πανελλήνιες), again, including courses I aced the first time, such as Mathematics. I was supposed to spend the first half of 2007 preparing for these exams, but instead I spent most of it freelancing and learning more about web development. I did quite well on the courses I had been previously examined on (although not as good as the first time), but borderline failed freehand drawing. Passing freehand drawing was a requirement for Architecture, so that was out of the question now. This seemed like a disaster at the time, but in retrospect, I’m very grateful to the grader that failed me. I would’ve been utterly miserable in Architecture. Not wanting to go back to EE, I took a look at my options. My mother suggested Computer Science and even though I was still a bit reluctant, I put it in my application. I picked a CS school that seemed more programming-oriented, as I didn’t want to have many physics, computer architecture and circuits courses again. When the results came out, I had been placed there. It turned out to be one of my best decisions. I could get good grades on most of the courses with hardly any studying, as I knew lots of the stuff already. I also learned a bunch of useful new things. I can’t say that everything I learned was useful for my work, but it was enough to make it worth it. In mid 2007, the online forum we built had grown quite a lot. We decided to make a company around it, in order to be able to accept more high-end advertising. We had many dreams about expanding what it does, most of which never got materialized. In 2008, after a long time of back and forth, we officially registered a company for it so I stopped freelancing and focused solely on that. It wasn’t easy, but eventually it started generating a very moderate income. I decided to start a Greek blog to post about my CSS and JS discoveries, but it didn’t go very well. After a dozen posts or so, I decided to close it down, and start a new one, in English this time. It turned out that developers abroad were more interested in what I had to say, so I got my first conference invitation in 2010, to speak in a new Polish conference called Front-Trends . When I got the invitation email, I couldn’t believe my eyes. Why would someone want me to speak at a conference? I wasn’t that good! How would I speak in front of all these people? It even crossed my mind that it might be a joke, but they had confirmed speakers like Douglas Crockford, Jake Archibald, Jeremy Keith and Paul Bakaus. I told my inner shy self to shut up, and enthusiastically agreed to speak there. I spent the 8 months until that conference stressing about my presentation. I had never been to a conference outside Greece, and the only Greek conference I had attended was a graphic design one. I had only spoken once before, to an audience of around 30 people in a barcamp-style event. I decided that I didn’t want my first web development conference to be the one I speak at, so I bought a ticket for Fronteers 2010 . It had a great line-up and was quite affordable (less than €300 for a ticket). I convinced 3 of my friends to come with me (for vacation), and we shared a quadruple hotel room, so the accommodation ended up not costing too much either. It was an amazing experience that I will never forget. I met people I admired and only knew through their work online. It was the first time in my life that I was face to face with people that really shared the same interests. I even met my partner to date there. Until today, Fronteers is my favorite conference. Partly because it was my first, partly because it’s a truly great conference with a very strong sense of community. There was a talk or two at Fronteers that year, which were criticized for showing things that most people in the audience already knew. This became my worst fear about giving talks. Until today, I always try to add nuggets of more advanced techniques in my talks, to avoid getting that kind of reaction, and it works quite well. I remember going back home after Fronteers and pretty much changing all my slides for my upcoming talk. I trashed my death-by-powerpoint kind of slides and my neat bulleted lists and made a web-based slideshow with interactive examples for everything I wanted to show. I was incredibly nervous before and during my Front-Trends talk, so I kept mumbling and confusing my words. However, despite what I thought throughout, the crowd there loved it. The comments on twitter were enthusiastic! Many people even said it was the best talk of the conference. That first talk was the beginning of a roller-coaster that I just can’t describe. I started getting more invitations for talks, articles, workshops and many other kinds of fascinating things. I met amazing people along the way. Funny, like-minded, intelligent people. To this day, I think that getting in this industry has been the best thing in my life. I have experienced no sexism or other discrimination, nothing negative, just pure fun, creativity and a sense that I belong in a community with like-minded people that understand me. It’s been great, and I hope it continues to be like this for a very long time. Thank you all.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>How I got into web development the long version</title>
  <link>https://lea.verou.me/2012/05/how-i-got-into-web-development-the-long-version/</link>
  <pubDate>Sun, 17 May 2026 01:45:18 +0200</pubDate>
  <description>Im often asked how I got into web development, especially from people that havent met many women in the field. Other times its people with little kids and they are asking for guidance about how to steer them into programming. I promised them that I would write a long post about it at some point, and now that Im in the verge of some big changes in my life, Ive started reflecting on the fascinating journey that got me here. Rebecca Murphey wrote something similar a while back (albeit much shorter and less detailed), and I think it would be nice if more people in the field started posting their stories, especially women. I sure would find them interesting and if you give it a shot, youll see its quite enjoyable too. I sure had a blast writing this, although it was a bit hard to hit the Publish button afterwards. Keep in mind that this is just my personal story (perhaps in excruciating detail). Im not going to attempt to give any advice, and Im not suggesting that my path was ideal. Ive regretted some of my decisions myself, whereas some others proved to be great, although they seemed like failures at the time. I think I was quite lucky in how certain things turned out and I thank the Flying Spaghetti Monster daily for them. Warning: This is going to be a very long read (over 3000 words) and there is no tl;dr. Childhood (1986-1998) I was born on June 13th, 1986. I grew up in a Greek island called Lesbos (yes, the island where the word lesbian comes from , in case you were wondering), in the small town of Kalloni . I didnt have a computer as a kid, but I always loved making things. I had no siblings, so my childhood was mostly spent playing solitarily with paper, fabric, staples, scissors and the like. I was making all kinds of stuff: Little books, wallets, bags, pillows, anything I could come up with that was doable with my limited set of tools and materials. I also loved drawing. I had typical toys as well (legos, dolls, playmobil, cars, teddy bears) but the prevailing tendency in my childhood was making stuff . I wasnt particularly interested in taking things apart to see how they worked, I just liked making new things. I had never used a computer until I was around 10. We spent Christmas with an uncle of mine and his family in Athens. That uncle was working at Microsoft Hellas, and had a Windows 95 machine in his apartment. I got hooked from the first moment I used that computer. I didnt do anything particularly interesting in it, just played around with MS Paint and some other equally mundane applications. However, for me it was so fascinating that I spent most of my Christmas vacation that year exploring Windows 95. After I returned to Lesbos, I knew I badly wanted a computer for myself. However, computers were quite expensive back then, so I didnt get one immediately, even though my family was quite well off. My father started taking me to his jobs offices on weekends, and I spent hours every time on a Windows 3.1 machine, exploring it, mostly drawing on its paint app. In 1997, my mother finally bought me a computer. It cost around 700K drachmas (around 2000?) which was much more at the time than it is today. It was a Pentium MMX at 233MHz with 32MB of RAM and a 3.1GB hard drive, which was quite good at the time. I was so looking forward for it to arrive, and when it did, I spent every afternoon using it, from the moment I got back from school, until late at night. The only times I didnt use my computer was when I was reading computer books or magazines or studying for school. In a year, I had become quite proficient about how its OS worked (Windows 95), editing the registry, trying to learn DOS (its command line). I also exercised my creativity by making magazines and newspapers in Microsoft Word. Im quite surprised I didnt break it, even though I was experimenting with anything I could get my cursor on. Unfortunately, my computer fascination was largely solitary. There were no other geeks in my little town I could relate to, which I guess made me even more of an introvert. The only people reading my MS Word-generated newspaper were me and a friend of mine. During my years in Lesbos, I only met 2 other kinda geeky kids, and we didnt really hit it off. One of them was living too far, the other was kind of annoying. :P The former however gave me his fonts, which I was really grateful for. I loved fonts. I didnt have any typographic sophistication, so I loved about every font, but I remember desperately wanting to make my own. Unfortunately, I never pursued that, as I couldnt find any font creation software until very recently. In late 1997, we visited some relatives in a NYC suburb to spend Christmas there. It was my first time in the US and I fell in love with the place. My uncle, knowing my computer obsession took me to a big computer store called CompUSA. I was like a kid in a candy store! The software that caught my eye the most was called Mutimedia Fusion . It was a graphical IDE that allowed you to make applications (mostly games and screensavers, but you could potentially make anything) without writing any code. The thought processes involved were the same as in programming, but instead of typing commands, you picked them from menus or wrote mathematical expressions through a GUI. You could even go online and get new plugins that added functionality, but my access to the internet in my little town was very limited. I got super excited. The idea of being able to make my very own programs, was too good to be true. I convinced my mother to buy it for me and thankfully, she did. For the year that followed, my afternoons and weekends became way more creative. I wasnt interested in making games, but more in utility applications. Things that were going to be useful for my imaginary users. My biggest app back then was something that allowed you to draw different kinds of grids (from horizontal and vertical grids to simple 3d-like kinds of grids), with different parameters, or even mix them together and overlay them over an image. Anything that combined programming with graphics was doubly fascinating for me. My access to the internet was limited, so I couldnt share my creations with anybody. What kept me going was the idea that if I make something amazing, it will get popular and people will use it. I had no idea how that would happen, but it was useful as a carrot in front of me that made me constantly strive to improve. We had dial-up, but due to technical issues, I could only connect about 10% of the times I tried it, and even then I had to keep it short as it was quite expensive. I spent my limited time online downloading plugins for Multimedia Fusion, searching anything I could come up with in Altavista and perusing IRC chatrooms with Microsoft Comic Chat. Adolescence (1998-2004) After a year of making applications with Multimedia Fusion, I wanted something more flexible and powerful. I wanted to finally learn a programming language. My Microsoft uncle sent me a free copy of Visual Studio, so I was trying to decide which Visual Whatever language was best to start with. Having read that C++ was teh pro stuff, I got a book about Visual C++. Unfortunately, I couldnt understand much. I decided that it was probably too early for me and C++, so I got a Visual Basic 6 book. It was about 10cm thick, detailing everything you could ever possibly want to learn about Visual Basic. Thankfully, Visual Basic didnt prove so hard, so I started with it, making small apps and finally ported my grid application from Multimedia Fusion to Visual Basic 6. I had a very fun and creative 3 years, full of new knowledge and exercise for the mind. Unfortunately, when I reached 15, I realized that boys in my little town werent really into geeky girls. I decided that if I wanted a boyfriend, I should quit programming (if any geeky teenage girls are reading this: Just be patient. It gets better, you cant imagine how much). It helped that my computer was broken during the summer and I had to wait for it to come back, so I had to find other things to do in the meantime. Unable to code, I pursued other geeky interests, such as mobile phones and mathematics, which I guess shows that no matter how much you try, you cant escape who you are. In retrospect, this helped me, as I got some pretty good distinctions in the various stages of the national mathematical competitions, up to 2nd place nationally for two years in a row (these competitions had 4 stages. I failed the preliminary contest for the Balkan Mathematical Olympiad, so I never went there.). I was fascinated by Number Theory and started wanting to become a mathematician, rather than a programmer. Sometime around then I also moved from my small town to Athens, which I wanted to do since childhood. When the time of career decisions came, I chickened out. I knew that if I became a mathematician and failed at research, I would end up teaching mathematics in a high school. I didnt want that, so I picked a safer career path. Since my grades were very good, I went to study Electrical and Computer Engineering, which is a profession held in very high esteem in Greece, about as much as lawyers and doctors. I told myself that I would probably find it interesting, as it would involve lots of mathematics and programming. I was wrong. Adulthood (2004-Today) I was away from Athens, in a city that most Greeks love (Thessaloniki). However, I found it cold, gray, old and with hordes of cockroaches. I hated it with a passion. I also hated my university. It involved little coding and little theoretical Mathematics, the kind that I loved. Most of it was physics and branches of Mathematics I didnt like, such as linear algebra. It only had two coding courses, both of which were quite mundane and lacked any kind of creativity. Moreover, most of my fellow students had perviously wanted to become doctors and failed medical school so they just went for the next highly respected option. They had no interest in technology and their main life goals were job security, making money and be respected. I felt more lonely than ever. After the first semester, I slowly stopped going to lectures and eventually gave up socializing with them. Not going to lectures is not particularly unusual for a university student in Greece. Most Greeks do it after a while, since attendance is not compulsory and Greek universities are free (as in beer). As long as you pass your exams every semester and do your homework, you can still get a degree just fine. During my first summer as a university student, we decided with my then boyfriend to make an online forum. We were both big fans of online forums and we wanted to make something better. He set up the forum software in an afternoon (using SMF ) and then we started customizing it. I didnt know much about web development back then, so I constrained myself to helping with images and settings. After 2 months, the forum grew to around 200 members, and we decided to switch to the more professional (and costly) forum software, vBulletin . It was probably too early, but the signs were positive, so we thought better earlier than later. The migration took 2-3 days of nonstop work, during which we took turns in sleeping and worked the entire time that we were awake. We wanted everything to be perfect, even the forum theme should be as similar to the old one as possible. I had a more involved role in this, and I even started learning a bit of PHP while trying to install some mods (modifications to the vBulletin source code that people posted). Due to my programming background, I caught up with it quite easily and after a few months, I was the only one fiddling with code on the website. I was learning more and more about PHP, HTML, CSS and (later) JavaScript. That online forum was my primary playground, where I put my newly acquired knowledge into practice. Throughout these years, I released quite a few of my own vBulletin mods , many of which are still in use in vBulletin forums worldwide. Having spent so many years making apps that nobody used, I found it fascinating that you can make something and have people use it only a few hours later. By the end of 2005, I started undertaking some very small scale client work, most (or all) of which doesnt exist anymore. I was not only interested in code, but also in graphic design. I started buying lots of books, both about the languages involved and graphic design principles. The pace of learning new things back then was crazy, almost on par with my early adolescence years. In late 2006, I decided I couldnt take it any more with my university. I had absolutely no interest in Electrical Engineering, and my web development work had consumed me entirely. I didnt want to give up on higher education, so I tried to decide where I should switch to. Computer Science was the obvious choice, but having grown up with civil engineer parents, I didnt want to give up on engineering just yet (strangely, CS is not considered engineering in Greece, its considered a science, kinda like Mathematics). I also loved graphic design, so I considered going to a graphic design school, but there are no respected graphic design universities in Greece and I wasnt ready to study abroad. I was also in a long term relationship in Greece, which I didnt want to give up on. I decided to go with Architecture, although I had no interest in buildings. The idea was that it bridges engineering and art, so it would satisfy both of my interests. Unfortunately, since I hadnt taken drawing classes in high school, I had to take the entire national university placement exams (), again, including courses I aced the first time, such as Mathematics. I was supposed to spend the first half of 2007 preparing for these exams, but instead I spent most of it freelancing and learning more about web development. I did quite well on the courses I had been previously examined on (although not as good as the first time), but borderline failed freehand drawing. Passing freehand drawing was a requirement for Architecture, so that was out of the question now. This seemed like a disaster at the time, but in retrospect, Im very grateful to the grader that failed me. I wouldve been utterly miserable in Architecture. Not wanting to go back to EE, I took a look at my options. My mother suggested Computer Science and even though I was still a bit reluctant, I put it in my application. I picked a CS school that seemed more programming-oriented, as I didnt want to have many physics, computer architecture and circuits courses again. When the results came out, I had been placed there. It turned out to be one of my best decisions. I could get good grades on most of the courses with hardly any studying, as I knew lots of the stuff already. I also learned a bunch of useful new things. I cant say that everything I learned was useful for my work, but it was enough to make it worth it. In mid 2007, the online forum we built had grown quite a lot. We decided to make a company around it, in order to be able to accept more high-end advertising. We had many dreams about expanding what it does, most of which never got materialized. In 2008, after a long time of back and forth, we officially registered a company for it so I stopped freelancing and focused solely on that. It wasnt easy, but eventually it started generating a very moderate income. I decided to start a Greek blog to post about my CSS and JS discoveries, but it didnt go very well. After a dozen posts or so, I decided to close it down, and start a new one, in English this time. It turned out that developers abroad were more interested in what I had to say, so I got my first conference invitation in 2010, to speak in a new Polish conference called Front-Trends . When I got the invitation email, I couldnt believe my eyes. Why would someone want me to speak at a conference? I wasnt that good! How would I speak in front of all these people? It even crossed my mind that it might be a joke, but they had confirmed speakers like Douglas Crockford, Jake Archibald, Jeremy Keith and Paul Bakaus. I told my inner shy self to shut up, and enthusiastically agreed to speak there. I spent the 8 months until that conference stressing about my presentation. I had never been to a conference outside Greece, and the only Greek conference I had attended was a graphic design one. I had only spoken once before, to an audience of around 30 people in a barcamp-style event. I decided that I didnt want my first web development conference to be the one I speak at, so I bought a ticket for Fronteers 2010 . It had a great line-up and was quite affordable (less than 300 for a ticket). I convinced 3 of my friends to come with me (for vacation), and we shared a quadruple hotel room, so the accommodation ended up not costing too much either. It was an amazing experience that I will never forget. I met people I admired and only knew through their work online. It was the first time in my life that I was face to face with people that really shared the same interests. I even met my partner to date there. Until today, Fronteers is my favorite conference. Partly because it was my first, partly because its a truly great conference with a very strong sense of community. There was a talk or two at Fronteers that year, which were criticized for showing things that most people in the audience already knew. This became my worst fear about giving talks. Until today, I always try to add nuggets of more advanced techniques in my talks, to avoid getting that kind of reaction, and it works quite well. I remember going back home after Fronteers and pretty much changing all my slides for my upcoming talk. I trashed my death-by-powerpoint kind of slides and my neat bulleted lists and made a web-based slideshow with interactive examples for everything I wanted to show. I was incredibly nervous before and during my Front-Trends talk, so I kept mumbling and confusing my words. However, despite what I thought throughout, the crowd there loved it. The comments on twitter were enthusiastic! Many people even said it was the best talk of the conference. That first talk was the beginning of a roller-coaster that I just cant describe. I started getting more invitations for talks, articles, workshops and many other kinds of fascinating things. I met amazing people along the way. Funny, like-minded, intelligent people. To this day, I think that getting in this industry has been the best thing in my life. I have experienced no sexism or other discrimination, nothing negative, just pure fun, creativity and a sense that I belong in a community with like-minded people that understand me. Its been great, and I hope it continues to be like this for a very long time. Thank you all.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Pure CSS scrolling shadows with background-attachment: local</title>
  <link>https://lea.verou.me/2012/04/background-attachment-local/</link>
  <pubDate>Sun, 17 May 2026 01:45:17 +0200</pubDate>
  <description>A few days ago, the incredibly talented Roman Komarov posted an experiment of his with pure CSS “scrolling shadows” . If you’re using Google Reader, you are probably familiar with the effect: In Roman’s experiment, he is using absolutely positioned pseudoelements to cover the shadows (which are basically radial gradients as background images), taking advantage of the fact that when you scroll a scrollable container, its background does not scroll with it, but absolutely positioned elements within do. Therefore, when you scroll, the shadows are no longer obscured and can show through. Furthermore, these pseudoelements are linear gradients from white to transparent, so that these shadows are uncovered smoothly. When I saw Roman’s demo, I started wondering whether this is possible with no extra containers at all (pseudoelements included). It seemed like a perfect use case for background-attachment: local . Actually, it was the first real use case for it I had ever came up with or seen. “background-attachment… what? I only know scroll and fixed!” scroll and fixed were the only values for background-attachment back in the days of CSS 2.1. scroll is the initial value and positions the background relative to the element it’s applied on , whereas fixed positions it relative to the viewport , resulting in the background staying in place when the page was scrolled. As a result of these definitions, when only a part of the page was scrollable (e.g. a with overflow: auto ), its background did not scroll when the container itself was scrolled. In Backgrounds &amp; Borders Level 3 , a new value was added to lift this restriction: local . When background-attachment: local is applied, the background is positioned relative to the element’s contents. The result is that it scrolls when the element is scrolled. This is not a new feature, it has been with us since the first drafts of Backgrounds and Borders 3 in 2005 (of course, implementations took some more time, starting from 2009). If the way it works seems unclear, play a bit with this dabblet that demonstrates all three values (your browser needs to support all three of course): “Ok, I get it. Back to the scrolling shadows please?” Basically, the idea was to convert these absolutely positioned pseudoelements into background layers that have background-attachment: local applied. I tried it, it worked and helped reduce the code quite a lot. Here’s the dabblet with it: The drawback of this is that it reduces browser support a bit. Roman’s original experiment might even work in IE8, if the gradients are converted into images (gradients are not essential for the functionality). When you rely on background-attachment: local, you reduce browser support to IE9+, Safari 5+, Chrome and Opera . Firefox is the most notable absentee of that list, you can vote on bug #483446 if you’re interested in getting them to implement it (edit: Firefox supports this now [2013]). However, browser support is not that important, since the effect degrades very gracefully. On browsers that don’t support this, you just get no shadow. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Pure CSS scrolling shadows with background-attachment: local</title>
  <link>https://lea.verou.me/2012/04/background-attachment-local/</link>
  <pubDate>Sun, 17 May 2026 01:45:17 +0200</pubDate>
  <description>A few days ago, the incredibly talented Roman Komarov posted an experiment of his with pure CSS scrolling shadows . If youre using Google Reader, you are probably familiar with the effect: In Romans experiment, he is using absolutely positioned pseudoelements to cover the shadows (which are basically radial gradients as background images), taking advantage of the fact that when you scroll a scrollable container, its background does not scroll with it, but absolutely positioned elements within do. Therefore, when you scroll, the shadows are no longer obscured and can show through. Furthermore, these pseudoelements are linear gradients from white to transparent, so that these shadows are uncovered smoothly. When I saw Romans demo, I started wondering whether this is possible with no extra containers at all (pseudoelements included). It seemed like a perfect use case for background-attachment: local . Actually, it was the first real use case for it I had ever came up with or seen. background-attachment what? I only know scroll and fixed! scroll and fixed were the only values for background-attachment back in the days of CSS 2.1. scroll is the initial value and positions the background relative to the element its applied on , whereas fixed positions it relative to the viewport , resulting in the background staying in place when the page was scrolled. As a result of these definitions, when only a part of the page was scrollable (e.g. a with overflow: auto ), its background did not scroll when the container itself was scrolled. In Backgrounds &amp; Borders Level 3 , a new value was added to lift this restriction: local . When background-attachment: local is applied, the background is positioned relative to the elements contents. The result is that it scrolls when the element is scrolled. This is not a new feature, it has been with us since the first drafts of Backgrounds and Borders 3 in 2005 (of course, implementations took some more time, starting from 2009). If the way it works seems unclear, play a bit with this dabblet that demonstrates all three values (your browser needs to support all three of course): Ok, I get it. Back to the scrolling shadows please? Basically, the idea was to convert these absolutely positioned pseudoelements into background layers that have background-attachment: local applied. I tried it, it worked and helped reduce the code quite a lot. Heres the dabblet with it: The drawback of this is that it reduces browser support a bit. Romans original experiment might even work in IE8, if the gradients are converted into images (gradients are not essential for the functionality). When you rely on background-attachment: local, you reduce browser support to IE9+, Safari 5+, Chrome and Opera . Firefox is the most notable absentee of that list, you can vote on bug #483446 if youre interested in getting them to implement it (edit: Firefox supports this now [2013]). However, browser support is not that important, since the effect degrades very gracefully. On browsers that dont support this, you just get no shadow. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>git commit -m &quot;EVERYTHING&quot;</title>
  <link>https://lea.verou.me/2012/04/git-commit-m-everything/</link>
  <pubDate>Sun, 17 May 2026 01:45:16 +0200</pubDate>
  <description>I was working on a project today, when I realized that I had forgotten to commit for days (local only repo). I switched to my terminal, spent at least five minutes trying to decide on the commit message before settling to the completely uninformative “Another commit”. Embarrassed with myself, I shared my frustration with twitter: https://twitter.com/LeaVerou/status/187533962283986944 Immediately, I started getting a flood of suggestions of what that commit message could have been. Some of them were hilarious, some clever and some both. So, I decided I wouldn’t be selfish and I’d share them. Enjoy: https://twitter.com/codepo8/status/187534089937620994 https://twitter.com/vmasto/status/187534173429448704 https://twitter.com/GovertVerschuur/status/187534218790846466 https://twitter.com/upperdog_se/status/187534242182467584 https://twitter.com/brunoscheele/status/187534245437243392 https://twitter.com/idiot/status/187534264265490433 https://twitter.com/LukeMaciak/status/187534411955314688 https://twitter.com/jfgen/status/187534471703175168 https://twitter.com/AlexGraul/status/187534893436256259 https://twitter.com/captcodemonkey/status/187535071627059201 https://twitter.com/BoltClock/status/187535138266165248 https://twitter.com/jwkozel/status/187535407397863425 https://twitter.com/skidding/status/187536889715228672 https://twitter.com/omgmog/status/187537072213598209 https://twitter.com/stevehickeydsgn/status/187538150007123969 https://twitter.com/_dte/status/187538777441452032 https://twitter.com/nathandim/status/187538945305870336 https://twitter.com/jwkozel/status/187539130585063424 https://twitter.com/LukeMaciak/status/187539160851165184 https://twitter.com/croncobaurul/status/187539379428925442 https://twitter.com/MayaPosch/status/187539668793950208 https://twitter.com/OllyHodgson/status/187539827493834752 https://twitter.com/eternicode/status/187540177734991873 https://twitter.com/mrtnrsl/status/187540310094643201 https://twitter.com/kioopi/status/187540668728619008 https://twitter.com/streetpc/status/187541599532744704 https://twitter.com/GNi33/status/187541700091195392 https://twitter.com/jo\_Osiah/status/187541784870666241 https://twitter.com/dalecruse/status/187541971949203460 https://twitter.com/jordanpittman/status/187542101658058753 https://twitter.com/michelegera/status/187542353446313984 https://twitter.com/StuRobson/status/187543502538809344 https://twitter.com/gmoulin_dev/status/187544693779873792</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>git commit -m &quot;EVERYTHING&quot;</title>
  <link>https://lea.verou.me/2012/04/git-commit-m-everything/</link>
  <pubDate>Sun, 17 May 2026 01:45:16 +0200</pubDate>
  <description>I was working on a project today, when I realized that I had forgotten to commit for days (local only repo). I switched to my terminal, spent at least five minutes trying to decide on the commit message before settling to the completely uninformative Another commit. Embarrassed with myself, I shared my frustration with twitter: https://twitter.com/LeaVerou/status/187533962283986944 Immediately, I started getting a flood of suggestions of what that commit message could have been. Some of them were hilarious, some clever and some both. So, I decided I wouldnt be selfish and Id share them. Enjoy: https://twitter.com/codepo8/status/187534089937620994 https://twitter.com/vmasto/status/187534173429448704 https://twitter.com/GovertVerschuur/status/187534218790846466 https://twitter.com/upperdog_se/status/187534242182467584 https://twitter.com/brunoscheele/status/187534245437243392 https://twitter.com/idiot/status/187534264265490433 https://twitter.com/LukeMaciak/status/187534411955314688 https://twitter.com/jfgen/status/187534471703175168 https://twitter.com/AlexGraul/status/187534893436256259 https://twitter.com/captcodemonkey/status/187535071627059201 https://twitter.com/BoltClock/status/187535138266165248 https://twitter.com/jwkozel/status/187535407397863425 https://twitter.com/skidding/status/187536889715228672 https://twitter.com/omgmog/status/187537072213598209 https://twitter.com/stevehickeydsgn/status/187538150007123969 https://twitter.com/_dte/status/187538777441452032 https://twitter.com/nathandim/status/187538945305870336 https://twitter.com/jwkozel/status/187539130585063424 https://twitter.com/LukeMaciak/status/187539160851165184 https://twitter.com/croncobaurul/status/187539379428925442 https://twitter.com/MayaPosch/status/187539668793950208 https://twitter.com/OllyHodgson/status/187539827493834752 https://twitter.com/eternicode/status/187540177734991873 https://twitter.com/mrtnrsl/status/187540310094643201 https://twitter.com/kioopi/status/187540668728619008 https://twitter.com/streetpc/status/187541599532744704 https://twitter.com/GNi33/status/187541700091195392 https://twitter.com/jo\_Osiah/status/187541784870666241 https://twitter.com/dalecruse/status/187541971949203460 https://twitter.com/jordanpittman/status/187542101658058753 https://twitter.com/michelegera/status/187542353446313984 https://twitter.com/StuRobson/status/187543502538809344 https://twitter.com/gmoulin_dev/status/187544693779873792</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>In defense of reinventing wheels</title>
  <link>https://lea.verou.me/2012/04/in-defense-of-reinventing-wheels/</link>
  <pubDate>Sun, 17 May 2026 01:45:15 +0200</pubDate>
  <description>One of the first things a software engineer learns is “don’t reinvent the wheel”. If something is already made, use that instead of writing your own. “Stand on the shoulders of giants, they know what they’re doing better than you”. Writing your own tools and libraries, even when one already exists, is labelled “NIH syndrome” and is considered quite bad. “But what if my version is better?” . Surely, reinventing the wheel can’t be bad when your new wheel improves existing wheel designs, right? Well, not if the software is open source, which is usually the case in our industry. “Just contribute to it” you’ll be told. However, contributing to an open source project is basically teamwork. The success of any team depends on how well its members work together, which is not a given. Sometimes, your vision about the tool might be vastly different from that of the core members and it might be wiser to create your own prototype than to try and change the minds of all these people. However, Open Source politics is not what I wanted to discuss today. It’s not the biggest potential benefit of reinventing the wheel. Minimizing overhead is. You hardly ever need 100% of a project. Given enough time to study its inner workings, you could always delete quite a large chunk of it and it would still fit your needs perfectly. However, the effort needed to do that or to rewrite the percentage you actually need is big enough that you are willing to add redundant code to your codebase. Redundant code is bad. It still needs to get parsed and usually at least parts of it still need to be executed. Redundant code hinders performance. The more code, the slower your app. Especially when we are dealing with backend code, when every line might end up being executed hundreds or even thousands of times per second. The slower your app becomes, the bigger the need to seriously address performance. The result of that is even more code (e.g. caching stuff) that could have been saved in the first place, by just running what you need. This is the reason software like Joomla, Drupal or vBulletin is so extremely bloated and brings servers to their knees if a site becomes mildly successful. It’s the cost of code that tries to match everyone’s needs. Performance is not the only drawback involved in redundant code. A big one is maintainability. This code won’t only need to be parsed by the machine, it will also be parsed by humans, that don’t know what’s actually needed and what isn’t until they understand what every part does. Therefore, even the simplest of changes become hard. I’m not saying that using existing software or libraries is bad. I’m saying that it’s always a tradeoff between minimizing effort on one side and minimizing redundant code on the other side. I’m saying that you should consider writing your own code when the percentage of features you need from existing libraries is tiny (lets say less than 20%). It might not be worth carrying the extra 80% forever. For example, in a project I’m currently working on, I needed to make a simple localization system so that the site can be multilingual. I chose to use JSON files to contain the phrases. I didn’t want the phrases to include HTML, since I didn’t want to have to escape certain symbols. However, they had to include simple formatting like bold and links, otherwise the number of phrases would have to be huge. The obvious solution is Markdown . My first thought was to use an existing library, which for PHP is PHP Markdown . By digging a bit deeper I found that it’s actually considered pretty good and it seems to be well maintained (last update in January 2012) and mature (exists for over 2 years). I should happily use it then, right? That’s what I was planning to do. And then it struck me: I’m the only person writing these phrases. Even if more people write translations in the future, they will still go through me. So far, the only need for such formatting is links and bold. Everything else (e.g. lists) is handled by the HTML templates. That’s literally two lines of PHP ! So, I wrote my own function. It’s a bit bigger, since I also added emphasis, just in case: function markdown($text) { // Links $text = preg\_replace(&#39;@\\\\\[(.+?)\\\\\]\\\\((#.+?)\\\\)@&#39;, &#39; $1 &#39;, $text); // Bold $text = preg\_replace(&#39;@(? $1 &#39;, $text); // Emphasis $text = preg\_replace(&#39;@(? $1 &#39;, $text); return $text; } Since PHP regular expressions also support negative lookbehind, I can even avoid escaped characters, in the same line. Unfortunately, since PHP lacks regular expression literals, backslashes have to be doubled ( \\ instead of \ so \\\\ instead of \\ , which is pretty horrible). For comparison, PHP Markdown is about 1.7K lines of code. It’s great, if you need the full power of Markdown (e.g. for a comment system) and I’m glad Michel Fortin wrote it. However, for super simple, controlled use cases, is it really worth the extra code? I say no. Rachel Andrew recently wrote about something tangentially similar, in her blog post titled “ Stop solving problems you don’t yet have ”. It’s a great read and I’d advise you to read that too.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>In defense of reinventing wheels</title>
  <link>https://lea.verou.me/2012/04/in-defense-of-reinventing-wheels/</link>
  <pubDate>Sun, 17 May 2026 01:45:15 +0200</pubDate>
  <description>One of the first things a software engineer learns is dont reinvent the wheel. If something is already made, use that instead of writing your own. Stand on the shoulders of giants, they know what theyre doing better than you. Writing your own tools and libraries, even when one already exists, is labelled NIH syndrome and is considered quite bad. But what if my version is better? . Surely, reinventing the wheel cant be bad when your new wheel improves existing wheel designs, right? Well, not if the software is open source, which is usually the case in our industry. Just contribute to it youll be told. However, contributing to an open source project is basically teamwork. The success of any team depends on how well its members work together, which is not a given. Sometimes, your vision about the tool might be vastly different from that of the core members and it might be wiser to create your own prototype than to try and change the minds of all these people. However, Open Source politics is not what I wanted to discuss today. Its not the biggest potential benefit of reinventing the wheel. Minimizing overhead is. You hardly ever need 100% of a project. Given enough time to study its inner workings, you could always delete quite a large chunk of it and it would still fit your needs perfectly. However, the effort needed to do that or to rewrite the percentage you actually need is big enough that you are willing to add redundant code to your codebase. Redundant code is bad. It still needs to get parsed and usually at least parts of it still need to be executed. Redundant code hinders performance. The more code, the slower your app. Especially when we are dealing with backend code, when every line might end up being executed hundreds or even thousands of times per second. The slower your app becomes, the bigger the need to seriously address performance. The result of that is even more code (e.g. caching stuff) that could have been saved in the first place, by just running what you need. This is the reason software like Joomla, Drupal or vBulletin is so extremely bloated and brings servers to their knees if a site becomes mildly successful. Its the cost of code that tries to match everyones needs. Performance is not the only drawback involved in redundant code. A big one is maintainability. This code wont only need to be parsed by the machine, it will also be parsed by humans, that dont know whats actually needed and what isnt until they understand what every part does. Therefore, even the simplest of changes become hard. Im not saying that using existing software or libraries is bad. Im saying that its always a tradeoff between minimizing effort on one side and minimizing redundant code on the other side. Im saying that you should consider writing your own code when the percentage of features you need from existing libraries is tiny (lets say less than 20%). It might not be worth carrying the extra 80% forever. For example, in a project Im currently working on, I needed to make a simple localization system so that the site can be multilingual. I chose to use JSON files to contain the phrases. I didnt want the phrases to include HTML, since I didnt want to have to escape certain symbols. However, they had to include simple formatting like bold and links, otherwise the number of phrases would have to be huge. The obvious solution is Markdown . My first thought was to use an existing library, which for PHP is PHP Markdown . By digging a bit deeper I found that its actually considered pretty good and it seems to be well maintained (last update in January 2012) and mature (exists for over 2 years). I should happily use it then, right? Thats what I was planning to do. And then it struck me: Im the only person writing these phrases. Even if more people write translations in the future, they will still go through me. So far, the only need for such formatting is links and bold. Everything else (e.g. lists) is handled by the HTML templates. Thats literally two lines of PHP ! So, I wrote my own function. Its a bit bigger, since I also added emphasis, just in case: function markdown($text) { // Links $text = preg\_replace(&#39;@\\\\\[(.+?)\\\\\]\\\\((#.+?)\\\\)@&#39;, &#39; $1 &#39;, $text); // Bold $text = preg\_replace(&#39;@(? $1 &#39;, $text); // Emphasis $text = preg\_replace(&#39;@(? $1 &#39;, $text); return $text; } Since PHP regular expressions also support negative lookbehind, I can even avoid escaped characters, in the same line. Unfortunately, since PHP lacks regular expression literals, backslashes have to be doubled ( \\ instead of \ so \\\\ instead of \\ , which is pretty horrible). For comparison, PHP Markdown is about 1.7K lines of code. Its great, if you need the full power of Markdown (e.g. for a comment system) and Im glad Michel Fortin wrote it. However, for super simple, controlled use cases, is it really worth the extra code? I say no. Rachel Andrew recently wrote about something tangentially similar, in her blog post titled Stop solving problems you dont yet have . Its a great read and Id advise you to read that too.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Flexible multiline definition lists with 2 lines of CSS 2.1</title>
  <link>https://lea.verou.me/2012/02/flexible-multiline-definition-lists-with-2-lines-of-css/</link>
  <pubDate>Sun, 17 May 2026 01:45:14 +0200</pubDate>
  <description>If you’ve used definition lists ( ) you’re aware of the problem. By default, s and s have display:block . In order to turn them into what we want in most cases (each pair of term and definition on one line) we usually employ a number of different techniques: Using a different for each pair: Style dictating markup, which is bad Floats: Not flexible display: run-in; on the : Browser support is bad (No Firefox support) Adding a after each and setting both term and definition as display:inline : Invalid markup. Need I say more? If only adding s was valid… Or, even better, what if we could insert s from CSS? Actually, we can! As you might be aware, the CR and LF characters that comprise a line break are regular unicode characters that can be inserted anywhere just like every unicode character. They have the unicode codes 000D and 000A respectively. This means they can also be inserted as generated content, if escaped properly. Then we can use an appropriate white-space value to make the browser respect line breaks only in that part (the inserted line break). It looks like this: dd:after { content: ‘\A’; white-space: pre; } Note that nothing above is CSS3. It’s all good ol’ CSS 2.1. Of course, if you have multiple s for every , you will need to alter the code a bit. But in that case, this formatting probably won’t be what you want anyway. Edit: As Christian Heilmann pointed out , HTML3 (!) used to have a compact attribute on elements, which basically did this. It is now obsolete in HTML5, like every other presentational HTML feature. You can see a live result here: Tested to work in IE8+, Chrome, Firefox 3+, Opera 10+, Safari 4+ .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Flexible multiline definition lists with 2 lines of CSS 2.1</title>
  <link>https://lea.verou.me/2012/02/flexible-multiline-definition-lists-with-2-lines-of-css/</link>
  <pubDate>Sun, 17 May 2026 01:45:14 +0200</pubDate>
  <description>If youve used definition lists ( ) youre aware of the problem. By default, s and s have display:block . In order to turn them into what we want in most cases (each pair of term and definition on one line) we usually employ a number of different techniques: Using a different for each pair: Style dictating markup, which is bad Floats: Not flexible display: run-in; on the : Browser support is bad (No Firefox support) Adding a after each and setting both term and definition as display:inline : Invalid markup. Need I say more? If only adding s was valid Or, even better, what if we could insert s from CSS? Actually, we can! As you might be aware, the CR and LF characters that comprise a line break are regular unicode characters that can be inserted anywhere just like every unicode character. They have the unicode codes 000D and 000A respectively. This means they can also be inserted as generated content, if escaped properly. Then we can use an appropriate white-space value to make the browser respect line breaks only in that part (the inserted line break). It looks like this: dd:after { content: \A; white-space: pre; } Note that nothing above is CSS3. Its all good ol CSS 2.1. Of course, if you have multiple s for every , you will need to alter the code a bit. But in that case, this formatting probably wont be what you want anyway. Edit: As Christian Heilmann pointed out , HTML3 (!) used to have a compact attribute on elements, which basically did this. It is now obsolete in HTML5, like every other presentational HTML feature. You can see a live result here: Tested to work in IE8+, Chrome, Firefox 3+, Opera 10+, Safari 4+ .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>A List Apart article: Every time you call a proprietary feature &quot;CSS3&quot;, a kitten dies</title>
  <link>https://lea.verou.me/2012/02/a-list-apart-article-every-time-you-call-a-proprietary-feature-css3-a-kitten-dies/</link>
  <pubDate>Sun, 17 May 2026 01:45:13 +0200</pubDate>
  <description>My first article in ALA was published today, read it here: Every time you call a proprietary feature “CSS3”, a kitten dies Some comments about it on twitter: https://twitter.com/kkmett/status/169424038421204994 https://twitter.com/codepo8/status/169425611801108480 https://twitter.com/lydiamann/status/169425704092573696 https://twitter.com/zeldman/status/169426392654684160 https://twitter.com/alistapart/status/169426553787256833 https://twitter.com/happycog/status/169426900865908737 https://twitter.com/AlanBWhitney/status/169428991470612480 https://twitter.com/LeftyDesigner/status/169430280883544064 https://twitter.com/dap6000/status/169432718038409216 https://twitter.com/martuishere/status/169434598911123456 https://twitter.com/rachelober/status/169442236709355521 https://twitter.com/sgalineau/status/169443447554580480 https://twitter.com/sdague/status/169448377824722944 https://twitter.com/whitman/status/169451221495717889 https://twitter.com/peterwinnberg/status/169456073466585088 https://twitter.com/cssquirrel/status/169468390736527360 https://twitter.com/jameswillweb/status/169475627274088448 https://twitter.com/rogerjohansson/status/169482651198160896 https://twitter.com/beardChamp/status/169490532928720896</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Vendor prefixes, the CSS WG and me</title>
  <link>https://lea.verou.me/2012/02/vendor-prefixes-the-css-wg-and-me/</link>
  <pubDate>Sun, 17 May 2026 01:45:13 +0200</pubDate>
  <description>The CSS Working Group is recently discussing the very serious problem that vendor prefixes have become. We have reached a point where browsers are seriously considering to implement -webkit- prefixes , just because authors won’t bother using anything else. This is just sad. :( Daniel Glazman , Christian Heilmann and others wrote about it, making very good points and hoping that authors will wake up and start behaving. If you haven’t already, visit those links and read what they are saying. I’m not very optimistic about it, but I’ll do whatever I can to support their efforts. And that brings us to the other thing that made me sad these days. 2 days ago, the CSS WG published its Minutes (sorta like a meeting) and I was surprised to hear that I’ve been mentioned. My surprise quickly turned into this painful feeling in your stomach when you’re being unfairly accused: tantek: Opposite is happening right now. Web standards activists are teaching people to use -webkit- tantek: People like Lea Verou. tantek: Their demos are filled with -webkit-. You will see presentations from all the web standards advocates advocating people to use -webkit- prefixes. Try to picture being blamed of the very thing you hate, and you might understand how that felt. I’ve always been an advocate of inclusive CSS coding that doesn’t shut down other browsers. It’s good for future-proofing, it’s good for competition and it’s the right thing to do. Heck, I even made a popular script to help people adding all prefixes ! I’m even one of the few people in the industry who has never expressed a definite browser preference . I love and hate every browser equally, as I can see assets and defects in all of them (ok, except Safari. Safari must die :P). When Tantek realized he had falsely accused me of this, he corrected himself in the #css IRC room on w3.org : \[17:27\] (ASIDE: regarding using -webkit- prefix, clarification re: Lea Verou - she&#39;s advocated using \*both\* vendor prefixed properties (multiple vendors) and the unprefixed version after them. See her talk http://www.slideshare.net/LeaVerou/css3-a-practical-introduction-ft2010-talk from Front-Trends 2010 for example. An actual example of -webkit- \*only\* prefix examples (thus implied advocacy) is Google&#39;s http://slides.html5rocks.com/ , e.g. \[17:27\] http://slides.html5rocks.com/#css-columns has three -webkit- property declarations starting with -webkit-column-count ) That’s nice of him, and it does help. At least I had a link to give to people who kept asking me on twitter if I was really the prefix monster he made me out to be. :P The problem is that not many read the IRC logs, but many more read the www-style archives. Especially since, with all this buzz, many people were directed into reading this discussion by the above articles. I don’t know how many people will be misled by Tantek’s uninformed comment without reading his correction, but I know for sure that the number is non-zero. And the worst of all is that many of them are people in the CSSWG or in the W3C in general, people who I have great respect and admiration for. And quite frankly, that sucks. I don’t think Tantek had bad intentions. I’ve met him multiple times and I know he’s a nice guy. Maybe he was being lazy by making comments he didn’t check, but that’s about it. It could happen to many people. My main frustration is that it feels there is nothing I can do about it, besides answering people when they take the time to talk to me about it. I can do nothing with the ones that won’t, and that’s the majority. At least, if a forum was used over a mailing list, this could’ve been edited or something.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>A List Apart article: Every time you call a proprietary feature &quot;CSS3&quot;, a kitten dies</title>
  <link>https://lea.verou.me/2012/02/a-list-apart-article-every-time-you-call-a-proprietary-feature-css3-a-kitten-dies/</link>
  <pubDate>Sun, 17 May 2026 01:45:13 +0200</pubDate>
  <description>My first article in ALA was published today, read it here: Every time you call a proprietary feature CSS3, a kitten dies Some comments about it on twitter: https://twitter.com/kkmett/status/169424038421204994 https://twitter.com/codepo8/status/169425611801108480 https://twitter.com/lydiamann/status/169425704092573696 https://twitter.com/zeldman/status/169426392654684160 https://twitter.com/alistapart/status/169426553787256833 https://twitter.com/happycog/status/169426900865908737 https://twitter.com/AlanBWhitney/status/169428991470612480 https://twitter.com/LeftyDesigner/status/169430280883544064 https://twitter.com/dap6000/status/169432718038409216 https://twitter.com/martuishere/status/169434598911123456 https://twitter.com/rachelober/status/169442236709355521 https://twitter.com/sgalineau/status/169443447554580480 https://twitter.com/sdague/status/169448377824722944 https://twitter.com/whitman/status/169451221495717889 https://twitter.com/peterwinnberg/status/169456073466585088 https://twitter.com/cssquirrel/status/169468390736527360 https://twitter.com/jameswillweb/status/169475627274088448 https://twitter.com/rogerjohansson/status/169482651198160896 https://twitter.com/beardChamp/status/169490532928720896</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Vendor prefixes, the CSS WG and me</title>
  <link>https://lea.verou.me/2012/02/vendor-prefixes-the-css-wg-and-me/</link>
  <pubDate>Sun, 17 May 2026 01:45:13 +0200</pubDate>
  <description>The CSS Working Group is recently discussing the very serious problem that vendor prefixes have become. We have reached a point where browsers are seriously considering to implement -webkit- prefixes , just because authors wont bother using anything else. This is just sad. :( Daniel Glazman , Christian Heilmann and others wrote about it, making very good points and hoping that authors will wake up and start behaving. If you havent already, visit those links and read what they are saying. Im not very optimistic about it, but Ill do whatever I can to support their efforts. And that brings us to the other thing that made me sad these days. 2 days ago, the CSS WG published its Minutes (sorta like a meeting) and I was surprised to hear that Ive been mentioned. My surprise quickly turned into this painful feeling in your stomach when youre being unfairly accused: tantek: Opposite is happening right now. Web standards activists are teaching people to use -webkit- tantek: People like Lea Verou. tantek: Their demos are filled with -webkit-. You will see presentations from all the web standards advocates advocating people to use -webkit- prefixes. Try to picture being blamed of the very thing you hate, and you might understand how that felt. Ive always been an advocate of inclusive CSS coding that doesnt shut down other browsers. Its good for future-proofing, its good for competition and its the right thing to do. Heck, I even made a popular script to help people adding all prefixes ! Im even one of the few people in the industry who has never expressed a definite browser preference . I love and hate every browser equally, as I can see assets and defects in all of them (ok, except Safari. Safari must die :P). When Tantek realized he had falsely accused me of this, he corrected himself in the #css IRC room on w3.org : \[17:27\] (ASIDE: regarding using -webkit- prefix, clarification re: Lea Verou - she&#39;s advocated using \*both\* vendor prefixed properties (multiple vendors) and the unprefixed version after them. See her talk http://www.slideshare.net/LeaVerou/css3-a-practical-introduction-ft2010-talk from Front-Trends 2010 for example. An actual example of -webkit- \*only\* prefix examples (thus implied advocacy) is Google&#39;s http://slides.html5rocks.com/ , e.g. \[17:27\] http://slides.html5rocks.com/#css-columns has three -webkit- property declarations starting with -webkit-column-count ) Thats nice of him, and it does help. At least I had a link to give to people who kept asking me on twitter if I was really the prefix monster he made me out to be. :P The problem is that not many read the IRC logs, but many more read the www-style archives. Especially since, with all this buzz, many people were directed into reading this discussion by the above articles. I dont know how many people will be misled by Tanteks uninformed comment without reading his correction, but I know for sure that the number is non-zero. And the worst of all is that many of them are people in the CSSWG or in the W3C in general, people who I have great respect and admiration for. And quite frankly, that sucks. I dont think Tantek had bad intentions. Ive met him multiple times and I know hes a nice guy. Maybe he was being lazy by making comments he didnt check, but thats about it. It could happen to many people. My main frustration is that it feels there is nothing I can do about it, besides answering people when they take the time to talk to me about it. I can do nothing with the ones that wont, and thats the majority. At least, if a forum was used over a mailing list, this couldve been edited or something.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Moving an element along a circle</title>
  <link>https://lea.verou.me/2012/02/moving-an-element-along-a-circle/</link>
  <pubDate>Sun, 17 May 2026 01:45:12 +0200</pubDate>
  <description>It all started a few months ago, when Chris Coyier casually asked me how would I move an element along a circle, without of course rotating the element itself. If I recall correctly, his solution was to use multiple keyframes, for various points on a circle’s circumference, approximating it. I couldn’t think of anything better at the time, but the question was stuck in the back of my head. 3 months ago, I came up with a first solution. Unfortunately, it required an extra wrapper element. The idea was to use two rotate transforms with different origins and opposite angles that cancel each other at any given time. The first transform-origin would be the center of the circle path and the other one the center of the element. Because we can’t use multiple transform-origins, a wrapper element was needed. So, even though this solution was better, I wasn’t fully satisfied with it due to the need for the extra element. So, it kept being stuck in the back of my head. Recently, I suggested to www-style that transform-origin should be a list and accept multiple origins and presented this example as a use case. And then Aryeh Gregor came up with this genius idea to prove that it’s already possible if you chain translate() transforms between the opposite rotates. I simplified the code a bit, and here it is: With the tools we currently have, I don’t think it gets any simpler than that.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Moving an element along a circle</title>
  <link>https://lea.verou.me/2012/02/moving-an-element-along-a-circle/</link>
  <pubDate>Sun, 17 May 2026 01:45:12 +0200</pubDate>
  <description>It all started a few months ago, when Chris Coyier casually asked me how would I move an element along a circle, without of course rotating the element itself. If I recall correctly, his solution was to use multiple keyframes, for various points on a circles circumference, approximating it. I couldnt think of anything better at the time, but the question was stuck in the back of my head. 3 months ago, I came up with a first solution. Unfortunately, it required an extra wrapper element. The idea was to use two rotate transforms with different origins and opposite angles that cancel each other at any given time. The first transform-origin would be the center of the circle path and the other one the center of the element. Because we cant use multiple transform-origins, a wrapper element was needed. So, even though this solution was better, I wasnt fully satisfied with it due to the need for the extra element. So, it kept being stuck in the back of my head. Recently, I suggested to www-style that transform-origin should be a list and accept multiple origins and presented this example as a use case. And then Aryeh Gregor came up with this genius idea to prove that its already possible if you chain translate() transforms between the opposite rotates. I simplified the code a bit, and here it is: With the tools we currently have, I dont think it gets any simpler than that.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Simpler CSS typing animation, with the ch unit</title>
  <link>https://lea.verou.me/2012/02/simpler-css-typing-animation-with-the-ch-unit/</link>
  <pubDate>Sun, 17 May 2026 01:45:11 +0200</pubDate>
  <description>A while ago, I posted about how to use steps() as an easing function to create a typing animation that degrades gracefully. Today I decided to simplify it a bit and make it more flexible, at the cost of browser support. The new version fully works in Firefox 1+ and IE10, since Opera and WebKit don’t support the ch unit and even though IE9 supports it, it doesn’t support CSS animations. To put it simply, one ch unit is equivalent to the width of the zero (0) character of the font. So, in monospace fonts, it’s equivalent to the width of every character, since every character has the same width. In the new version, we don’t need an obscuring span, so no extra HTML and it will work with non-solid backgrounds too. Also, even though the number of characters still needs to be hard-coded, it doesn’t need to be hardcoded in the animation any more, so it could be easily done through script without messing with creating/modifying stylesheets. Note how each animation only has one keyframe, and takes advantage of the fact that when the from (0%) and to (100%) keyframes are missing, the browser generates them from the fallback styles. I use this a lot when coding animations, as I hate duplication. In browsers that support CSS animations, but not the ch unit (such as WebKit based browsers), the animation will still occur, since we included a fallback in ems, but it won’t be 100% perfect. I think that’s a pretty good fallback, but if it bothers you, just declare a fallback of auto (or don’t declare one at all, and it will naturally fall back to auto). In browsers that don’t support CSS animations at all (such as Opera), the caret will be a solid black line that doesn’t blink. I thought that’s better than not showing it at all, but if you disagree, it’s very easy to hide it in those browsers completely: Just swap the border-color between the keyframe and the h1 rule (hint: when a border-color is not declared, it’s currentColor ). Edit: It appears that Firefox’s support for the ch unit is a bit buggy so, the following example won’t work with the Monaco font for example. This is not the correct behavior. Enjoy:</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Simpler CSS typing animation, with the ch unit</title>
  <link>https://lea.verou.me/2012/02/simpler-css-typing-animation-with-the-ch-unit/</link>
  <pubDate>Sun, 17 May 2026 01:45:11 +0200</pubDate>
  <description>A while ago, I posted about how to use steps() as an easing function to create a typing animation that degrades gracefully. Today I decided to simplify it a bit and make it more flexible, at the cost of browser support. The new version fully works in Firefox 1+ and IE10, since Opera and WebKit dont support the ch unit and even though IE9 supports it, it doesnt support CSS animations. To put it simply, one ch unit is equivalent to the width of the zero (0) character of the font. So, in monospace fonts, its equivalent to the width of every character, since every character has the same width. In the new version, we dont need an obscuring span, so no extra HTML and it will work with non-solid backgrounds too. Also, even though the number of characters still needs to be hard-coded, it doesnt need to be hardcoded in the animation any more, so it could be easily done through script without messing with creating/modifying stylesheets. Note how each animation only has one keyframe, and takes advantage of the fact that when the from (0%) and to (100%) keyframes are missing, the browser generates them from the fallback styles. I use this a lot when coding animations, as I hate duplication. In browsers that support CSS animations, but not the ch unit (such as WebKit based browsers), the animation will still occur, since we included a fallback in ems, but it wont be 100% perfect. I think thats a pretty good fallback, but if it bothers you, just declare a fallback of auto (or dont declare one at all, and it will naturally fall back to auto). In browsers that dont support CSS animations at all (such as Opera), the caret will be a solid black line that doesnt blink. I thought thats better than not showing it at all, but if you disagree, its very easy to hide it in those browsers completely: Just swap the border-color between the keyframe and the h1 rule (hint: when a border-color is not declared, its currentColor ). Edit: It appears that Firefoxs support for the ch unit is a bit buggy so, the following example wont work with the Monaco font for example. This is not the correct behavior. Enjoy:</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Exactly how much CSS3 does your browser support?</title>
  <link>https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/</link>
  <pubDate>Sun, 17 May 2026 01:45:10 +0200</pubDate>
  <description>This project started as an attempt to improve dabblet and to generate data for the book chapter I’m writing for Smashing Book #3. I wanted to create a very simple/basic testsuite for CSS3 stuff so that you could hover on a e.g. CSS3 property and you got a nice browser support popup. While I didn’t achieve that (turns out BrowserScope doesn’t do that kind of thing), I still think it’s interesting as a spin-off project, especially since the results will probably surprise you. How it works css3test (very superficially) tests pretty much everything in the specs mentioned on the sidebar (not just the popular widely implemented stuff). You can click on every feature to expand it and see the exact the testcases run and whether they passed. It only checks what syntax the browser recognizes, which doesn’t necessarily mean it will work correctly when used. WebKit is especially notorious for cheating in tests like this, recognizing stuff it doesn’t understand, like the values “round” and “space” for background-repeat, but the cheating isn’t big enough to seriously compromise the test. Whether a feature is supported with a prefix or not doesn’t matter for the result. If it’s supported without a prefix, it will test that one. If it’s supported only with a prefix, it will test the prefixed one. For properties especially, if an unprefixed one is supported, it will be used in all the tests. Only stuff that’s in a W3C specification is tested. So, please don’t ask or send pull requests for proprietary things like -webkit-gradient() or -webkit-background-clip: text; or -webkit-box-reflect and so on. Every feature contributes the same to the end score , as well as to the score of the individual spec, regardless of the number of tests it has. Crazy shit Chrome may display slightly different scores (1% difference) across pageloads. It seems that for some reason, it fails the tests for border-image completely on some pageloads, which doesn’t make any sense. Whoever wants to investigate, I’d be grateful. Edit: Fixed (someone found and submitted an even crazier workaround.). Browserscope This is the first project of mine in which I’ve used browserscope . This means that your results will be sent over to its servers and aggreggated. When I have enough data, I’m gonna built a nice table for everyone to see :) In the meantime, check the results page. It doesn’t work on my browser, U SUCK! The test won’t work on dinosaur browsers like IE8, but who cares measuring their CSS3 support anyway? “For a laugh” isn’t a good enough answer to warrant the time needed. If you find a bug, please remember you didn’t pay a dime for this before nagging. Politely report it on Github, or even better, fix it and send a pull request. Why did you build it? To motivate browsers to support the less hyped stuff, because I’m tired of seeing the same things being evangelized over and over. There’s much more to CSS3. Current results At the time of this writing, these are the results for the major modern browsers: Chrome Canary, WebKit nightlies, Firefox Nightly: 64% Chrome, IE10PP4: 63% Firefox 10: 61% Safari 5.1, iOS5 Safari: 60% Opera 11.60: 56% Firefox 9: 58% Firefox 6-8: 57% Firefox 5, Opera 11.1 - 11.5: 55% Safari 5.0: 54% Firefox 4: 49% Safari 4: 47% Opera 10: 45% Firefox 3.6: 44% IE9: 39% Enjoy! css3test.com Fork css3test on Github Browserscope results</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Exactly how much CSS3 does your browser support?</title>
  <link>https://lea.verou.me/2012/02/exactly-how-much-css3-does-your-browser-support/</link>
  <pubDate>Sun, 17 May 2026 01:45:10 +0200</pubDate>
  <description>This project started as an attempt to improve dabblet and to generate data for the book chapter Im writing for Smashing Book #3. I wanted to create a very simple/basic testsuite for CSS3 stuff so that you could hover on a e.g. CSS3 property and you got a nice browser support popup. While I didnt achieve that (turns out BrowserScope doesnt do that kind of thing), I still think its interesting as a spin-off project, especially since the results will probably surprise you. How it works css3test (very superficially) tests pretty much everything in the specs mentioned on the sidebar (not just the popular widely implemented stuff). You can click on every feature to expand it and see the exact the testcases run and whether they passed. It only checks what syntax the browser recognizes, which doesnt necessarily mean it will work correctly when used. WebKit is especially notorious for cheating in tests like this, recognizing stuff it doesnt understand, like the values round and space for background-repeat, but the cheating isnt big enough to seriously compromise the test. Whether a feature is supported with a prefix or not doesnt matter for the result. If its supported without a prefix, it will test that one. If its supported only with a prefix, it will test the prefixed one. For properties especially, if an unprefixed one is supported, it will be used in all the tests. Only stuff thats in a W3C specification is tested. So, please dont ask or send pull requests for proprietary things like -webkit-gradient() or -webkit-background-clip: text; or -webkit-box-reflect and so on. Every feature contributes the same to the end score , as well as to the score of the individual spec, regardless of the number of tests it has. Crazy shit Chrome may display slightly different scores (1% difference) across pageloads. It seems that for some reason, it fails the tests for border-image completely on some pageloads, which doesnt make any sense. Whoever wants to investigate, Id be grateful. Edit: Fixed (someone found and submitted an even crazier workaround.). Browserscope This is the first project of mine in which Ive used browserscope . This means that your results will be sent over to its servers and aggreggated. When I have enough data, Im gonna built a nice table for everyone to see :) In the meantime, check the results page. It doesnt work on my browser, U SUCK! The test wont work on dinosaur browsers like IE8, but who cares measuring their CSS3 support anyway? For a laugh isnt a good enough answer to warrant the time needed. If you find a bug, please remember you didnt pay a dime for this before nagging. Politely report it on Github, or even better, fix it and send a pull request. Why did you build it? To motivate browsers to support the less hyped stuff, because Im tired of seeing the same things being evangelized over and over. Theres much more to CSS3. Current results At the time of this writing, these are the results for the major modern browsers: Chrome Canary, WebKit nightlies, Firefox Nightly: 64% Chrome, IE10PP4: 63% Firefox 10: 61% Safari 5.1, iOS5 Safari: 60% Opera 11.60: 56% Firefox 9: 58% Firefox 6-8: 57% Firefox 5, Opera 11.1 - 11.5: 55% Safari 5.0: 54% Firefox 4: 49% Safari 4: 47% Opera 10: 45% Firefox 3.6: 44% IE9: 39% Enjoy! css3test.com Fork css3test on Github Browserscope results</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Why tabs are clearly superior</title>
  <link>https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/</link>
  <pubDate>Sun, 17 May 2026 01:45:09 +0200</pubDate>
  <description>If you follow me on twitter or have heard one of my talks you’ll probably know I despise spaces for indentation with a passion. However, I’ve never gone into the details of my opinion on stage, and twitter isn’t really the right medium for advocacy. I always wanted to write a blog post about my take on this old debate, so here it is. Tabs for indentation, spaces for alignment Let’s get this out of the way: Tabs should never be used for alignment. Using tabs for alignment is actively worse than using spaces for indentation and is the base of all arguments against tabs. But using tabs for alignment is misuse, and negates their main advantage: personalization. It’s like trying to eat soup with a fork and complaining because it doesn’t scoop up liquid well. Consider this code snippet: if (something) { let x = 10, y = 0; } Each line inside the conditional is indented with a tab, but the variables are aligned with four spaces. Change the tab size to see how everything adapts beautifully: Tab size: 4 And yes, of course using tabs for alignment is a mess, because that’s not what they’re for: if (something) { let x = 10, y = 0; } Another example: remember CSS vendor prefixes? div { -webkit-transition: 1s; -moz-transition: 1s; -ms-transition: 1s; -o-transition: 1s; transition: 1s; } 1. Tabs can be personalized The width of a tab character can be adjusted per editor. This is not a disadvantage of tabs as commonly evangelized, but a major advantage . People can view your code in the way they feel comfortable with, not in the way *you* prefer. Tabs are one step towards decoupling the code’s presentation from its logic, just like CSS decouples presentation from HTML. They give more power to the reader rather than letting the author control everything. Basically, using spaces is like saying “I don’t care about how you feel more comfortable reading code, I will force you to use my preferences because it’s my code”. Personalization is incredibly valuable when a team is collaborating, as different engineers can have different opinions. Some engineers prefer their indents to be 2 spaces wide, some prefer them to be 4 spaces wide. With spaces for alignment, a lead engineer imposes their preference on the entire team; with tabs everyone gets to choose what they feel comfortable with. 2. You don’t depend on certain tools When using spaces, you depend on your editor to abstract away the fact that an indent is actually N characters instead of one. You depend on your editor to insert N spaces every time you press the Tab key and to delete N characters every time you press backspace or delete near an indent. I have never seen an editor where this abstraction did not leak at all. If you’re not careful, it’s easy to end up with indentation that is not an integer multiple of the indent width, which is a mess. With tabs, the indent width is simply the number of tabs at the beginning of a line. You don’t depend on tools to hide anything, and change the meaning of keyboard keys. Even in the most basic of plain text editors, you can use the keyboard to navigate indents in integer increments. 3. Tabs encode strictly more information about the code Used right, tabs are only used for a singular purpose: indentation. This makes them easy to target programmatically, e.g. through regular expressions or find &amp; replace. Spaces on the other hand, have many meanings, so programmatically matching indents is a non-trivial problem. Even if you only match space characters at the beginning of a line, there is no way of knowing when to stop, as spaces are also used for alignment. Being able to tell the difference requires awareness about the semantics of the language itself. 4. Tabs facilitate portability As pointed out by Norbert Süle in the comments , when you copy and paste code that’s indented with spaces, you have to manually adjust the indentation afterwards, unless the other person also happens to prefer the same width indents as you. With tabs, there is no such issue, as it’s always tabs so it will fit in with your (tabbed) code seamlessly. The world would be a better place if everyone used tabs. 5. Tabs take up less space One of the least important arguments here, but still worth mentioning. Tabs take up only one byte, while spaces take up as many bytes as their width, usually 2-4x that. On large codebases this can add up. E.g. in a codebase of 1M loc, averaging 1 indent per line (good luck computing these kinds of stats with spaces btw, see 3 above), with an indent width of 4 spaces, you would save 3MB of space by using tabs instead of spaces. It’s not a tremendous cost if spaces actually offered a benefit, but it’s unclear what the benefit is. The downsides of using tabs for indentation Literally all downsides of using tabs for indentation stem from how vocal their opponents are and how pervasive spaces are for indentation. To the point that using spaces for indentation is associated with significantly higher salaries ! In browsers It is unfortunate that most UAs have declared war to tabs by using a default tab size of 8, far too wide for any practical purpose. For code you post online, you can use the tab-size property to set tab size to a more reasonable value, e.g. 4. It’s widely supported . For reading code on other websites, you can use an extension like Stylus to set the tab size to whatever you want. I have this rule applying on all websites: /* ==UserStyle== @name 7/22/2022, 5:43:07 PM @namespace * @version 1.0.0 @description A new userstyle @author Me ==/UserStyle== */ * { tab-size: 4 !important; } In tooling Editors that handle smart tabs correctly are few and far between. Even VS Code, the most popular editor right now, doesn’t handle them correctly , though there are extensions ( Tab-Indent Space-Align , Smart Tabs , and others) What does it matter, tabs, spaces, whatever, it’s just a pointless detail Sure, in the grand scheme of things, using spaces for indentation will not kill anyone. But it’s a proxy for a greater argument: that technology should make it possible to read code in the way you prefer, without having to get team buy-in on your preferences. There are other ways to do this (reformatting post-pull and pre-commit), but are too heavyweight and intrusive. If we can’t even get people to see the value of not enforcing the same indentation width on everyone, how can we expect them to see the value in further personalization? Further reading Tabs. Spaces. Indentation. Alignment. Indentation With Spaces Considered Harmful Tabs vs spaces for code indentation Why I love having tabs in source code Tabs vs spaces Indent with tabs, align with spaces Relevant: Elastic tabstops Thanks to Oli for proofreading the first version of this post.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Why tabs are clearly superior</title>
  <link>https://lea.verou.me/2012/01/why-tabs-are-clearly-superior/</link>
  <pubDate>Sun, 17 May 2026 01:45:09 +0200</pubDate>
  <description>If you follow me on twitter or have heard one of my talks youll probably know I despise spaces for indentation with a passion. However, Ive never gone into the details of my opinion on stage, and twitter isnt really the right medium for advocacy. I always wanted to write a blog post about my take on this old debate, so here it is. Tabs for indentation, spaces for alignment Lets get this out of the way: Tabs should never be used for alignment. Using tabs for alignment is actively worse than using spaces for indentation and is the base of all arguments against tabs. But using tabs for alignment is misuse, and negates their main advantage: personalization. Its like trying to eat soup with a fork and complaining because it doesnt scoop up liquid well. Consider this code snippet: if (something) { let x = 10, y = 0; } Each line inside the conditional is indented with a tab, but the variables are aligned with four spaces. Change the tab size to see how everything adapts beautifully: Tab size: 4 And yes, of course using tabs for alignment is a mess, because thats not what theyre for: if (something) { let x = 10, y = 0; } Another example: remember CSS vendor prefixes? div { -webkit-transition: 1s; -moz-transition: 1s; -ms-transition: 1s; -o-transition: 1s; transition: 1s; } 1. Tabs can be personalized The width of a tab character can be adjusted per editor. This is not a disadvantage of tabs as commonly evangelized, but a major advantage . People can view your code in the way they feel comfortable with, not in the way *you* prefer. Tabs are one step towards decoupling the codes presentation from its logic, just like CSS decouples presentation from HTML. They give more power to the reader rather than letting the author control everything. Basically, using spaces is like saying I dont care about how you feel more comfortable reading code, I will force you to use my preferences because its my code. Personalization is incredibly valuable when a team is collaborating, as different engineers can have different opinions. Some engineers prefer their indents to be 2 spaces wide, some prefer them to be 4 spaces wide. With spaces for alignment, a lead engineer imposes their preference on the entire team; with tabs everyone gets to choose what they feel comfortable with. 2. You dont depend on certain tools When using spaces, you depend on your editor to abstract away the fact that an indent is actually N characters instead of one. You depend on your editor to insert N spaces every time you press the Tab key and to delete N characters every time you press backspace or delete near an indent. I have never seen an editor where this abstraction did not leak at all. If youre not careful, its easy to end up with indentation that is not an integer multiple of the indent width, which is a mess. With tabs, the indent width is simply the number of tabs at the beginning of a line. You dont depend on tools to hide anything, and change the meaning of keyboard keys. Even in the most basic of plain text editors, you can use the keyboard to navigate indents in integer increments. 3. Tabs encode strictly more information about the code Used right, tabs are only used for a singular purpose: indentation. This makes them easy to target programmatically, e.g. through regular expressions or find &amp; replace. Spaces on the other hand, have many meanings, so programmatically matching indents is a non-trivial problem. Even if you only match space characters at the beginning of a line, there is no way of knowing when to stop, as spaces are also used for alignment. Being able to tell the difference requires awareness about the semantics of the language itself. 4. Tabs facilitate portability As pointed out by Norbert Süle in the comments , when you copy and paste code thats indented with spaces, you have to manually adjust the indentation afterwards, unless the other person also happens to prefer the same width indents as you. With tabs, there is no such issue, as its always tabs so it will fit in with your (tabbed) code seamlessly. The world would be a better place if everyone used tabs. 5. Tabs take up less space One of the least important arguments here, but still worth mentioning. Tabs take up only one byte, while spaces take up as many bytes as their width, usually 2-4x that. On large codebases this can add up. E.g. in a codebase of 1M loc, averaging 1 indent per line (good luck computing these kinds of stats with spaces btw, see 3 above), with an indent width of 4 spaces, you would save 3MB of space by using tabs instead of spaces. Its not a tremendous cost if spaces actually offered a benefit, but its unclear what the benefit is. The downsides of using tabs for indentation Literally all downsides of using tabs for indentation stem from how vocal their opponents are and how pervasive spaces are for indentation. To the point that using spaces for indentation is associated with significantly higher salaries ! In browsers It is unfortunate that most UAs have declared war to tabs by using a default tab size of 8, far too wide for any practical purpose. For code you post online, you can use the tab-size property to set tab size to a more reasonable value, e.g. 4. Its widely supported . For reading code on other websites, you can use an extension like Stylus to set the tab size to whatever you want. I have this rule applying on all websites: /* ==UserStyle== @name 7/22/2022, 5:43:07 PM @namespace * @version 1.0.0 @description A new userstyle @author Me ==/UserStyle== */ * { tab-size: 4 !important; } In tooling Editors that handle smart tabs correctly are few and far between. Even VS Code, the most popular editor right now, doesnt handle them correctly , though there are extensions ( Tab-Indent Space-Align , Smart Tabs , and others) What does it matter, tabs, spaces, whatever, its just a pointless detail Sure, in the grand scheme of things, using spaces for indentation will not kill anyone. But its a proxy for a greater argument: that technology should make it possible to read code in the way you prefer, without having to get team buy-in on your preferences. There are other ways to do this (reformatting post-pull and pre-commit), but are too heavyweight and intrusive. If we cant even get people to see the value of not enforcing the same indentation width on everyone, how can we expect them to see the value in further personalization? Further reading Tabs. Spaces. Indentation. Alignment. Indentation With Spaces Considered Harmful Tabs vs spaces for code indentation Why I love having tabs in source code Tabs vs spaces Indent with tabs, align with spaces Relevant: Elastic tabstops Thanks to Oli for proofreading the first version of this post.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My new year’s resolution</title>
  <link>https://lea.verou.me/2012/01/my-new-years-resolution/</link>
  <pubDate>Sun, 17 May 2026 01:45:08 +0200</pubDate>
  <description>Warning: Personal post ahead. If you’re here to read some code trickery, move along and wait for the next post, kthxbai Blogs are excellent places for new year’s resolutions. Posts stay there for years, to remind you what you’ve been thinking long ago. A list on a piece of paper or a file in your computer will be forgotten and lost, but a resolution on your blog will come back to haunt you. Sometimes you want that extra push. I’m not too fond of new year’s resolutions and this may as well be my first, but this year there are certain goals I want to achieve, unlike previous years were things were more fluid. So, in 2012 I want to… Land my dreamjob in a US company/organization I respect Get the hell out of Greece and move to the Bay Area Strive to improve my english even more, until I sound and write like a native speaker Find a publisher I respect that’s willing to print in full color and write my first book. Stop getting into stupid fights on twitter. They are destructive to both my well-being and my creativity. Get my degree in Computer Science. This has been my longest side project, 4 years and counting. I wonder how many of those I will have achieved this time next year, how many I will have failed and how many I won’t care about any more…</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>What we still can’t do client-side</title>
  <link>https://lea.verou.me/2012/01/what-we-still-can%E2%80%99t-do-client-side/</link>
  <pubDate>Sun, 17 May 2026 01:45:08 +0200</pubDate>
  <description>With the rise of all these APIs and the browser race to implement them, you’d think that currently we can do pretty much everything in JavaScript and even if we currently can’t due to browser support issues, we will once the specs are implemented. Unfortunately, that’s not true. There are still things we can’t do, and there’s no specification to address them at the time of this writing and no way to do them with the APIs we already have (or if there is a way, it’s unreasonably complicated). We can’t do templating across pages Before rushing to tell me “no, we can”, keep reading. I mean have different files and re-use them accross different pages. For example, a header and a footer. If our project is entirely client-side, we have to repeat them manually on every page. Of course, we can always use (i)frames, but that solution is worse than the problem it solves. There should be a simple way to inject HTML from another file, like server-side includes, but client-side. without using JavaScript at all, this is a task that belongs to HTML (with JS we can always use XHR to do it but…). The browser would then be able to cache these static parts, with significant speed improvements on subsequent page loads. Update: The Web Components family of specs sort of helps with this, but still requires a lot of DIY and Mozilla is against HTML imports and will not implement them , which is one main component of this. We can’t do localization At least not in a sane, standard way. Client-side localization is a big PITA. There should be an API for this. That would have the added advantage that browsers could pick it up and offer a UI for it. I can’t count the number of times I’ve thought a website didn’t have an English version just because their UI was so bad I couldn’t find the switcher. Google Chrome often detects a website language and offers to translate it, if such an API existed we could offer properly translated versions of the website in a manner detectable by the browser. Update: We have the ECMAScript Globalization API , although it looks far from ideal at the moment. We can’t do screen capture And not just of the screen, but we can’t even capture an element on the page and draw it on a canvas unless we use huge libraries that basically try to emulate a browser or SVG foreignObject which has its own share of issues. We should have a Screen Capture API, or at the very least, a way to draw DOM nodes on canvas. Yes, there are privacy concerns that need to be taken care of, but this is so tremendously useful that it’s worth the time needed to go intro researching those. We can’t get POST parameters and HTTP headers There’s absolutely NO way to get the POST parameters or the HTTP response headers that the current page was sent with. You can get the GET parameters through the location object, but no way to get POST parameters. This makes it very hard to make client-side applications that accept input from 3rd party websites when that input is too long to be on the URL (as is the case of dabblet for example). We can’t make peer to peer connections There is absolutely no way to connect to another client running our web app (to play a game for example), without an intermediate server. Update: There’s RTCPeerConnection in WebRTC , though the API is pretty horrible. _________ Anything else we still can’t do and we still don’t have an API to do so in the future? Say it in the comments! Or, if I’m mistaken about one of the above and there is actually an active spec to address it, please point me to it! Why would you want to do these things client-side?! Everything that helps take load away from the server is good. The client is always one machine, everything on the server may end up running thousands of times per second if the web app succeeds, making the app slow and/or costly to run. I strongly believe in lean servers. Servers should only do things that architecturally need a server (e.g. centralized data storage), everything else is the client’s job. Almost everything that we use native apps for, should (and eventually will) be doable by JavaScript.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My new years resolution</title>
  <link>https://lea.verou.me/2012/01/my-new-years-resolution/</link>
  <pubDate>Sun, 17 May 2026 01:45:08 +0200</pubDate>
  <description>Warning: Personal post ahead. If youre here to read some code trickery, move along and wait for the next post, kthxbai Blogs are excellent places for new years resolutions. Posts stay there for years, to remind you what youve been thinking long ago. A list on a piece of paper or a file in your computer will be forgotten and lost, but a resolution on your blog will come back to haunt you. Sometimes you want that extra push. Im not too fond of new years resolutions and this may as well be my first, but this year there are certain goals I want to achieve, unlike previous years were things were more fluid. So, in 2012 I want to Land my dreamjob in a US company/organization I respect Get the hell out of Greece and move to the Bay Area Strive to improve my english even more, until I sound and write like a native speaker Find a publisher I respect thats willing to print in full color and write my first book. Stop getting into stupid fights on twitter. They are destructive to both my well-being and my creativity. Get my degree in Computer Science. This has been my longest side project, 4 years and counting. I wonder how many of those I will have achieved this time next year, how many I will have failed and how many I wont care about any more</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>What we still cant do client-side</title>
  <link>https://lea.verou.me/2012/01/what-we-still-can%E2%80%99t-do-client-side/</link>
  <pubDate>Sun, 17 May 2026 01:45:08 +0200</pubDate>
  <description>With the rise of all these APIs and the browser race to implement them, youd think that currently we can do pretty much everything in JavaScript and even if we currently cant due to browser support issues, we will once the specs are implemented. Unfortunately, thats not true. There are still things we cant do, and theres no specification to address them at the time of this writing and no way to do them with the APIs we already have (or if there is a way, its unreasonably complicated). We cant do templating across pages Before rushing to tell me no, we can, keep reading. I mean have different files and re-use them accross different pages. For example, a header and a footer. If our project is entirely client-side, we have to repeat them manually on every page. Of course, we can always use (i)frames, but that solution is worse than the problem it solves. There should be a simple way to inject HTML from another file, like server-side includes, but client-side. without using JavaScript at all, this is a task that belongs to HTML (with JS we can always use XHR to do it but). The browser would then be able to cache these static parts, with significant speed improvements on subsequent page loads. Update: The Web Components family of specs sort of helps with this, but still requires a lot of DIY and Mozilla is against HTML imports and will not implement them , which is one main component of this. We cant do localization At least not in a sane, standard way. Client-side localization is a big PITA. There should be an API for this. That would have the added advantage that browsers could pick it up and offer a UI for it. I cant count the number of times Ive thought a website didnt have an English version just because their UI was so bad I couldnt find the switcher. Google Chrome often detects a website language and offers to translate it, if such an API existed we could offer properly translated versions of the website in a manner detectable by the browser. Update: We have the ECMAScript Globalization API , although it looks far from ideal at the moment. We cant do screen capture And not just of the screen, but we cant even capture an element on the page and draw it on a canvas unless we use huge libraries that basically try to emulate a browser or SVG foreignObject which has its own share of issues. We should have a Screen Capture API, or at the very least, a way to draw DOM nodes on canvas. Yes, there are privacy concerns that need to be taken care of, but this is so tremendously useful that its worth the time needed to go intro researching those. We cant get POST parameters and HTTP headers Theres absolutely NO way to get the POST parameters or the HTTP response headers that the current page was sent with. You can get the GET parameters through the location object, but no way to get POST parameters. This makes it very hard to make client-side applications that accept input from 3rd party websites when that input is too long to be on the URL (as is the case of dabblet for example). We cant make peer to peer connections There is absolutely no way to connect to another client running our web app (to play a game for example), without an intermediate server. Update: Theres RTCPeerConnection in WebRTC , though the API is pretty horrible. _________ Anything else we still cant do and we still dont have an API to do so in the future? Say it in the comments! Or, if Im mistaken about one of the above and there is actually an active spec to address it, please point me to it! Why would you want to do these things client-side?! Everything that helps take load away from the server is good. The client is always one machine, everything on the server may end up running thousands of times per second if the web app succeeds, making the app slow and/or costly to run. I strongly believe in lean servers. Servers should only do things that architecturally need a server (e.g. centralized data storage), everything else is the clients job. Almost everything that we use native apps for, should (and eventually will) be doable by JavaScript.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Dabblet blog</title>
  <link>https://lea.verou.me/2012/01/dabblet-blog/</link>
  <pubDate>Sun, 17 May 2026 01:45:07 +0200</pubDate>
  <description>Not sure if you noticed, but Dabblet now has a blog: blog.dabblet.com I’ll post there about Dabblet updates and not flood my regular subscribers here who may not care. So, if you are interested on Dabblet’s progress, follow that blog or @dabblet on twitter. That was also an excuse to finally try tumblr . So far, so good. I love how it gives you custom domains and full theme control for free (hosted Wordpress charges for those). Gorgeous, GORGEOUS interface too. Most of the themes have markup from the 2005-2007 era, but that was no surprise. I customized the theme I picked to make it more HTML5-ey and more on par with dabblet’s style and it was super easy (though my attempt is by no means finished). There are a few shortcomings (like no titles for picture posts), but nothing too bad.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Dabblet blog</title>
  <link>https://lea.verou.me/2012/01/dabblet-blog/</link>
  <pubDate>Sun, 17 May 2026 01:45:07 +0200</pubDate>
  <description>Not sure if you noticed, but Dabblet now has a blog: blog.dabblet.com Ill post there about Dabblet updates and not flood my regular subscribers here who may not care. So, if you are interested on Dabblets progress, follow that blog or @dabblet on twitter. That was also an excuse to finally try tumblr . So far, so good. I love how it gives you custom domains and full theme control for free (hosted Wordpress charges for those). Gorgeous, GORGEOUS interface too. Most of the themes have markup from the 2005-2007 era, but that was no surprise. I customized the theme I picked to make it more HTML5-ey and more on par with dabblets style and it was super easy (though my attempt is by no means finished). There are a few shortcomings (like no titles for picture posts), but nothing too bad.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On web apps and their keyboard shortcuts</title>
  <link>https://lea.verou.me/2011/12/on-web-apps-and-their-keyboard-shortcuts/</link>
  <pubDate>Sun, 17 May 2026 01:45:05 +0200</pubDate>
  <description>Yesterday, I released dabblet. One of its aspects that I took extra care of, is it’s keyboard navigation. I used many of the commonly established application shortcuts to navigate and perform actions in it. Some of these naturally collided with the native browser shortcuts and I got a few bug reports about that. Actually, overriding the browser shortcuts was by design, and I’ll explain my point of view below. Native apps use these shortcuts all the time. For example, I press Cmd+1,2,3 etc in Espresso to navigate through files in my project. People press F1 for help. And so on. These shortcuts are so ingrained in our (power users) minds and so useful that we thoroughly miss them when they’re not there. Every time I press Cmd+1 in an OSX app and I don’t go to the first tab, I’m distraught. However, in web apps, these shortcuts are taken by the browser. We either have to use different shortcuts or accept overriding the browser’s defaults. Using different shortcuts seems to be considered best practice, but how useful are these shortcuts anyway? They have to be individually learned for every web app, and that’s hardly about memorizing the “keyboard shortcuts” list. Our muscles learn much more slowly than our minds. To be able to use these shortcuts as mindlessly as we use the regular application shortcuts, we need to spend a long time using the web app and those shortcuts. If we ever do get used to them that much, we’ll have trouble with the other shortcuts that most apps use, as our muscles will try to use the new ones. Using the de facto standard keyboard shortcuts carries no such issues. They take advantage of muscle memory from day one. If we advocate that web is the new native, it means our web apps should be entitled to everything native apps are. If native editors can use Cmd+1 to go to the first tab and F1 for help, so should a web editor. When you’re running a web app, the browser environment is merely a host, like your OS. The focus is the web app. When you’re working in a web app and you press a keyboard shortcut, chances are you’re looking to interact with that app, not with the browser Chrome. For example, I’m currently writing in Wordpress’ editor. When I press Cmd+S, I expect my draft to be saved, not the browser to attempt to save the current HTML page. Would it make sense if they wanted to be polite and chose a different shortcut, like Alt+S? I would have to learn the Save shortcut all over again and I’d forever confuse the two. Of course, it depends on how you define a web app. If we’re talking about a magazine website for example, you’re using the browser as a kind of reader. The app you’re using is still the browser, and overriding its keyboard shortcuts is bad. It’s a sometimes fine distinction, and many disagreements about this issue are basically disagreements about what constitutes a web app and how much of an application web apps are. So, what are your thoughts? Play it safe and be polite to the host or take advantage of muscle memory? Edit: Johnathan Snook posted these thoughts in the comments, and I thought his suggested approach is pure genius and every web UX person should read it: On Yahoo! Mail, we have this same problem. It’s an application with many of the same affordances of a desktop application. As a result, we want to have the same usability of a desktop application—including with keyboard shortcuts. In some cases, like Cmd-P for printing, we’ll override the browser default because the browser will not have the correct output. For something like tab selection/editing, we don’t override the defaults and instead, create alternate shortcuts for doing so. One thing I suggest you could try is to behave somewhat like overflow areas in a web page. When you scroll with a scroll mouse or trackpad in the area, the browser will scroll that area until it reaches it’s scroll limit and then will switch to scrolling the entire page. It would be interesting to experiment with this same approach with other in-page mechanisms. For example, with tabs, I often use Cmd-Shift-[ and Cmd-Shift-] to change tabs (versus Cmd-1/2/3, etc). You could have it do so within the page until it hits its limit (first tab/last tab) and then after that, let the event fall back to the browser. For Cmd-1, have it select the first tab. If the user is already on the first tab, have it fall back to the browser.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On web apps and their keyboard shortcuts</title>
  <link>https://lea.verou.me/2011/12/on-web-apps-and-their-keyboard-shortcuts/</link>
  <pubDate>Sun, 17 May 2026 01:45:05 +0200</pubDate>
  <description>Yesterday, I released dabblet. One of its aspects that I took extra care of, is its keyboard navigation. I used many of the commonly established application shortcuts to navigate and perform actions in it. Some of these naturally collided with the native browser shortcuts and I got a few bug reports about that. Actually, overriding the browser shortcuts was by design, and Ill explain my point of view below. Native apps use these shortcuts all the time. For example, I press Cmd+1,2,3 etc in Espresso to navigate through files in my project. People press F1 for help. And so on. These shortcuts are so ingrained in our (power users) minds and so useful that we thoroughly miss them when theyre not there. Every time I press Cmd+1 in an OSX app and I dont go to the first tab, Im distraught. However, in web apps, these shortcuts are taken by the browser. We either have to use different shortcuts or accept overriding the browsers defaults. Using different shortcuts seems to be considered best practice, but how useful are these shortcuts anyway? They have to be individually learned for every web app, and thats hardly about memorizing the keyboard shortcuts list. Our muscles learn much more slowly than our minds. To be able to use these shortcuts as mindlessly as we use the regular application shortcuts, we need to spend a long time using the web app and those shortcuts. If we ever do get used to them that much, well have trouble with the other shortcuts that most apps use, as our muscles will try to use the new ones. Using the de facto standard keyboard shortcuts carries no such issues. They take advantage of muscle memory from day one. If we advocate that web is the new native, it means our web apps should be entitled to everything native apps are. If native editors can use Cmd+1 to go to the first tab and F1 for help, so should a web editor. When youre running a web app, the browser environment is merely a host, like your OS. The focus is the web app. When youre working in a web app and you press a keyboard shortcut, chances are youre looking to interact with that app, not with the browser Chrome. For example, Im currently writing in Wordpress editor. When I press Cmd+S, I expect my draft to be saved, not the browser to attempt to save the current HTML page. Would it make sense if they wanted to be polite and chose a different shortcut, like Alt+S? I would have to learn the Save shortcut all over again and Id forever confuse the two. Of course, it depends on how you define a web app. If were talking about a magazine website for example, youre using the browser as a kind of reader. The app youre using is still the browser, and overriding its keyboard shortcuts is bad. Its a sometimes fine distinction, and many disagreements about this issue are basically disagreements about what constitutes a web app and how much of an application web apps are. So, what are your thoughts? Play it safe and be polite to the host or take advantage of muscle memory? Edit: Johnathan Snook posted these thoughts in the comments, and I thought his suggested approach is pure genius and every web UX person should read it: On Yahoo! Mail, we have this same problem. Its an application with many of the same affordances of a desktop application. As a result, we want to have the same usability of a desktop applicationincluding with keyboard shortcuts. In some cases, like Cmd-P for printing, well override the browser default because the browser will not have the correct output. For something like tab selection/editing, we dont override the defaults and instead, create alternate shortcuts for doing so. One thing I suggest you could try is to behave somewhat like overflow areas in a web page. When you scroll with a scroll mouse or trackpad in the area, the browser will scroll that area until it reaches its scroll limit and then will switch to scrolling the entire page. It would be interesting to experiment with this same approach with other in-page mechanisms. For example, with tabs, I often use Cmd-Shift-[ and Cmd-Shift-] to change tabs (versus Cmd-1/2/3, etc). You could have it do so within the page until it hits its limit (first tab/last tab) and then after that, let the event fall back to the browser. For Cmd-1, have it select the first tab. If the user is already on the first tab, have it fall back to the browser.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing dabblet: An interactive CSS playground</title>
  <link>https://lea.verou.me/2011/12/introducing-dabblet-an-interactive-css-playground/</link>
  <pubDate>Sun, 17 May 2026 01:45:04 +0200</pubDate>
  <description>I loved JSFiddle ever since I first used it. Being able to test something almost instantly and without littering my hard drive opened new possibilities for me. I use it daily for experiments, browser bug testcases, code snippet storage, code sharing and many other things. However, there were always a few things that bugged me: JSFiddle is very JS oriented, as you can tell even from the name itself JSFiddle is heavily server-side so there’s always at least the lag of an HTTP request every time you make an action. It makes sense not to run JS on every keystroke (JSBin does it and it’s super annoying, even caused me to fall in an infinite loop once) but CSS and HTML could be updated without any such problems. I’m a huge tabs fan, I hate spaces for indenting with a passion. Every time I want to test a considerable amount of CSS3, I need to include -prefix-free as a resource and I can’t save that preference or any other (like “No library”). Don’t get me wrong, I LOVE JSFiddle. It was a pioneer and it paved the way for all similar apps. It’s great for JavaScript experiments. But for pure CSS/HTML experiments, we can do better. The thought of making some interactive playground for CSS experiments was lingering in my mind for quite a while, but never attempted to start it as I knew it would be a lot of fascinating work and I wouldn’t be able to focus on anything else throughout. While I was writing my 24ways article , I wanted to include lots of CSS demos and I wanted the code to be editable and in some cases on top of the result to save space. JSFiddle’s embedding didn’t do that, so I decided to make something simple, just for that article. It quickly evolved to something much bigger, and yes I was right, it was lots of fascinating work and I wasn’t able to focus on anything else throughout. I even delayed my 24ways article for the whole time I was developing it, and I’m grateful that Drew was so patient. After 3 weeks of working on it, I present dabblet . Features So what does dabblet have that similar apps don’t? Here’s a list: Realtime updates, no need to press a button or anything Saves everything to Github gists , so even if dabblet goes away (not that I plan to!) you won’t lose your data No page reloads even on saving, everything is XHR-ed Many familiar keyboard shortcuts Small inline previewers for many kinds of CSS values, in particular for: colors , absolute lengths , durations, angles , easing functions and gradients . Check them all in this dabblet . Automatically adds prefixes with -prefix-free , to speed up testing Use the Alt key and the up/down arrows to increment/decrement , and values. Dabblet is open source under a NPOSL 3.0 license You can save anonymously even when you are logged in Multiple view modes: Result behind code, Split views (horizontal or vertical), separate tabs. View modes can be saved as a personal preference or in the gists (as different demos may look better with different view modes) Editable even from an embedded iframe (to embed just use the same dabblet URL, it will be automatically adjusted through media queries) Here’s a rough screencast that I made in 10 minutes to showcase some of dabblet’s features. There’s no sound and is super sloppy but I figured even this lame excuse of a screencast is better than none. I’m hoping to make a proper screencast in the next few days. However, dabblet is still very new. I wouldn’t even call it a beta yet, more like an Alpha. I’ve tried to iron out every bug I could find, but I’m sure there are many more lingering around. Also, it has some limitations, but it’s my top priority to fix them: It’s currently not possible to see or link to older versions of a dabblet. You can of course use Github to view them. It currently only works in modern, CORS-enabled browsers. Essentially Chrome, Safari and Firefox. I intend to support Opera too, once Opera 12 comes out. As for IE, I’ll bother with it when a significant percentage of web developers start using it as their main browser. Currently, I don’t know anyone that does. It doesn’t yet work very well on mobile but I’m working on it and it’s a top priority You can’t yet add other scripts like LESS or remove -prefix-free. Hasn’t been tested in Windows very much, so not sure what issues it might have there. I hope you enjoy using it as much as I enjoyed making it. Please report any bugs and suggest new features in its bug tracker . Examples Here are some dabblets that should get you started: https://dabblet.com/gist/1441328 https://dabblet.com/gist/1454230 https://dabblet.com/gist/1454409 https://dabblet.com/gist/1457668 https://dabblet.com/gist/1457677 https://dabblet.com/gist/1421054 https://dabblet.com/gist/1454889 Credits Roman Komarov helped tremendously by doing QA work on dabblet. Without his efforts, it would have been super buggy and much less polished. I’d also like to thank David Storey for coming up with the name “dabblet” and for his support throughout these 3 weeks. Last but not least, I’d also like to thank Oli Studholme and Rich Clark for promoting dabblet in their .net magazine articles even before its release. Update: Dabblet has its own twitter account now: Follow @dabblet</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Introducing dabblet: An interactive CSS playground</title>
  <link>https://lea.verou.me/2011/12/introducing-dabblet-an-interactive-css-playground/</link>
  <pubDate>Sun, 17 May 2026 01:45:04 +0200</pubDate>
  <description>I loved JSFiddle ever since I first used it. Being able to test something almost instantly and without littering my hard drive opened new possibilities for me. I use it daily for experiments, browser bug testcases, code snippet storage, code sharing and many other things. However, there were always a few things that bugged me: JSFiddle is very JS oriented, as you can tell even from the name itself JSFiddle is heavily server-side so theres always at least the lag of an HTTP request every time you make an action. It makes sense not to run JS on every keystroke (JSBin does it and its super annoying, even caused me to fall in an infinite loop once) but CSS and HTML could be updated without any such problems. Im a huge tabs fan, I hate spaces for indenting with a passion. Every time I want to test a considerable amount of CSS3, I need to include -prefix-free as a resource and I cant save that preference or any other (like No library). Dont get me wrong, I LOVE JSFiddle. It was a pioneer and it paved the way for all similar apps. Its great for JavaScript experiments. But for pure CSS/HTML experiments, we can do better. The thought of making some interactive playground for CSS experiments was lingering in my mind for quite a while, but never attempted to start it as I knew it would be a lot of fascinating work and I wouldnt be able to focus on anything else throughout. While I was writing my 24ways article , I wanted to include lots of CSS demos and I wanted the code to be editable and in some cases on top of the result to save space. JSFiddles embedding didnt do that, so I decided to make something simple, just for that article. It quickly evolved to something much bigger, and yes I was right, it was lots of fascinating work and I wasnt able to focus on anything else throughout. I even delayed my 24ways article for the whole time I was developing it, and Im grateful that Drew was so patient. After 3 weeks of working on it, I present dabblet . Features So what does dabblet have that similar apps dont? Heres a list: Realtime updates, no need to press a button or anything Saves everything to Github gists , so even if dabblet goes away (not that I plan to!) you wont lose your data No page reloads even on saving, everything is XHR-ed Many familiar keyboard shortcuts Small inline previewers for many kinds of CSS values, in particular for: colors , absolute lengths , durations, angles , easing functions and gradients . Check them all in this dabblet . Automatically adds prefixes with -prefix-free , to speed up testing Use the Alt key and the up/down arrows to increment/decrement , and values. Dabblet is open source under a NPOSL 3.0 license You can save anonymously even when you are logged in Multiple view modes: Result behind code, Split views (horizontal or vertical), separate tabs. View modes can be saved as a personal preference or in the gists (as different demos may look better with different view modes) Editable even from an embedded iframe (to embed just use the same dabblet URL, it will be automatically adjusted through media queries) Heres a rough screencast that I made in 10 minutes to showcase some of dabblets features. Theres no sound and is super sloppy but I figured even this lame excuse of a screencast is better than none. Im hoping to make a proper screencast in the next few days. However, dabblet is still very new. I wouldnt even call it a beta yet, more like an Alpha. Ive tried to iron out every bug I could find, but Im sure there are many more lingering around. Also, it has some limitations, but its my top priority to fix them: Its currently not possible to see or link to older versions of a dabblet. You can of course use Github to view them. It currently only works in modern, CORS-enabled browsers. Essentially Chrome, Safari and Firefox. I intend to support Opera too, once Opera 12 comes out. As for IE, Ill bother with it when a significant percentage of web developers start using it as their main browser. Currently, I dont know anyone that does. It doesnt yet work very well on mobile but Im working on it and its a top priority You cant yet add other scripts like LESS or remove -prefix-free. Hasnt been tested in Windows very much, so not sure what issues it might have there. I hope you enjoy using it as much as I enjoyed making it. Please report any bugs and suggest new features in its bug tracker . Examples Here are some dabblets that should get you started: https://dabblet.com/gist/1441328 https://dabblet.com/gist/1454230 https://dabblet.com/gist/1454409 https://dabblet.com/gist/1457668 https://dabblet.com/gist/1457677 https://dabblet.com/gist/1421054 https://dabblet.com/gist/1454889 Credits Roman Komarov helped tremendously by doing QA work on dabblet. Without his efforts, it would have been super buggy and much less polished. Id also like to thank David Storey for coming up with the name dabblet and for his support throughout these 3 weeks. Last but not least, Id also like to thank Oli Studholme and Rich Clark for promoting dabblet in their .net magazine articles even before its release. Update: Dabblet has its own twitter account now: Follow @dabblet</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Vendor prefixes have failed, what’s next?</title>
  <link>https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/</link>
  <pubDate>Sun, 17 May 2026 01:45:03 +0200</pubDate>
  <description>Edit: This was originally written to be posted in www-style , the mailing list for CSS development. I thought it might be a good idea to post it here as other people might be interested too. It wasn’t. Most people commenting didn’t really get the point of the article and thought I’m suggesting we should simply drop prefixes. Others think that it’s an acceptable solution for the CSS WG if CSS depends on external libraries like my own -prefix-free or LESS and SASS. I guess it was an failure of my behalf (“Know your audience”) and thus I’m disabling comments. Discussion about prefixes was recently stirred up again by an article by Henri Sivonen , so the CSS WG started debating for the 100th time about when features should become unprefixed. I think we need to think out of the box and come up with new strategies to solve the issues that vendor prefixes were going to fix. Vendor prefixes have failed and we can’t solve their issues by just unprefixing properties more early. Issues The above might seem a bold statement, so let me try to support it by recapping the serious issues we run into with vendor prefixes: 1. Unnecessary bloat Authors need to use prefixes even when the implementations are already interoperable. As a result, they end up pointlessly duplicating the declarations, making maintenance hard and/or introducing overhead from CSS pre- and post-processors to take care of this duplication. We need to find a way to reduce this bloat to only the cases where different declarations are actually needed . 2. Spec changes still break existing content The biggest advantage of the current situation was supposed to be that spec changes would not break existing content, but prefixes have failed to even do this. The thing is, most authors will use something if it’s available , no questions asked. I doubt anyone that has done any real web development would disagree with that. And in most cases, they will prefer a slightly different application of a feature than none at all, so they use prefixed properties along with unprefixed. Then, when the WG makes a backwards-incompatible change, existing content breaks. I don’t think this can really be addressed in any way except disabling the feature by default in public builds. Any kind of prefix or notation is pointless to stop this, we’ll always run into the same issue. If we disable the feature by default, almost nobody will use it since they can’t tell visitors to change their browser settings. Do we really want that? Yes, the WG will be able to make all the changes they want, but then then who will give feedback for these changes? Certainly not authors, as they will effectively have zero experience working with the feature as most of them don’t have the time to play around with features they can’t use right now. I think we should accept that changes will break *some* existing content, and try to standardize faster, instead of having tons of features in WD limbo. However, I still think that there should be some kind of notation to denote that a feature is experimental so that at least authors know what they’re getting themselves into by using it and for browsers to be able to experiment a bit more openly. I don’t think that vendor prefixes are the right notation for this though. 3. Web development has become a popularity contest I’ll explain this with an example: CSS animations were first supported by WebKit. People only used the -webkit- prefix with them and they were fine with it. Then Firefox also implemented them, and most authors started adding -moz- to their use cases. Usually only to the new ones, their old ones are still WebKit only. After a while, Microsoft announced CSS animations in IE10. Some authors started adding -ms- prefixes to their new websites, some others didn’t because IE10 isn’t out yet. When IE10 is out, they still won’t add it because their current use cases will be for the most part not maintained any more. Some authors don’t even add -ms- because they dislike IE. Opera will soon implement CSS animations. Who will really go back and add -o- versions? Most people will not care, because they think Opera has too little market share to warrant the extra bloat. So browsers appear to support less features, only because authors have to take an extra step to explicitly support them. Browsers do not display pages with their full capabilities because authors were lazy, ignorant, or forgetful. This is unfair to both browser vendors and web users. We need to find a way to (optionally?) decouple implementation and browser vendor in the experimental feature notation. Ideas There is a real problem that vendor prefixes attempted to solve, but vendor prefixes didn’t prove out to be a good solution. I think we should start thinking outside the box and propose new ideas instead of sticking to vendor prefixes and debating their duration. I’ll list here a few of my ideas and I’m hoping others will follow suit. 1. Generic prefix (-x- or something else) and/or new @rule A generic prefix has been proposed before , and usually the argument against it is that different vendors may have incompatible implementations. This could be addressed at a more general level, instead of having the prefix on every feature: An @-rule for addressing specific vendors. for example: @vendor (moz,webkit,o) { .foo { -x-property: value; } } @vendor (ms) { .foo { -x-property: other-value; } } A potential downside is selector duplication, but remember: The @vendor rule would ONLY be used when implementations are actually incompatible . Of course, there’s the potential for misuse, as authors could end up writing separate CSS for separate browsers using this new rule. However, I think we’re in a stage where most authors have realized that this is a bad idea, and if they want to do it, they can do it now anyway (for example, by using @-moz-document to target Moz and so on) 2. Supporting both prefixed and unprefixed for WD features This delegates the decision to the author, instead of the WG and implementors. The author could choose to play it safe and use vendor prefixes or risk it in order to reduce bloat on a per-feature basis. I guess a problem with this approach is that extra properties mean extra memory, but it’s something that many browsers already do when they start supporting a property unprefixed and don’t drop the prefixed version like they should. Note: While this post was still in draft, I was informed that Alex Mogilevsky has suggested something very similar. Read his proposal . 3. Prefixes for versioning, not vendors When a browser implements a property for the first time, they will use the prefix -a- . Then, when another browser implements that feature, they look at the former browser’s implementation, and if theirs is compatible, they use the same prefix. If it’s incompatible, they increment it by one, using -b- and so on. A potential problem with this is collisions: Vendors using the same prefix not because their implementations are compatible but because they developed them almost simultaneously and didn’t know about each other’s implementation. Also, it causes trouble for the smaller vendors that might want to implement a feature first. We need more ideas Even if the above are not good ideas, I’m hoping that they’ll inspire others to come up with something better. I think we need more ideas about this, rather than more debates about fine-tuning the details of one bad solution.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Vendor prefixes have failed, whats next?</title>
  <link>https://lea.verou.me/2011/11/vendor-prefixes-have-failed-whats-next/</link>
  <pubDate>Sun, 17 May 2026 01:45:03 +0200</pubDate>
  <description>Edit: This was originally written to be posted in www-style , the mailing list for CSS development. I thought it might be a good idea to post it here as other people might be interested too. It wasnt. Most people commenting didnt really get the point of the article and thought Im suggesting we should simply drop prefixes. Others think that its an acceptable solution for the CSS WG if CSS depends on external libraries like my own -prefix-free or LESS and SASS. I guess it was an failure of my behalf (Know your audience) and thus Im disabling comments. Discussion about prefixes was recently stirred up again by an article by Henri Sivonen , so the CSS WG started debating for the 100th time about when features should become unprefixed. I think we need to think out of the box and come up with new strategies to solve the issues that vendor prefixes were going to fix. Vendor prefixes have failed and we cant solve their issues by just unprefixing properties more early. Issues The above might seem a bold statement, so let me try to support it by recapping the serious issues we run into with vendor prefixes: 1. Unnecessary bloat Authors need to use prefixes even when the implementations are already interoperable. As a result, they end up pointlessly duplicating the declarations, making maintenance hard and/or introducing overhead from CSS pre- and post-processors to take care of this duplication. We need to find a way to reduce this bloat to only the cases where different declarations are actually needed . 2. Spec changes still break existing content The biggest advantage of the current situation was supposed to be that spec changes would not break existing content, but prefixes have failed to even do this. The thing is, most authors will use something if its available , no questions asked. I doubt anyone that has done any real web development would disagree with that. And in most cases, they will prefer a slightly different application of a feature than none at all, so they use prefixed properties along with unprefixed. Then, when the WG makes a backwards-incompatible change, existing content breaks. I dont think this can really be addressed in any way except disabling the feature by default in public builds. Any kind of prefix or notation is pointless to stop this, well always run into the same issue. If we disable the feature by default, almost nobody will use it since they cant tell visitors to change their browser settings. Do we really want that? Yes, the WG will be able to make all the changes they want, but then then who will give feedback for these changes? Certainly not authors, as they will effectively have zero experience working with the feature as most of them dont have the time to play around with features they cant use right now. I think we should accept that changes will break *some* existing content, and try to standardize faster, instead of having tons of features in WD limbo. However, I still think that there should be some kind of notation to denote that a feature is experimental so that at least authors know what theyre getting themselves into by using it and for browsers to be able to experiment a bit more openly. I dont think that vendor prefixes are the right notation for this though. 3. Web development has become a popularity contest Ill explain this with an example: CSS animations were first supported by WebKit. People only used the -webkit- prefix with them and they were fine with it. Then Firefox also implemented them, and most authors started adding -moz- to their use cases. Usually only to the new ones, their old ones are still WebKit only. After a while, Microsoft announced CSS animations in IE10. Some authors started adding -ms- prefixes to their new websites, some others didnt because IE10 isnt out yet. When IE10 is out, they still wont add it because their current use cases will be for the most part not maintained any more. Some authors dont even add -ms- because they dislike IE. Opera will soon implement CSS animations. Who will really go back and add -o- versions? Most people will not care, because they think Opera has too little market share to warrant the extra bloat. So browsers appear to support less features, only because authors have to take an extra step to explicitly support them. Browsers do not display pages with their full capabilities because authors were lazy, ignorant, or forgetful. This is unfair to both browser vendors and web users. We need to find a way to (optionally?) decouple implementation and browser vendor in the experimental feature notation. Ideas There is a real problem that vendor prefixes attempted to solve, but vendor prefixes didnt prove out to be a good solution. I think we should start thinking outside the box and propose new ideas instead of sticking to vendor prefixes and debating their duration. Ill list here a few of my ideas and Im hoping others will follow suit. 1. Generic prefix (-x- or something else) and/or new @rule A generic prefix has been proposed before , and usually the argument against it is that different vendors may have incompatible implementations. This could be addressed at a more general level, instead of having the prefix on every feature: An @-rule for addressing specific vendors. for example: @vendor (moz,webkit,o) { .foo { -x-property: value; } } @vendor (ms) { .foo { -x-property: other-value; } } A potential downside is selector duplication, but remember: The @vendor rule would ONLY be used when implementations are actually incompatible . Of course, theres the potential for misuse, as authors could end up writing separate CSS for separate browsers using this new rule. However, I think were in a stage where most authors have realized that this is a bad idea, and if they want to do it, they can do it now anyway (for example, by using @-moz-document to target Moz and so on) 2. Supporting both prefixed and unprefixed for WD features This delegates the decision to the author, instead of the WG and implementors. The author could choose to play it safe and use vendor prefixes or risk it in order to reduce bloat on a per-feature basis. I guess a problem with this approach is that extra properties mean extra memory, but its something that many browsers already do when they start supporting a property unprefixed and dont drop the prefixed version like they should. Note: While this post was still in draft, I was informed that Alex Mogilevsky has suggested something very similar. Read his proposal . 3. Prefixes for versioning, not vendors When a browser implements a property for the first time, they will use the prefix -a- . Then, when another browser implements that feature, they look at the former browsers implementation, and if theirs is compatible, they use the same prefix. If its incompatible, they increment it by one, using -b- and so on. A potential problem with this is collisions: Vendors using the same prefix not because their implementations are compatible but because they developed them almost simultaneously and didnt know about each others implementation. Also, it causes trouble for the smaller vendors that might want to implement a feature first. We need more ideas Even if the above are not good ideas, Im hoping that theyll inspire others to come up with something better. I think we need more ideas about this, rather than more debates about fine-tuning the details of one bad solution.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Animatable: A CSS transitions gallery</title>
  <link>https://lea.verou.me/2011/10/animatable-a-css-transitions-gallery/</link>
  <pubDate>Sun, 17 May 2026 01:25:18 +0200</pubDate>
  <description>What kind of transitions can you create with only one property? This is what my new experiment, animatable aims to explore. It’s essentially a gallery of basic transitions. It aims to show how different animatable properties look when they transition and to broaden our horizons about which properties can be animated. Hover over the demos to see the animation in action, or click “Animate All” to see all of them (warning: might induce nausea, headache and seizures :P ). You can also click on it to see more details and get a permalink . Instead of clicking, you can also navigate with the arrow keys and press Esc to return to the main listing. Fork it on Github and add your own ideas. Be sure to add your twitter username to them as a data-author attribute! I’ve only tested in Firefox and Chrome for OSX so far. Not sure which other browsers are supported. However, since it uses CSS animations, we know for sure that it won’t work in browsers that don’t support CSS animations. Hope you enjoy it :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Animatable: A CSS transitions gallery</title>
  <link>https://lea.verou.me/2011/10/animatable-a-css-transitions-gallery/</link>
  <pubDate>Sun, 17 May 2026 01:25:18 +0200</pubDate>
  <description>What kind of transitions can you create with only one property? This is what my new experiment, animatable aims to explore. Its essentially a gallery of basic transitions. It aims to show how different animatable properties look when they transition and to broaden our horizons about which properties can be animated. Hover over the demos to see the animation in action, or click Animate All to see all of them (warning: might induce nausea, headache and seizures :P ). You can also click on it to see more details and get a permalink . Instead of clicking, you can also navigate with the arrow keys and press Esc to return to the main listing. Fork it on Github and add your own ideas. Be sure to add your twitter username to them as a data-author attribute! Ive only tested in Firefox and Chrome for OSX so far. Not sure which other browsers are supported. However, since it uses CSS animations, we know for sure that it wont work in browsers that dont support CSS animations. Hope you enjoy it :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from Fronteers, JSConf EU, Frontend and FromTheFront</title>
  <link>https://lea.verou.me/2011/10/my-experience-from-fronteers-jsconf-eu-frontend-and-fromthefront/</link>
  <pubDate>Sun, 17 May 2026 01:25:17 +0200</pubDate>
  <description>This month has been very busy conference-wise. I had 4 conferences in a row, so I was flying from country to country and giving talks for 2 weeks. As I usually do after conferences, this post sums up my experiences and feedback I got from these conferences, in chronological order. FromTheFront This was a rather low-budget Italian conference that took place in Cesena, a city near Bologna. Despite the extremely low ticket price, they managed to pull off a very decent one day conference, which is very admirable. Italian food is so good that I’d recommend visiting this country even if it’s just for the food! They were very nice hosts, and I thoroughly enjoyed my time there. My talk was right after Jeremy Keith ’s, who is a very well-known and experienced speaker that knows how to make audiences delirious (in a good way), so I was naturally a bit nervous about the unavoidable comparison. Despite my fears, my talk was very well received. Here’s a sample of the twitter feedback I got: https://twitter.com/cedmax/status/119499350039740416 https://twitter.com/caludio/status/119500241358696448 https://twitter.com/andypanix/status/119502579943223296 https://twitter.com/verlok/status/119511814827556864 https://twitter.com/fburatti/status/119512158500433921 https://twitter.com/Facens/status/119513991616794624 https://twitter.com/matteocollina/status/119506533548699648 JSConf EU Next stop was Berlin and JSConf’s European sister conference. This was one of the most well organized conferences I’ve been to: The food, the coffee, the afterparties, the wifi, the projectors, everything was top notch. Also, it had a get-together the day after the conference (called “hangover.js”) which I think is great and more conferences should start adopting this tradition. It eases the pain of the conference being over and you get to say goodbye to a few folks you weren’t able to catch at the afterparty. It also featured many cool ideas, like a gal drawing live visualizations of the talks ( Here’s mine ) and a singer to open the conference in the first day singing a song to …Brendan Eich (!). I made new friends, had lots of fun and everything was awesome. I was a bit more nervous about my talk for two reasons: Firstly, it was my first JavaScript talk, and secondly, it had no live demos like my CSS talks, which is a big part of why people like them. It went much better than I expected, and I got very good feedback and even though I went hugely overtime (I had 30 minutes and did 55!) nobody complained. Thankfully, it was right before lunch so I didn’t eat up another speaker’s time (which is part of the reason I love the pre-lunch spot so much). I didn’t get the super-enthusiastic feedback I get from my CSS talks, but it was good enough to not be disappointed. Here’s a sample: https://twitter.com/codepo8/status/120450328318590976 https://twitter.com/WebReflection/status/120457934152007680 https://twitter.com/nddrylliog/status/120457956830621696 https://twitter.com/cramforce/status/120459076604928000 https://twitter.com/wpbasti/status/120462654316883968 https://twitter.com/claudiopro/status/120465480380203008 https://twitter.com/VeganBen/status/120466919001300992 https://twitter.com/nddrylliog/status/120469327110619137 You can find my slides on Speakerdeck , Slideshare or the HTML version on my website . Fronteers I was looking forward to Fronteers the most, since it’s my favorite conference. It might not be the one with the most money or the biggest, but it has a special place in my heart for a number of different reasons (not all of which I can write in a public blog post). It was the first international conference I ever attended (in 2010) and I’ve met there so many people I used to only know (and admire) as a name &amp; avatar before. It’s the conference I’ve had the most fun at, in both years I’ve been there. Everyone, the volunteers, the attendees, the speakers, everyone is awesome. There is something magic about this conference, as most of its speakers and attendees think about it in the same way (Christian Heilmann for example calls it “his special conference” and he goes to A LOT of conferences). It doesn’t just feel like a professional conference, it feels like a big, loving, open, web development family that gets together once a year to celebrate the advances in our field. But this time, I wasn’t just an attendee. I wasn’t a regular speaker either. I was also hosting a workshop, my first full day workshop. I was super stressed about that, and in retrospect, it was the most exhausting thing I have ever done. Some other speakers told me it felt so exhausting because it was my first, I really hope they’re right. Luckily, attendees loved it, and they didn’t seem to notice my progressively getting tired after the 4th hour. Here’s some of the feedback I got: https://twitter.com/flyingpinguin/status/121507980964409346 https://twitter.com/V\_v\_V/status/121511282758258688 https://twitter.com/Jessman5/status/121519177977708547 https://twitter.com/tforza/status/121524521709744128 https://twitter.com/ronderksen/status/121531111665971200 My talk was the next day, and even though I was afraid it would be bad due to being tired from the workshop and the pre-party, I think it was my best talk ever. I was much more relaxed, and I got the most enthusiastic feedback I ever have. My hand literally got tired favoriting tweets, and I’m pretty sure I missed some. Here’s a small sample: https://twitter.com/smashingmag/status/121881600366608384 https://twitter.com/ldebrouwer/status/121881926587002881 https://twitter.com/wnas/status/121882159756750848 https://twitter.com/FronteersConf/status/121883462251720704 https://twitter.com/ldebrouwer/status/121884227083042816 https://twitter.com/addy\_osmani/status/121885202581684224 https://twitter.com/LuukWilms/status/121887866258333697 https://twitter.com/Georg\_Tavonius/status/121888702678044672 https://twitter.com/okonetchnikov/status/121889094346350592 https://twitter.com/decthomas/status/121889393752543233 https://twitter.com/eising/status/121889474782314496 https://twitter.com/artjulian/status/121889486358589440 https://twitter.com/ronderksen/status/121889810230157312 https://twitter.com/dantz/status/121890121669804032 https://twitter.com/nerdismus/status/121890225273323520 https://twitter.com/okke29/status/121890604610355200 https://twitter.com/mylittletony/status/121891613139144704 https://twitter.com/LuukWilms/status/121893686874345472 https://twitter.com/soswow/status/121893745921769472 https://twitter.com/mennovanslooten/status/121893921394655232 https://twitter.com/FronteersConf/status/121894605963804673 https://twitter.com/edgarleijs/status/121894745596362752 https://twitter.com/usethetics/status/121894796573937664 https://twitter.com/peterpeerdeman/status/121895792301707264 https://twitter.com/bjornjohansen/status/121896246809083904 https://twitter.com/cruster/status/121896792873897984 https://twitter.com/decthomas/status/121911585227816960 https://twitter.com/aral/status/121920658778234880 https://twitter.com/ldebrouwer/status/121992421474177025 https://twitter.com/wiekatz/status/121992495436537856 https://twitter.com/GovertVerschuur/status/121993644482891776 https://twitter.com/johannesaxner/status/121999077859790849 https://twitter.com/flurin/status/122042528794017792 My slides are now online at Speakerdeck , Slideshare and the interactive version on my website . Frontend 2011 Oslo is a city I’ve been to many times in the past, so there was nothing new to see there. I didn’t make it to the speakers dinner &amp; pre-party due to my late flight, which kinda sucked but it’s my fault since it took me a long while to decide on my flight dates. The conference itself was a bit more design-focused that I’d like, but very well organized. It took place in the same hotel the speakers were staying at, which is always a good thing. It also had the best coffee I’ve ever drank at a conference, and one of the best I’ve tasted in general. I also loved the idea of having multiple projectors, so that everyone in the audience can see clearly. They had the very original idea of not only drawing caricatures for every speaker ( here’s mine , I also got it in a nice frame) but also having the artist in the venue to draw caricatures for attendees as well! My talk went smoothly, and received very good feedback: https://twitter.com/iceMagic/status/123737225560199168 https://twitter.com/gustaff\_weldon/status/123741227563753472 https://twitter.com/kaelig/status/123741479142297601 https://twitter.com/orioltf/status/123742189896474624 https://twitter.com/chemikpil/status/123742210842820608 https://twitter.com/gav\_taylor/status/123742248646094848 https://twitter.com/mikaeljorhult/status/123742623524585473 https://twitter.com/ken\_guru/status/123744573666246656 https://twitter.com/cornae/status/123803246300114945 That’s it. I now get to rest for a while. Next stop is SWDC in November, which will host the première of my new talk “CSS in the 4th dimension: Not your daddy’s CSS animations” which will be about CSS transitions &amp; animations, from the basics all way to badass secrets. Thanks to all the conference organizers for inviting me and for the attendees for attending and giving feedback on my talks. You are all awesome, and it was the best 2 weeks ever. :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Optimizing long lists of yes/no values with JavaScript</title>
  <link>https://lea.verou.me/2011/10/optimizing-long-lists-of-yesno-values-with-javascript/</link>
  <pubDate>Sun, 17 May 2026 01:25:17 +0200</pubDate>
  <description>My newest article on Smashing Magazine’s coding section is for the geekiest among you. It’s about how you can pack long lists of boolean values into a string in the most space-efficient way. Hope you enjoy it :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from Fronteers, JSConf EU, Frontend and FromTheFront</title>
  <link>https://lea.verou.me/2011/10/my-experience-from-fronteers-jsconf-eu-frontend-and-fromthefront/</link>
  <pubDate>Sun, 17 May 2026 01:25:17 +0200</pubDate>
  <description>This month has been very busy conference-wise. I had 4 conferences in a row, so I was flying from country to country and giving talks for 2 weeks. As I usually do after conferences, this post sums up my experiences and feedback I got from these conferences, in chronological order. FromTheFront This was a rather low-budget Italian conference that took place in Cesena, a city near Bologna. Despite the extremely low ticket price, they managed to pull off a very decent one day conference, which is very admirable. Italian food is so good that Id recommend visiting this country even if its just for the food! They were very nice hosts, and I thoroughly enjoyed my time there. My talk was right after Jeremy Keith s, who is a very well-known and experienced speaker that knows how to make audiences delirious (in a good way), so I was naturally a bit nervous about the unavoidable comparison. Despite my fears, my talk was very well received. Heres a sample of the twitter feedback I got: https://twitter.com/cedmax/status/119499350039740416 https://twitter.com/caludio/status/119500241358696448 https://twitter.com/andypanix/status/119502579943223296 https://twitter.com/verlok/status/119511814827556864 https://twitter.com/fburatti/status/119512158500433921 https://twitter.com/Facens/status/119513991616794624 https://twitter.com/matteocollina/status/119506533548699648 JSConf EU Next stop was Berlin and JSConfs European sister conference. This was one of the most well organized conferences Ive been to: The food, the coffee, the afterparties, the wifi, the projectors, everything was top notch. Also, it had a get-together the day after the conference (called hangover.js) which I think is great and more conferences should start adopting this tradition. It eases the pain of the conference being over and you get to say goodbye to a few folks you werent able to catch at the afterparty. It also featured many cool ideas, like a gal drawing live visualizations of the talks ( Heres mine ) and a singer to open the conference in the first day singing a song to Brendan Eich (!). I made new friends, had lots of fun and everything was awesome. I was a bit more nervous about my talk for two reasons: Firstly, it was my first JavaScript talk, and secondly, it had no live demos like my CSS talks, which is a big part of why people like them. It went much better than I expected, and I got very good feedback and even though I went hugely overtime (I had 30 minutes and did 55!) nobody complained. Thankfully, it was right before lunch so I didnt eat up another speakers time (which is part of the reason I love the pre-lunch spot so much). I didnt get the super-enthusiastic feedback I get from my CSS talks, but it was good enough to not be disappointed. Heres a sample: https://twitter.com/codepo8/status/120450328318590976 https://twitter.com/WebReflection/status/120457934152007680 https://twitter.com/nddrylliog/status/120457956830621696 https://twitter.com/cramforce/status/120459076604928000 https://twitter.com/wpbasti/status/120462654316883968 https://twitter.com/claudiopro/status/120465480380203008 https://twitter.com/VeganBen/status/120466919001300992 https://twitter.com/nddrylliog/status/120469327110619137 You can find my slides on Speakerdeck , Slideshare or the HTML version on my website . Fronteers I was looking forward to Fronteers the most, since its my favorite conference. It might not be the one with the most money or the biggest, but it has a special place in my heart for a number of different reasons (not all of which I can write in a public blog post). It was the first international conference I ever attended (in 2010) and Ive met there so many people I used to only know (and admire) as a name &amp; avatar before. Its the conference Ive had the most fun at, in both years Ive been there. Everyone, the volunteers, the attendees, the speakers, everyone is awesome. There is something magic about this conference, as most of its speakers and attendees think about it in the same way (Christian Heilmann for example calls it his special conference and he goes to A LOT of conferences). It doesnt just feel like a professional conference, it feels like a big, loving, open, web development family that gets together once a year to celebrate the advances in our field. But this time, I wasnt just an attendee. I wasnt a regular speaker either. I was also hosting a workshop, my first full day workshop. I was super stressed about that, and in retrospect, it was the most exhausting thing I have ever done. Some other speakers told me it felt so exhausting because it was my first, I really hope theyre right. Luckily, attendees loved it, and they didnt seem to notice my progressively getting tired after the 4th hour. Heres some of the feedback I got: https://twitter.com/flyingpinguin/status/121507980964409346 https://twitter.com/V\_v\_V/status/121511282758258688 https://twitter.com/Jessman5/status/121519177977708547 https://twitter.com/tforza/status/121524521709744128 https://twitter.com/ronderksen/status/121531111665971200 My talk was the next day, and even though I was afraid it would be bad due to being tired from the workshop and the pre-party, I think it was my best talk ever. I was much more relaxed, and I got the most enthusiastic feedback I ever have. My hand literally got tired favoriting tweets, and Im pretty sure I missed some. Heres a small sample: https://twitter.com/smashingmag/status/121881600366608384 https://twitter.com/ldebrouwer/status/121881926587002881 https://twitter.com/wnas/status/121882159756750848 https://twitter.com/FronteersConf/status/121883462251720704 https://twitter.com/ldebrouwer/status/121884227083042816 https://twitter.com/addy\_osmani/status/121885202581684224 https://twitter.com/LuukWilms/status/121887866258333697 https://twitter.com/Georg\_Tavonius/status/121888702678044672 https://twitter.com/okonetchnikov/status/121889094346350592 https://twitter.com/decthomas/status/121889393752543233 https://twitter.com/eising/status/121889474782314496 https://twitter.com/artjulian/status/121889486358589440 https://twitter.com/ronderksen/status/121889810230157312 https://twitter.com/dantz/status/121890121669804032 https://twitter.com/nerdismus/status/121890225273323520 https://twitter.com/okke29/status/121890604610355200 https://twitter.com/mylittletony/status/121891613139144704 https://twitter.com/LuukWilms/status/121893686874345472 https://twitter.com/soswow/status/121893745921769472 https://twitter.com/mennovanslooten/status/121893921394655232 https://twitter.com/FronteersConf/status/121894605963804673 https://twitter.com/edgarleijs/status/121894745596362752 https://twitter.com/usethetics/status/121894796573937664 https://twitter.com/peterpeerdeman/status/121895792301707264 https://twitter.com/bjornjohansen/status/121896246809083904 https://twitter.com/cruster/status/121896792873897984 https://twitter.com/decthomas/status/121911585227816960 https://twitter.com/aral/status/121920658778234880 https://twitter.com/ldebrouwer/status/121992421474177025 https://twitter.com/wiekatz/status/121992495436537856 https://twitter.com/GovertVerschuur/status/121993644482891776 https://twitter.com/johannesaxner/status/121999077859790849 https://twitter.com/flurin/status/122042528794017792 My slides are now online at Speakerdeck , Slideshare and the interactive version on my website . Frontend 2011 Oslo is a city Ive been to many times in the past, so there was nothing new to see there. I didnt make it to the speakers dinner &amp; pre-party due to my late flight, which kinda sucked but its my fault since it took me a long while to decide on my flight dates. The conference itself was a bit more design-focused that Id like, but very well organized. It took place in the same hotel the speakers were staying at, which is always a good thing. It also had the best coffee Ive ever drank at a conference, and one of the best Ive tasted in general. I also loved the idea of having multiple projectors, so that everyone in the audience can see clearly. They had the very original idea of not only drawing caricatures for every speaker ( heres mine , I also got it in a nice frame) but also having the artist in the venue to draw caricatures for attendees as well! My talk went smoothly, and received very good feedback: https://twitter.com/iceMagic/status/123737225560199168 https://twitter.com/gustaff\_weldon/status/123741227563753472 https://twitter.com/kaelig/status/123741479142297601 https://twitter.com/orioltf/status/123742189896474624 https://twitter.com/chemikpil/status/123742210842820608 https://twitter.com/gav\_taylor/status/123742248646094848 https://twitter.com/mikaeljorhult/status/123742623524585473 https://twitter.com/ken\_guru/status/123744573666246656 https://twitter.com/cornae/status/123803246300114945 Thats it. I now get to rest for a while. Next stop is SWDC in November, which will host the première of my new talk CSS in the 4th dimension: Not your daddys CSS animations which will be about CSS transitions &amp; animations, from the basics all way to badass secrets. Thanks to all the conference organizers for inviting me and for the attendees for attending and giving feedback on my talks. You are all awesome, and it was the best 2 weeks ever. :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Optimizing long lists of yes/no values with JavaScript</title>
  <link>https://lea.verou.me/2011/10/optimizing-long-lists-of-yesno-values-with-javascript/</link>
  <pubDate>Sun, 17 May 2026 01:25:17 +0200</pubDate>
  <description>My newest article on Smashing Magazines coding section is for the geekiest among you. Its about how you can pack long lists of boolean values into a string in the most space-efficient way. Hope you enjoy it :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Easily keep gh-pages in sync with master</title>
  <link>https://lea.verou.me/2011/10/easily-keep-gh-pages-in-sync-with-master/</link>
  <pubDate>Sun, 17 May 2026 01:25:16 +0200</pubDate>
  <description>I always loved Github’s ability to publish pages for a project and get the strain out of your server. However, every time I tried it, I struggled to keep the gh-pages branch up to date. Until I discovered the awesome git rebase . Usually my github workflow is like this: git add . git status // to see what changes are going to be commited git commit -m ‘Some descriptive commit message’ git push origin master Now, when I use gh-pages, there are only a few more commands that I have to use after the above: git checkout gh-pages // go to the gh-pages branch git rebase master // bring gh-pages up to date with master git push origin gh-pages // commit the changes git checkout master // return to the master branch I know this is old news to some of you (I’m a github n00b, struggling with basic stuff, so my advice is probably for other n00bs), but if I had read this a few months ago, it would’ve saved me big hassles, so I’m writing it for the others out there that are like me a few months ago. Now if only I find an easy way to automate this… :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Easily keep gh-pages in sync with master</title>
  <link>https://lea.verou.me/2011/10/easily-keep-gh-pages-in-sync-with-master/</link>
  <pubDate>Sun, 17 May 2026 01:25:16 +0200</pubDate>
  <description>I always loved Githubs ability to publish pages for a project and get the strain out of your server. However, every time I tried it, I struggled to keep the gh-pages branch up to date. Until I discovered the awesome git rebase . Usually my github workflow is like this: git add . git status // to see what changes are going to be commited git commit -m Some descriptive commit message git push origin master Now, when I use gh-pages, there are only a few more commands that I have to use after the above: git checkout gh-pages // go to the gh-pages branch git rebase master // bring gh-pages up to date with master git push origin gh-pages // commit the changes git checkout master // return to the master branch I know this is old news to some of you (Im a github n00b, struggling with basic stuff, so my advice is probably for other n00bs), but if I had read this a few months ago, it wouldve saved me big hassles, so Im writing it for the others out there that are like me a few months ago. Now if only I find an easy way to automate this :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>PrefixFree: Break free from CSS prefix hell!</title>
  <link>https://lea.verou.me/2011/10/prefixfree-break-free-from-css-prefix-hell/</link>
  <pubDate>Sun, 17 May 2026 01:25:15 +0200</pubDate>
  <description>I wrote this script while at the airport travelling to Oslo and during the Frontend 2011 conference . I think it’s amazing, and it makes authoring CSS3 a pleasure. Read my announcement about it on Smashing Magazine. Hope you like it!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from Frontendconf Zurich</title>
  <link>https://lea.verou.me/2011/09/my-experience-from-frontendconf-zurich/</link>
  <pubDate>Sun, 17 May 2026 01:25:15 +0200</pubDate>
  <description>I’m writing this blog post while eating some of the amazing Lindt chocolates I got for free 10 days ago at Frontend conference in Zurich . But it wasn’t a good experience only because of them! First of all, it gave me the opportunity to visit Zurich for free, and meet an old friend for the first time. A girl we used to be penpals with at primary school &amp; junior high when she was still living in Athens and I in Lesvos. She is now living in Zurich and doing her PhD in ETH. I arrived in Zurich a day earlier and stayed in her place that first night. We caught up and I had a great time. Secondly, the rest of the speakers are great people and fun too, it was a pleasure to meet them. Especially Smashing Magazine ’s Vitaly Friedman. He’s a very kind guy, nothing like what you’d expect from somebody so successful. I also got the chance to meet Robert again, who was lots of fun as always. Those Swedes have a great sense of humor! The conference itself was very nice, although small (only 200 people). Many inspiring talks, although I couldn’t attend them all because they were split into multiple tracks in one day. I would very much prefer it if it had 1 track and was 2 days. The 2nd day was an unconference, where attendees could speak, about whatever they wanted. I decided to get some sleep the second day, so I arrived a bit later, and didn’t attend many talks. It was kinda sad that it finished so early, around 4pm almost everyone was gone and most speakers were flying back the same day. My talk went great, although I had the most technical glitches I’ve ever faced in a talk. That was my fault, not the conference’s. I guess I should learn to stop tweaking my slides at the last moment, cause things might break (and this time they did). Despite those glitches however, the audience loved it. Here’s a small sample of the twitter feedback I got: https://twitter.com/frontendconfch/status/112089631616532480 https://twitter.com/FabianBeiner/status/112089949096001536 https://twitter.com/michalbe/status/112090281519751168 https://twitter.com/jfahrenkrug/status/112091377571074048 https://twitter.com/kcornelius/status/112091409833668608 https://twitter.com/backflip/status/112091599823056896 https://twitter.com/cainvommars/status/112091892581285888 https://twitter.com/euklid/status/112093174897459200 https://twitter.com/shvi/status/112095896040243200 https://twitter.com/lorentzforce/status/112096221350461441 https://twitter.com/mettlerd/status/112101440541032448 https://twitter.com/FabianBeiner/status/112101569058713600 https://twitter.com/jfahrenkrug/status/112101867424718848 https://twitter.com/jfahrenkrug/status/112103061736009729 https://twitter.com/Schnitzel/status/112103388187070464 https://twitter.com/marcoegli/status/112103726835175424 https://twitter.com/FabianBeiner/status/112103972227133440 https://twitter.com/mauricenaef/status/112104694171705344 https://twitter.com/lulezi/status/112105754789560320 https://twitter.com/smash\_it\_on/status/112107410155515904 https://twitter.com/walktheweb/status/112107790918615040 https://twitter.com/andypanix/status/112113881966579712 https://twitter.com/lorentzforce/status/112121470355914752 https://twitter.com/loleg/status/112126016213876738 https://twitter.com/whitefleaCH/status/112132969468137472 https://twitter.com/Juztin/status/112195913929326592 https://twitter.com/codepo8/status/112252082718916608 https://twitter.com/derSchepp/status/112275821703602176 https://twitter.com/susanjrobertson/status/112286939964641280 If you read the above carefully, you might have noticed that my talk was recorded, so you can see it too. :) Enjoy!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>PrefixFree: Break free from CSS prefix hell!</title>
  <link>https://lea.verou.me/2011/10/prefixfree-break-free-from-css-prefix-hell/</link>
  <pubDate>Sun, 17 May 2026 01:25:15 +0200</pubDate>
  <description>I wrote this script while at the airport travelling to Oslo and during the Frontend 2011 conference . I think its amazing, and it makes authoring CSS3 a pleasure. Read my announcement about it on Smashing Magazine. Hope you like it!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from Frontendconf Zurich</title>
  <link>https://lea.verou.me/2011/09/my-experience-from-frontendconf-zurich/</link>
  <pubDate>Sun, 17 May 2026 01:25:15 +0200</pubDate>
  <description>Im writing this blog post while eating some of the amazing Lindt chocolates I got for free 10 days ago at Frontend conference in Zurich . But it wasnt a good experience only because of them! First of all, it gave me the opportunity to visit Zurich for free, and meet an old friend for the first time. A girl we used to be penpals with at primary school &amp; junior high when she was still living in Athens and I in Lesvos. She is now living in Zurich and doing her PhD in ETH. I arrived in Zurich a day earlier and stayed in her place that first night. We caught up and I had a great time. Secondly, the rest of the speakers are great people and fun too, it was a pleasure to meet them. Especially Smashing Magazine s Vitaly Friedman. Hes a very kind guy, nothing like what youd expect from somebody so successful. I also got the chance to meet Robert again, who was lots of fun as always. Those Swedes have a great sense of humor! The conference itself was very nice, although small (only 200 people). Many inspiring talks, although I couldnt attend them all because they were split into multiple tracks in one day. I would very much prefer it if it had 1 track and was 2 days. The 2nd day was an unconference, where attendees could speak, about whatever they wanted. I decided to get some sleep the second day, so I arrived a bit later, and didnt attend many talks. It was kinda sad that it finished so early, around 4pm almost everyone was gone and most speakers were flying back the same day. My talk went great, although I had the most technical glitches Ive ever faced in a talk. That was my fault, not the conferences. I guess I should learn to stop tweaking my slides at the last moment, cause things might break (and this time they did). Despite those glitches however, the audience loved it. Heres a small sample of the twitter feedback I got: https://twitter.com/frontendconfch/status/112089631616532480 https://twitter.com/FabianBeiner/status/112089949096001536 https://twitter.com/michalbe/status/112090281519751168 https://twitter.com/jfahrenkrug/status/112091377571074048 https://twitter.com/kcornelius/status/112091409833668608 https://twitter.com/backflip/status/112091599823056896 https://twitter.com/cainvommars/status/112091892581285888 https://twitter.com/euklid/status/112093174897459200 https://twitter.com/shvi/status/112095896040243200 https://twitter.com/lorentzforce/status/112096221350461441 https://twitter.com/mettlerd/status/112101440541032448 https://twitter.com/FabianBeiner/status/112101569058713600 https://twitter.com/jfahrenkrug/status/112101867424718848 https://twitter.com/jfahrenkrug/status/112103061736009729 https://twitter.com/Schnitzel/status/112103388187070464 https://twitter.com/marcoegli/status/112103726835175424 https://twitter.com/FabianBeiner/status/112103972227133440 https://twitter.com/mauricenaef/status/112104694171705344 https://twitter.com/lulezi/status/112105754789560320 https://twitter.com/smash\_it\_on/status/112107410155515904 https://twitter.com/walktheweb/status/112107790918615040 https://twitter.com/andypanix/status/112113881966579712 https://twitter.com/lorentzforce/status/112121470355914752 https://twitter.com/loleg/status/112126016213876738 https://twitter.com/whitefleaCH/status/112132969468137472 https://twitter.com/Juztin/status/112195913929326592 https://twitter.com/codepo8/status/112252082718916608 https://twitter.com/derSchepp/status/112275821703602176 https://twitter.com/susanjrobertson/status/112286939964641280 If you read the above carefully, you might have noticed that my talk was recorded, so you can see it too. :) Enjoy!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Major update to Chainvas: modularity, a client side build script &amp; more</title>
  <link>https://lea.verou.me/2011/09/major-update-to-chainvas-modularity-a-client-side-build-script-more/</link>
  <pubDate>Sun, 17 May 2026 01:25:14 +0200</pubDate>
  <description>A week ago, I released Chainvas . It was a spin-off script I wrote while developing my cubic-bezier tool , to make using the Canvas API a bit less painful. However, unlike similar attempts to make the Canvas API chainable, most of my code was written in a very generic manner, and was actually able to make every API chainable. However, when I released it, even though I mentioned that it can be used for other APIs and provided some examples, practically everyone that shared the link on twitter or other means (thank you .net magazine for the newsletter mention btw!) focused on what Chainvas did for Canvas. Actually, while using Chainvas myself, I found it immensely more useful for chaining DOM methods and setting multiple element properties at once. Chainvas had a lot of potential, that most people were missing. And then it dawned on me: I should modularize the library! A generic chaining library at its core and additional modules for making the different APIs chainable. And I did it. On the way to that, I added IE8 compatibility, and tested in many other browsers, thanks to Browserstack . I actually found that Chainvas’ core even works in IE6! I also wrote unit tests , a much more extensive documentation , added a script generated table of contents and designed a logo and a Chainvas pride banner . Also, since it was now modular, it needed a build script. I badly wanted to make this client side, so I followed this architecture: Every module is included in chainvas.js and chainvas.min.js, along with a header comment that follows a specific syntax . The user selects a compression level and then, the relevant script is downloaded through XHR and split into parts according to the module headers. Then a module list is generated with checkboxes for the user to select the ones they want to include. When the user checks and unchecks those checkboxes, the URL of the download link changes to a data URI that contains the script. This approach has the disadvantage that there is no default filename, and the “Save page as…” link is deactivated in Chrome (why Chrome??). However, I like the idea so much, I don’t mind these shortcomings. That’s about it. Enjoy and let me know about any bugs.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Major update to Chainvas: modularity, a client side build script &amp; more</title>
  <link>https://lea.verou.me/2011/09/major-update-to-chainvas-modularity-a-client-side-build-script-more/</link>
  <pubDate>Sun, 17 May 2026 01:25:14 +0200</pubDate>
  <description>A week ago, I released Chainvas . It was a spin-off script I wrote while developing my cubic-bezier tool , to make using the Canvas API a bit less painful. However, unlike similar attempts to make the Canvas API chainable, most of my code was written in a very generic manner, and was actually able to make every API chainable. However, when I released it, even though I mentioned that it can be used for other APIs and provided some examples, practically everyone that shared the link on twitter or other means (thank you .net magazine for the newsletter mention btw!) focused on what Chainvas did for Canvas. Actually, while using Chainvas myself, I found it immensely more useful for chaining DOM methods and setting multiple element properties at once. Chainvas had a lot of potential, that most people were missing. And then it dawned on me: I should modularize the library! A generic chaining library at its core and additional modules for making the different APIs chainable. And I did it. On the way to that, I added IE8 compatibility, and tested in many other browsers, thanks to Browserstack . I actually found that Chainvas core even works in IE6! I also wrote unit tests , a much more extensive documentation , added a script generated table of contents and designed a logo and a Chainvas pride banner . Also, since it was now modular, it needed a build script. I badly wanted to make this client side, so I followed this architecture: Every module is included in chainvas.js and chainvas.min.js, along with a header comment that follows a specific syntax . The user selects a compression level and then, the relevant script is downloaded through XHR and split into parts according to the module headers. Then a module list is generated with checkboxes for the user to select the ones they want to include. When the user checks and unchecks those checkboxes, the URL of the download link changes to a data URI that contains the script. This approach has the disadvantage that there is no default filename, and the Save page as link is deactivated in Chrome (why Chrome??). However, I like the idea so much, I dont mind these shortcomings. Thats about it. Enjoy and let me know about any bugs.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>A better tool for cubic-bezier() easing</title>
  <link>https://lea.verou.me/2011/09/a-better-tool-for-cubic-bezier-easing/</link>
  <pubDate>Sun, 17 May 2026 01:25:13 +0200</pubDate>
  <description>A few days ago, I had a talk at a conference in Zurich (I’m going to write more about it in another post). The talk was about “10 things you might not know about CSS3”. The first of those things was how you can do bouncing transitions with cubic-bezier() instead of an easing keyword. As usual, my slides included a few live demos of the functionality, in which I edited the cubic-bezier() parameters and the audience could see the transition produced. However, in the case of cubic-bezier() that’s not enough. No matter how much you see someone changing the parameters, if you don’t picture it in a 2D plane, it’s very hard to understand how it works. So, the night before, I searched for a tool I could use to show them how bezier curves are formed. I found plenty, but all of them restricted the the coordinates to the 0-1 range. I’m not sure if the cause is ignorance about the spec changes or that Webkit hasn’t caught up with those changes yet ( but it will, soon ). The only one that supported values out of range was this one from the Opera Dragonfly developers, but I found it kinda impossible to adapt. For my talk, I tried to adapt one of them but it was late so I gave up after a while and ended up just showing them a screenshot. And the day after the talk, I started adapting this to my needs (ever tried coding at a conference? It’s awesome, you get to ask questions from very knowledgeable people and ger replies straight away). And then I started cleaning up the code, changing how it worked, adding features. At this point, I think the only thing that’s left from that tool is …the HTML5 doctype. After 3-4 days, I finished it, and got it its own domain, cubic-bezier.com (I was surprised it was still free). So, in a nutshell, what makes this better? Lots of things: It supports y values out of range, as per the latest version of the spec (and shows a warning for Webkit) It’s fully accessible from the keyboard You can move the handles not only by dragging but also by clicking on the plane or using the keyboard arrow keys You can mouse over the plane and see the progression and time percentages that correspond to every point You can save curves you like in your “Library” (uses localStorage to persist them) You can import and export curves to/from your library to share them with others You can share a permalink to every curve. For example, here’s a bouncing transition (FF &amp; Opera only) You can compare the current curve with any in your library, setting the duration yourself Custom favicon that reflects the current curve Cool stuff used Given that this tool is not only for developers, but for badass developers that care about stuff like cubic-bezier(), I think I can safely assume they’re using a top notch browser. So, I went crazy with using cool modern stuff: HTML5: Canvas, localStorage, History API, range inputs, oninput event, output, classList, data- attributes ES5: Accessors, Array#map, Array#forEach Selectors API JSON CSS3: Transitions, gradients, media queries, border-radius, shadows, :in-range pseudoclass, box-sizing, transforms, text-overflow I also used my tiny chaining framework, Chainvas throughout this project. Browser support So far, I’ve tested it in modern versions of Chrome, Firefox, Opera and Safari and it seems to work. I haven’t tested it in IE10 (too lazy to open vm), although I want it to work there too, so if it doesn’t let me know. :) Enjoy! cubic-bezier.com</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Chainvas: Make APIs chainable, enhance the canvas API</title>
  <link>https://lea.verou.me/2011/09/chainvas-make-apis-chainable-enhance-the-canvas-api/</link>
  <pubDate>Sun, 17 May 2026 01:25:13 +0200</pubDate>
  <description>It’s definitely not the first time someone writes a script to make the canvas API chainable, as a quick Google search will confirm. However, I think my attempt has merit, because it’s not really focused in chaining canvas methods, but just about every API you use it on and because it’s super small, only 1KB! You can find it here: chainvas Enjoy!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>A better tool for cubic-bezier() easing</title>
  <link>https://lea.verou.me/2011/09/a-better-tool-for-cubic-bezier-easing/</link>
  <pubDate>Sun, 17 May 2026 01:25:13 +0200</pubDate>
  <description>A few days ago, I had a talk at a conference in Zurich (Im going to write more about it in another post). The talk was about 10 things you might not know about CSS3. The first of those things was how you can do bouncing transitions with cubic-bezier() instead of an easing keyword. As usual, my slides included a few live demos of the functionality, in which I edited the cubic-bezier() parameters and the audience could see the transition produced. However, in the case of cubic-bezier() thats not enough. No matter how much you see someone changing the parameters, if you dont picture it in a 2D plane, its very hard to understand how it works. So, the night before, I searched for a tool I could use to show them how bezier curves are formed. I found plenty, but all of them restricted the the coordinates to the 0-1 range. Im not sure if the cause is ignorance about the spec changes or that Webkit hasnt caught up with those changes yet ( but it will, soon ). The only one that supported values out of range was this one from the Opera Dragonfly developers, but I found it kinda impossible to adapt. For my talk, I tried to adapt one of them but it was late so I gave up after a while and ended up just showing them a screenshot. And the day after the talk, I started adapting this to my needs (ever tried coding at a conference? Its awesome, you get to ask questions from very knowledgeable people and ger replies straight away). And then I started cleaning up the code, changing how it worked, adding features. At this point, I think the only thing thats left from that tool is the HTML5 doctype. After 3-4 days, I finished it, and got it its own domain, cubic-bezier.com (I was surprised it was still free). So, in a nutshell, what makes this better? Lots of things: It supports y values out of range, as per the latest version of the spec (and shows a warning for Webkit) Its fully accessible from the keyboard You can move the handles not only by dragging but also by clicking on the plane or using the keyboard arrow keys You can mouse over the plane and see the progression and time percentages that correspond to every point You can save curves you like in your Library (uses localStorage to persist them) You can import and export curves to/from your library to share them with others You can share a permalink to every curve. For example, heres a bouncing transition (FF &amp; Opera only) You can compare the current curve with any in your library, setting the duration yourself Custom favicon that reflects the current curve Cool stuff used Given that this tool is not only for developers, but for badass developers that care about stuff like cubic-bezier(), I think I can safely assume theyre using a top notch browser. So, I went crazy with using cool modern stuff: HTML5: Canvas, localStorage, History API, range inputs, oninput event, output, classList, data- attributes ES5: Accessors, Array#map, Array#forEach Selectors API JSON CSS3: Transitions, gradients, media queries, border-radius, shadows, :in-range pseudoclass, box-sizing, transforms, text-overflow I also used my tiny chaining framework, Chainvas throughout this project. Browser support So far, Ive tested it in modern versions of Chrome, Firefox, Opera and Safari and it seems to work. I havent tested it in IE10 (too lazy to open vm), although I want it to work there too, so if it doesnt let me know. :) Enjoy! cubic-bezier.com</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Chainvas: Make APIs chainable, enhance the canvas API</title>
  <link>https://lea.verou.me/2011/09/chainvas-make-apis-chainable-enhance-the-canvas-api/</link>
  <pubDate>Sun, 17 May 2026 01:25:13 +0200</pubDate>
  <description>Its definitely not the first time someone writes a script to make the canvas API chainable, as a quick Google search will confirm. However, I think my attempt has merit, because its not really focused in chaining canvas methods, but just about every API you use it on and because its super small, only 1KB! You can find it here: chainvas Enjoy!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Help the community: report browser bugs</title>
  <link>https://lea.verou.me/2011/09/help-the-community-report-browser-bugs/</link>
  <pubDate>Sun, 17 May 2026 01:25:12 +0200</pubDate>
  <description>Thought I’d let you know that my Smashing Magazine article with that title was published today. It discusses why, how, when and where to report browser bugs, as well as how to make a good bug report. Get comfortable and make a big cup of coffee before you dive in, as it’s quite long (4000 words).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Help the community: report browser bugs</title>
  <link>https://lea.verou.me/2011/09/help-the-community-report-browser-bugs/</link>
  <pubDate>Sun, 17 May 2026 01:25:12 +0200</pubDate>
  <description>Thought Id let you know that my Smashing Magazine article with that title was published today. It discusses why, how, when and where to report browser bugs, as well as how to make a good bug report. Get comfortable and make a big cup of coffee before you dive in, as its quite long (4000 words).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Pure CSS3 typing animation with steps()</title>
  <link>https://lea.verou.me/2011/09/pure-css3-typing-animation-with-steps/</link>
  <pubDate>Sun, 17 May 2026 01:25:10 +0200</pubDate>
  <description>steps() is a relatively new addition to the CSS3 animations module. Instead of interpolating the values smoothly, it allows us to define the number of “frames” precisely. So I used it to create headers that have the well-known animated “typing effect”: As you can see, the number of characters is hardcoded in the steps() function, but that’s the only place. Everything else is totally flexible. Apart from the font: It has to be monospace, so that every character has the same width. Also, this particular way requires a solid background and an extra . You can avoid these limitations by directly animating the width of the heading itself, but this requires a fixed target width hardcoded in the animation, so 2 things that need to be changed for every heading: If you’re having trouble understanding how it works, take a look at this simpler example , with just the cursor. Gecko (Firefox) and Webkit only at the moment, since other engines haven’t implemented CSS animations yet. However, both examples degrade very gracefully in other browsers (IMO at least).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS.coloratum: Convert and share CSS colors</title>
  <link>https://lea.verou.me/2011/09/css-coloratum-convert-and-share-css-colors/</link>
  <pubDate>Sun, 17 May 2026 01:25:10 +0200</pubDate>
  <description>Whenever I wanted to convert a CSS named color to RGB, I used to open the CSS3 colors spec in a new tab, search in the page and copied the values. Every time it felt even more tedious. I didn’t want to search in long tables, I wanted to type the color somewhere and get the values back, in an easy to copy format. So, after yet another color lookup earlier today, I decided to scratch my own itch and do it myself. Of course, I didn’t plan to include a whole database of CSS colors in the website. My idea was much simpler: Use the named color to draw a rectangle in a and then read the R,G,B values through ctx.getImageData(). I got the core functionality done in under 10 minutes, so I started adding stuff. I added a hex and HSL representation, I used canvas.toDataURL() to get a data URI of the rectangle and use it as a dynamic favicon*, I made the colors sharable and bookmarkable by using an old-fashioned hash. Also, I realized it actually supports any CSS supported color represenation by design, not just named colors. Regarding the color conversions themselves, I took extra care to avoid redundancy. So values Since it’s for developers, I didn’t bother at all with fallbacks. Cool stuff used: HTML5: canvas, autofocus, output, oninput event, hashchange event CSS3: gradients, media queries, box-sizing, background-clip, border-radius, shadows, RGBA ES5: Array#map() Selectors API The reason the input’s border appears weird on Webkit is this long standing Webkit bug . Also, for some reason my nice dynamic favicons don’t display on Firefox, although they display fine in Webkit and Opera. Enjoy: CSS.coloratum Happy color sharing! Let me know of any problems or suggestions you may have. PS: In case you’re wondering about the domain, I’ve had it for ages for another project and I thought it was quite fitting. *Thanks to @milo for giving me the idea of using a dynamic favicon</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Pure CSS3 typing animation with steps()</title>
  <link>https://lea.verou.me/2011/09/pure-css3-typing-animation-with-steps/</link>
  <pubDate>Sun, 17 May 2026 01:25:10 +0200</pubDate>
  <description>steps() is a relatively new addition to the CSS3 animations module. Instead of interpolating the values smoothly, it allows us to define the number of frames precisely. So I used it to create headers that have the well-known animated typing effect: As you can see, the number of characters is hardcoded in the steps() function, but thats the only place. Everything else is totally flexible. Apart from the font: It has to be monospace, so that every character has the same width. Also, this particular way requires a solid background and an extra . You can avoid these limitations by directly animating the width of the heading itself, but this requires a fixed target width hardcoded in the animation, so 2 things that need to be changed for every heading: If youre having trouble understanding how it works, take a look at this simpler example , with just the cursor. Gecko (Firefox) and Webkit only at the moment, since other engines havent implemented CSS animations yet. However, both examples degrade very gracefully in other browsers (IMO at least).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS.coloratum: Convert and share CSS colors</title>
  <link>https://lea.verou.me/2011/09/css-coloratum-convert-and-share-css-colors/</link>
  <pubDate>Sun, 17 May 2026 01:25:10 +0200</pubDate>
  <description>Whenever I wanted to convert a CSS named color to RGB, I used to open the CSS3 colors spec in a new tab, search in the page and copied the values. Every time it felt even more tedious. I didnt want to search in long tables, I wanted to type the color somewhere and get the values back, in an easy to copy format. So, after yet another color lookup earlier today, I decided to scratch my own itch and do it myself. Of course, I didnt plan to include a whole database of CSS colors in the website. My idea was much simpler: Use the named color to draw a rectangle in a and then read the R,G,B values through ctx.getImageData(). I got the core functionality done in under 10 minutes, so I started adding stuff. I added a hex and HSL representation, I used canvas.toDataURL() to get a data URI of the rectangle and use it as a dynamic favicon*, I made the colors sharable and bookmarkable by using an old-fashioned hash. Also, I realized it actually supports any CSS supported color represenation by design, not just named colors. Regarding the color conversions themselves, I took extra care to avoid redundancy. So values Since its for developers, I didnt bother at all with fallbacks. Cool stuff used: HTML5: canvas, autofocus, output, oninput event, hashchange event CSS3: gradients, media queries, box-sizing, background-clip, border-radius, shadows, RGBA ES5: Array#map() Selectors API The reason the inputs border appears weird on Webkit is this long standing Webkit bug . Also, for some reason my nice dynamic favicons dont display on Firefox, although they display fine in Webkit and Opera. Enjoy: CSS.coloratum Happy color sharing! Let me know of any problems or suggestions you may have. PS: In case youre wondering about the domain, Ive had it for ages for another project and I thought it was quite fitting. *Thanks to @milo for giving me the idea of using a dynamic favicon</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On URL readability</title>
  <link>https://lea.verou.me/2011/08/on-url-readability/</link>
  <pubDate>Sun, 17 May 2026 01:25:09 +0200</pubDate>
  <description>Yesterday, I was watching some season 6 episodes of Futurama (btw, this is their best season ever!) and I noticed the URLs in the website I was in (let’s call it foo.com ). They were like: http://foo.com/futurama/season/6/episode/9 I thought to myself “hey, this looks very clean and readable”. And then I noticed that it only has 1 less character than its non-rewritten counterpart: http://foo.com/?futurama&amp;season=6&amp;episode=9 However, I’m pretty sure you agree that the second one is much harder to read. I asked for opinions on twitter , and got many interesting replies. Apart from the ones that completely missed the point, these were the core explanations: = and especially &amp; are more complex and look more like letters, so our brain has trouble tuning them out ( @feather @robert_tilt @rexxars @mrtazz @manchurian ) Slashes have more whitespace around them, so they are less obtrusive ( @feather @stevelove @kenny1987 @janl ) They’re all visual noise, but we always have slashes in a URL, so using the slash to separate keys and values as well only introduces 1 separator instead of 3 ( @bugster @craigpatik @nyaray ) Slashes imply hierarchy, which our brains process easier than key-value pairs. Key-value pairs could be in any order, paths have a specified order. ( @sggottlieb @edwelker @stevenhay @jwasjsberg @stazybohorn ) Ampersands and equal signs are harder to type than slashes. They’re both in the top row and ampersands even need the Shift key as well. ( @feather ) Ampersands and equal signs have semantic meaning in our minds, whereas slashes not as much ( @snadon ) Regarding hierarchy and RESTful design, the first example isn’t exactly correct. If it was hierarchical, it should be foo.com/futurama/season s /6/episode s /9. As it currently stands, it’s key-value pairs, masquerading as hierarchical. However, it still reads better. So I’m leaning towards the first three explanations, although I think all of them have a grain of truth. Which makes me wonder: Did we choose the wrong characters for our protocol? Could we have saved ourselves the hassle and performance overhead of URL rewriting if we were a bit more careful in choosing the separators back then? Also, some food for thought: Where do you think the following URLs stand in the legibility scale? http://foo.com/futurama/season=6/episode=9 http://foo.com/futurama/season:6/episode:9 http : //foo.com/futurama-season-6-episode-9 (suggested by Ben Alman) Do you think there are there any explanations that I missed?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On URL readability</title>
  <link>https://lea.verou.me/2011/08/on-url-readability/</link>
  <pubDate>Sun, 17 May 2026 01:25:09 +0200</pubDate>
  <description>Yesterday, I was watching some season 6 episodes of Futurama (btw, this is their best season ever!) and I noticed the URLs in the website I was in (lets call it foo.com ). They were like: http://foo.com/futurama/season/6/episode/9 I thought to myself hey, this looks very clean and readable. And then I noticed that it only has 1 less character than its non-rewritten counterpart: http://foo.com/?futurama&amp;season=6&amp;episode=9 However, Im pretty sure you agree that the second one is much harder to read. I asked for opinions on twitter , and got many interesting replies. Apart from the ones that completely missed the point, these were the core explanations: = and especially &amp; are more complex and look more like letters, so our brain has trouble tuning them out ( @feather @robert_tilt @rexxars @mrtazz @manchurian ) Slashes have more whitespace around them, so they are less obtrusive ( @feather @stevelove @kenny1987 @janl ) Theyre all visual noise, but we always have slashes in a URL, so using the slash to separate keys and values as well only introduces 1 separator instead of 3 ( @bugster @craigpatik @nyaray ) Slashes imply hierarchy, which our brains process easier than key-value pairs. Key-value pairs could be in any order, paths have a specified order. ( @sggottlieb @edwelker @stevenhay @jwasjsberg @stazybohorn ) Ampersands and equal signs are harder to type than slashes. Theyre both in the top row and ampersands even need the Shift key as well. ( @feather ) Ampersands and equal signs have semantic meaning in our minds, whereas slashes not as much ( @snadon ) Regarding hierarchy and RESTful design, the first example isnt exactly correct. If it was hierarchical, it should be foo.com/futurama/season s /6/episode s /9. As it currently stands, its key-value pairs, masquerading as hierarchical. However, it still reads better. So Im leaning towards the first three explanations, although I think all of them have a grain of truth. Which makes me wonder: Did we choose the wrong characters for our protocol? Could we have saved ourselves the hassle and performance overhead of URL rewriting if we were a bit more careful in choosing the separators back then? Also, some food for thought: Where do you think the following URLs stand in the legibility scale? http://foo.com/futurama/season=6/episode=9 http://foo.com/futurama/season:6/episode:9 http : //foo.com/futurama-season-6-episode-9 (suggested by Ben Alman) Do you think there are there any explanations that I missed?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>To write good code, you sometimes have to write bad code</title>
  <link>https://lea.verou.me/2011/08/to-write-good-code-you-sometimes-have-to-write-bad-code/</link>
  <pubDate>Sun, 17 May 2026 01:25:08 +0200</pubDate>
  <description>And I’m not referring to learning. For example, yesterday I was trying to write code for something and it ended up beng harder than I expected. It’s one of those rare cases where you can fully imagine how the solution should work, enough to tell it to another person, but you can’t put your thoughts to code and you feel you’re not smart enough. I find that in those cases, it helps a lot to open a new editor window and try to write code that just works. Without being elegant, fast or maintainable. Just something that works properly. And after you manage to put your thoughts into (bad) code, it’s easy to refine it from there and end up with good code. Just don’t stop at the bad code, like many beginners do. It’s like when designers sketch a rough draft for a logo, before drawing the digital version. Could you imagine how horrible it would be if they wanted to stop there and give the rough sketches to the client instead? :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>To write good code, you sometimes have to write bad code</title>
  <link>https://lea.verou.me/2011/08/to-write-good-code-you-sometimes-have-to-write-bad-code/</link>
  <pubDate>Sun, 17 May 2026 01:25:08 +0200</pubDate>
  <description>And Im not referring to learning. For example, yesterday I was trying to write code for something and it ended up beng harder than I expected. Its one of those rare cases where you can fully imagine how the solution should work, enough to tell it to another person, but you cant put your thoughts to code and you feel youre not smart enough. I find that in those cases, it helps a lot to open a new editor window and try to write code that just works. Without being elegant, fast or maintainable. Just something that works properly. And after you manage to put your thoughts into (bad) code, its easy to refine it from there and end up with good code. Just dont stop at the bad code, like many beginners do. Its like when designers sketch a rough draft for a logo, before drawing the digital version. Could you imagine how horrible it would be if they wanted to stop there and give the rough sketches to the client instead? :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Accessible star rating widget with pure CSS</title>
  <link>https://lea.verou.me/2011/08/accessible-star-rating-widget-with-pure-css/</link>
  <pubDate>Sun, 17 May 2026 01:25:07 +0200</pubDate>
  <description>For ages, we couldn’t utilize the sibling combinators ( ~ and + ) to ease the pain of creating star rating widgets, because of this stupid Webkit bug . Nowadays, not only it’s fixed, but the fix has already propagated to Chrome and Safari 5.1. So, we can at least use the sibling combinator to make coloring the stars easier. But can we use no JavaScript for a rating widget and make it just with CSS? Actually, we can. By adapting Ryan Seddon’s technique for custom radio buttons with CSS , we can turn a series of radio buttons into stars that change colors (for the purposes of this demo they’re just unicode characters that change colors, but in your case they may as well be images) and use the sibling combinator to color the previous stars. A series of radio buttons is what many people use as a star rating widget fallback anyway, so the markup required is not necessarily more than usual. The only thing that needs to be done differently is their reverse ordering: The highest ratings need to go first, due to the way CSS3 selectors work (this limitation might be removed in CSS4, but that’s a long way ahead). Of course, you’d still need JS to attach an event handler if you want the votes to be registered through AJAX, but that’s not part of the rating widget per se (it could still work as part of a regular form). What’s best is that it’s fully keyboard accessible (focus and then use keyboard arrows) and screen reader accessible (although VoiceOver will also pronounce the generated stars, but that won’t happen if you use images instead of unicode stars). I’m guessing it could become even more accessible with proper ARIA, but I’ll leave that as an exercise to the commenter :D In browsers that don’t support :checked (essentially only IE So, here it is: Legal note, for those who need it: This code is MIT licensed.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Why I love our industry</title>
  <link>https://lea.verou.me/2011/08/why-i-love-our-industry/</link>
  <pubDate>Sun, 17 May 2026 01:25:07 +0200</pubDate>
  <description>I was thinking today how blessed I feel for being a part of the worldwide web development community (and the broader programming community). In a world where throwing shit at others is an acceptable way of climbing to the top, our industry is a breeze of fresh air. Here are a few reasons why I find our industry unique, in a very good way: In which other industry is it common for people to spend several hours, days or in some cases even months, working on something to give it away for free, just to help people? In which other industry do people help you and promote you just because they think you’re good, without getting anything out of it? In which other industry do people listen to you, not because of your titles, degrees and “decades of experience”, but because of what you actually know? In which other industry can you go to a big professional conference with jeans and a t-shirt and be in the majority? (And the best part is, even if you don’t like that kind of outfit and you prefer to wear a suit, you still fit in, cause appearances just don’t matter) Judging whether someone’s work is good is a very rational and objective process (unlike arts). Sure, the various criteria have different weights for every person, but the criteria are the same for everyone, more or less (correctness, speed, maintainability, readability etc). Even though it’s a male dominated field, I’ve never* experienced any discrimination or lack of respect due to my gender. Quite the contrary actually. I’ve yet to meet a developer that lacks a sense of humor. Work for us is our passion, not a chore. Yes, there are passionate people in every field, but in our industry it’s the norm, not the exception. You don’t need to hide your geekiness. Instead, you’re encouraged to embrace it. Yes, I know that not all of them are true for every single person that happened to be a web developer. I’m talking about the part of the industry that’s active and that I meet in conferences, meetups, twitter etc. So, what are your reasons for liking our industry, if any? Lets keep this post happy and not whine about what we DON’T like please. :) *Well, except one bad joke once, but he recently said he’s sorry and his intentions were good throughout, so I don’t count it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Accessible star rating widget with pure CSS</title>
  <link>https://lea.verou.me/2011/08/accessible-star-rating-widget-with-pure-css/</link>
  <pubDate>Sun, 17 May 2026 01:25:07 +0200</pubDate>
  <description>For ages, we couldnt utilize the sibling combinators ( ~ and + ) to ease the pain of creating star rating widgets, because of this stupid Webkit bug . Nowadays, not only its fixed, but the fix has already propagated to Chrome and Safari 5.1. So, we can at least use the sibling combinator to make coloring the stars easier. But can we use no JavaScript for a rating widget and make it just with CSS? Actually, we can. By adapting Ryan Seddons technique for custom radio buttons with CSS , we can turn a series of radio buttons into stars that change colors (for the purposes of this demo theyre just unicode characters that change colors, but in your case they may as well be images) and use the sibling combinator to color the previous stars. A series of radio buttons is what many people use as a star rating widget fallback anyway, so the markup required is not necessarily more than usual. The only thing that needs to be done differently is their reverse ordering: The highest ratings need to go first, due to the way CSS3 selectors work (this limitation might be removed in CSS4, but thats a long way ahead). Of course, youd still need JS to attach an event handler if you want the votes to be registered through AJAX, but thats not part of the rating widget per se (it could still work as part of a regular form). Whats best is that its fully keyboard accessible (focus and then use keyboard arrows) and screen reader accessible (although VoiceOver will also pronounce the generated stars, but that wont happen if you use images instead of unicode stars). Im guessing it could become even more accessible with proper ARIA, but Ill leave that as an exercise to the commenter :D In browsers that dont support :checked (essentially only IE So, here it is: Legal note, for those who need it: This code is MIT licensed.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Why I love our industry</title>
  <link>https://lea.verou.me/2011/08/why-i-love-our-industry/</link>
  <pubDate>Sun, 17 May 2026 01:25:07 +0200</pubDate>
  <description>I was thinking today how blessed I feel for being a part of the worldwide web development community (and the broader programming community). In a world where throwing shit at others is an acceptable way of climbing to the top, our industry is a breeze of fresh air. Here are a few reasons why I find our industry unique, in a very good way: In which other industry is it common for people to spend several hours, days or in some cases even months, working on something to give it away for free, just to help people? In which other industry do people help you and promote you just because they think youre good, without getting anything out of it? In which other industry do people listen to you, not because of your titles, degrees and decades of experience, but because of what you actually know? In which other industry can you go to a big professional conference with jeans and a t-shirt and be in the majority? (And the best part is, even if you dont like that kind of outfit and you prefer to wear a suit, you still fit in, cause appearances just dont matter) Judging whether someones work is good is a very rational and objective process (unlike arts). Sure, the various criteria have different weights for every person, but the criteria are the same for everyone, more or less (correctness, speed, maintainability, readability etc). Even though its a male dominated field, Ive never* experienced any discrimination or lack of respect due to my gender. Quite the contrary actually. Ive yet to meet a developer that lacks a sense of humor. Work for us is our passion, not a chore. Yes, there are passionate people in every field, but in our industry its the norm, not the exception. You dont need to hide your geekiness. Instead, youre encouraged to embrace it. Yes, I know that not all of them are true for every single person that happened to be a web developer. Im talking about the part of the industry thats active and that I meet in conferences, meetups, twitter etc. So, what are your reasons for liking our industry, if any? Lets keep this post happy and not whine about what we DONT like please. :) *Well, except one bad joke once, but he recently said hes sorry and his intentions were good throughout, so I dont count it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Better “CSS3 ticket-like tags”</title>
  <link>https://lea.verou.me/2011/08/better-css3-ticket-like-tags/</link>
  <pubDate>Sun, 17 May 2026 01:25:06 +0200</pubDate>
  <description>Today I stumbled upon this tutorial, which from the screenshot, looked very interesting. So, I read on, and to my horror I noticed the author suggesting some questionable practices, the worst of which was using 3 HTML elements for every tag, including nonsense markup like . So, I thought I’d take a chance at trying to recreate this effect without any extra markup. And it turned out to be quite easy, although using CSS gradients limits browser support a bit (IE10, Firefox 3.6+, Chrome, Safari 5.1). They have the same disadvantage as the originals: They depend on the background color. However, unlike the originals, they come at less code, they’re scalable without changing a million values (as shown in the “bigger” section) and of course, no extra markup. You can see the results in the fiddle below: Disclaimer: webdesign tuts+ occasionally has some nice tutorials. I didn’t write this post to attack them in any way.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Better CSS3 ticket-like tags</title>
  <link>https://lea.verou.me/2011/08/better-css3-ticket-like-tags/</link>
  <pubDate>Sun, 17 May 2026 01:25:06 +0200</pubDate>
  <description>Today I stumbled upon this tutorial, which from the screenshot, looked very interesting. So, I read on, and to my horror I noticed the author suggesting some questionable practices, the worst of which was using 3 HTML elements for every tag, including nonsense markup like . So, I thought Id take a chance at trying to recreate this effect without any extra markup. And it turned out to be quite easy, although using CSS gradients limits browser support a bit (IE10, Firefox 3.6+, Chrome, Safari 5.1). They have the same disadvantage as the originals: They depend on the background color. However, unlike the originals, they come at less code, theyre scalable without changing a million values (as shown in the bigger section) and of course, no extra markup. You can see the results in the fiddle below: Disclaimer: webdesign tuts+ occasionally has some nice tutorials. I didnt write this post to attack them in any way.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>twee+: Longer tweets, no strings attached</title>
  <link>https://lea.verou.me/2011/08/tweeplus-longer-tweets-no-strings-attached/</link>
  <pubDate>Sun, 17 May 2026 01:25:05 +0200</pubDate>
  <description>As some people that have been following me for a while know, the 140 character limit on twitter bugs me a lot sometimes, and I’ve tried to find a way to overcome it previously as well. The most common ways these days seems to be either cutting down the long tweet into multiple pieces (yuck) or using a service to host the longer tweet and post a summary with a link to it. The latter isn’t an entirely horrible option. However, I see 3 big downsides: I’m not very comfortable with the idea of some external service hosting my content which could close down any time due to failure to monetize their website. In that case, I’d be left with some dead links that are of no value and my content would be lost forever. Yes, they usually warn you in advance in such cases, but such news could be missed for a number of reasons. People (including yours truly) don’t plan those things in advance. They just seek services like that at the exact moment when they want to post a long tweet. Being greeted with a prompt to use Twitter Connect is NOT nice. For starters, it slows me down. Also, I don’t want to give permission to every website on the effing interwebs to post on my twitter timeline. I owe it to my followers to be responsible and not risk filling their timelines with crap. Most of these websites look like someone puked and what came out happened to be HTML and CSS. The only exception I’ve found is twtmore , but it still suffers from #1 and #2. So, like every developer with a healthy amount of NIH syndrome, I decided to write my own :D My goals were: It had to be entirely client-side (except initially getting downloaded from the server of course). This way, whoever is concerned can download the full website and decode their tweets with it if it ever goes down. Also, being entirely client side allows it to scale very easily, as serving files is not a very resource intensive job (compared to databases and the like). No Twitter Connect, the tweets would get posted through Twitter Web Intents . It had to look good. I’m not primarily a designer, so I can’t make something that looks jaw-droppingly amazing, but I can at least make it look very decent. If I was gonna go through all the hassle of making this, I may as well try to keep it under 10K, so that I could take part in the 10K apart contest . (I haven’t submitted it yet, I’ll submit a few days before the deadline, as it seems you can’t make changes to your submission and I want to polish the code a bit, especially the JS — I’m not too proud about it) I managed to succeed in all my goals and I really liked the result. I ended up using it for stuff I never imagined, like checking if a twitter username corresponds to the account I think (as it shows the avatars). So I went ahead and came up with a name, bought a domain for it, and tweeplus.com was born :) twee+? Seriously? Yes, I like it. The plus means “more”, which is fitting and + kinda looks like a t, so it could be read as “tweet” as well. Yes, I know that the word “twee” has some negative connotations, but oh well, I still like the name. Whoever doesn’t can just not use it, I won’t get depressed, I promise. :P Geeky stuff How it works A relatively new feature, Twitter automatically wraps URLs in t.co links, making them only 20 characters long. All the text of the tweet is stored in the URL hash (query string will also work, although the output uses a hash). Some research revealed that actually browsers can handle surprisingly long URLs . Essentially, the only limit (2083 characters) is enforced by Internet Explorer. I decided to limit tweets to 2000 characters (encoded length), not only because of the IE limit, but also because I wouldn’t like people to post whole books in t.co links. We don’t want Twitter to start taking measures against this, do we? :) A hard part was deciding which encoding to use (twitter is quite limited in the characters it parses as part of a URL). My first thought was base64, but I quickly realized this was not a very good idea: The encoding &amp; decoding functions ( btoa() and atob() respectively) are relatively new and therefore not supported by older browsers. I’m fine with the app hardly working in old browsers, but existing links must as a minimum be readable. It uses approximately 1.34 characters per ASCII character. Unicode characters need to be URL-encoded first, otherwise an Exception is thrown. URL-encoding them uses 6 characters, which would result in 8 characters when they’re base64 encoded. Then I thought of using URL-encoding for the whole thing. The good thing with it is that for latin alphanumeric characters (which are the most) it doesn’t increase the string length at all. For non-alphanumeric characters it takes up 3 characters and 6 characters for Unicode ones. Also, it’s much more readable. Still, implementing it was tricky. It doesn’t encode some characters (like the dot), even though twitter doesn’t accept them as part of a URL. Also, escape() and encodeURI() behave differently and the Unicode encoding returned by the former isn’t accepted by Twitter . So I had to combine the two and do some substitutions manually. When the textarea changes, the hash does too. The whole thing is a form with action=“ http://twitter.com/intent/tweet ”, so submitting it does the right thing naturally. I keep a hidden input with the tweet and the textarea has no name, so it doesn’t get submitted. Usernames, hashtags and URLs get extracted and linkified. Usernames also get an avatar (it’s dead easy: Just use twitter.com/api/users/profile _image?screen_name={username} where {username} is the user’s username) Internal “pages” (like “About” or “Browser support”) are essentially long “tweets” too. A little easter egg is that if the browser supports radial gradients, the gradient follows the mouse, creating a spotlight effect. This looks nice on Chrome and Firefox, and really shitty on IE10, probably due to bugs in the gradient implementation (which I have to reduce &amp; report sometime). Buzzword compliance This little app demonstrates quite a lot new open web technologies (HTML5, CSS3 etc), such as: textarea maxlength placeholder autofocus (edit: I had to remove it cause it triggered a Webkit bug in Safari) required inputs New input types (url) History API oninput event (with keyup fallback ) hashchange event SVG Common CSS3 (border-radius, shadows, transitions, rgba, media queries) CSS3 gradients CSS3 animations CSS3 resize :empty Let me know if I forgot something. Oh yeah, I did forget something. There it is: twee+</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>twee+: Longer tweets, no strings attached</title>
  <link>https://lea.verou.me/2011/08/tweeplus-longer-tweets-no-strings-attached/</link>
  <pubDate>Sun, 17 May 2026 01:25:05 +0200</pubDate>
  <description>As some people that have been following me for a while know, the 140 character limit on twitter bugs me a lot sometimes, and Ive tried to find a way to overcome it previously as well. The most common ways these days seems to be either cutting down the long tweet into multiple pieces (yuck) or using a service to host the longer tweet and post a summary with a link to it. The latter isnt an entirely horrible option. However, I see 3 big downsides: Im not very comfortable with the idea of some external service hosting my content which could close down any time due to failure to monetize their website. In that case, Id be left with some dead links that are of no value and my content would be lost forever. Yes, they usually warn you in advance in such cases, but such news could be missed for a number of reasons. People (including yours truly) dont plan those things in advance. They just seek services like that at the exact moment when they want to post a long tweet. Being greeted with a prompt to use Twitter Connect is NOT nice. For starters, it slows me down. Also, I dont want to give permission to every website on the effing interwebs to post on my twitter timeline. I owe it to my followers to be responsible and not risk filling their timelines with crap. Most of these websites look like someone puked and what came out happened to be HTML and CSS. The only exception Ive found is twtmore , but it still suffers from #1 and #2. So, like every developer with a healthy amount of NIH syndrome, I decided to write my own :D My goals were: It had to be entirely client-side (except initially getting downloaded from the server of course). This way, whoever is concerned can download the full website and decode their tweets with it if it ever goes down. Also, being entirely client side allows it to scale very easily, as serving files is not a very resource intensive job (compared to databases and the like). No Twitter Connect, the tweets would get posted through Twitter Web Intents . It had to look good. Im not primarily a designer, so I cant make something that looks jaw-droppingly amazing, but I can at least make it look very decent. If I was gonna go through all the hassle of making this, I may as well try to keep it under 10K, so that I could take part in the 10K apart contest . (I havent submitted it yet, Ill submit a few days before the deadline, as it seems you cant make changes to your submission and I want to polish the code a bit, especially the JS Im not too proud about it) I managed to succeed in all my goals and I really liked the result. I ended up using it for stuff I never imagined, like checking if a twitter username corresponds to the account I think (as it shows the avatars). So I went ahead and came up with a name, bought a domain for it, and tweeplus.com was born :) twee+? Seriously? Yes, I like it. The plus means more, which is fitting and + kinda looks like a t, so it could be read as tweet as well. Yes, I know that the word twee has some negative connotations, but oh well, I still like the name. Whoever doesnt can just not use it, I wont get depressed, I promise. :P Geeky stuff How it works A relatively new feature, Twitter automatically wraps URLs in t.co links, making them only 20 characters long. All the text of the tweet is stored in the URL hash (query string will also work, although the output uses a hash). Some research revealed that actually browsers can handle surprisingly long URLs . Essentially, the only limit (2083 characters) is enforced by Internet Explorer. I decided to limit tweets to 2000 characters (encoded length), not only because of the IE limit, but also because I wouldnt like people to post whole books in t.co links. We dont want Twitter to start taking measures against this, do we? :) A hard part was deciding which encoding to use (twitter is quite limited in the characters it parses as part of a URL). My first thought was base64, but I quickly realized this was not a very good idea: The encoding &amp; decoding functions ( btoa() and atob() respectively) are relatively new and therefore not supported by older browsers. Im fine with the app hardly working in old browsers, but existing links must as a minimum be readable. It uses approximately 1.34 characters per ASCII character. Unicode characters need to be URL-encoded first, otherwise an Exception is thrown. URL-encoding them uses 6 characters, which would result in 8 characters when theyre base64 encoded. Then I thought of using URL-encoding for the whole thing. The good thing with it is that for latin alphanumeric characters (which are the most) it doesnt increase the string length at all. For non-alphanumeric characters it takes up 3 characters and 6 characters for Unicode ones. Also, its much more readable. Still, implementing it was tricky. It doesnt encode some characters (like the dot), even though twitter doesnt accept them as part of a URL. Also, escape() and encodeURI() behave differently and the Unicode encoding returned by the former isnt accepted by Twitter . So I had to combine the two and do some substitutions manually. When the textarea changes, the hash does too. The whole thing is a form with action= http://twitter.com/intent/tweet , so submitting it does the right thing naturally. I keep a hidden input with the tweet and the textarea has no name, so it doesnt get submitted. Usernames, hashtags and URLs get extracted and linkified. Usernames also get an avatar (its dead easy: Just use twitter.com/api/users/profile _image?screen_name={username} where {username} is the users username) Internal pages (like About or Browser support) are essentially long tweets too. A little easter egg is that if the browser supports radial gradients, the gradient follows the mouse, creating a spotlight effect. This looks nice on Chrome and Firefox, and really shitty on IE10, probably due to bugs in the gradient implementation (which I have to reduce &amp; report sometime). Buzzword compliance This little app demonstrates quite a lot new open web technologies (HTML5, CSS3 etc), such as: textarea maxlength placeholder autofocus (edit: I had to remove it cause it triggered a Webkit bug in Safari) required inputs New input types (url) History API oninput event (with keyup fallback ) hashchange event SVG Common CSS3 (border-radius, shadows, transitions, rgba, media queries) CSS3 gradients CSS3 animations CSS3 resize :empty Let me know if I forgot something. Oh yeah, I did forget something. There it is: twee+</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS gradients are faster than SVG backgrounds</title>
  <link>https://lea.verou.me/2011/08/css-gradients-are-much-faster-than-svg/</link>
  <pubDate>Sun, 17 May 2026 01:25:04 +0200</pubDate>
  <description>Which is really sad, because SVG is awesome. It lets you do what CSS gradients do and much more, in quite a small filesize, as it’s just text too. However, the browser needs to generate a DOM for every SVG graphic, which results in sluggishness. Here’s my test case Mouse over the 2 divs. They both use a spotlight effect that’s dynamically updated according to the position of the mouse cursor. One of them does it with an SVG (through a data URI), the other one through a CSS radial gradient. The test only works in Chrome , Firefox nightlies and perhaps IE10 (haven’t tested in Windows). Why? Because Opera doesn’t support radial gradients yet (however you can see how slow SVG is in it too), and Firefox before the nightlies used to have a bug with gradients in SVG through data URIs . Also, jsFiddle seems not to work in Webkit nightlies for some reason, but I’m too lazy right now to make a self-hosted test case. Thanks a lot to Christian Krebs (lead developer of Opera Dragonfly) who inspired these tests after a discussion we had today regarding CSS gradient performance. Edit: According to some commenters, they’re the same speed on Windows and Linux, so it could be an OSX issue. The only way to know for sure is to post more results, so go ahead and post yours! Also, some commenters say that this is not a fair comparison, because it generates a new SVG every time. I have several arguments to reply to this: We also generate a new gradient every time, so it is fair. You can’t manipulate an SVG used for a background, so it’s not an option for backgrounds. JS doesn’t run in it and you don’t have access to its DOM. The only way to do that would be to use an inline SVG embedded in HTML and the element() CSS3 function. However, that’s only supported by Firefox, so not really a pragmatic option.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS gradients are faster than SVG backgrounds</title>
  <link>https://lea.verou.me/2011/08/css-gradients-are-much-faster-than-svg/</link>
  <pubDate>Sun, 17 May 2026 01:25:04 +0200</pubDate>
  <description>Which is really sad, because SVG is awesome. It lets you do what CSS gradients do and much more, in quite a small filesize, as its just text too. However, the browser needs to generate a DOM for every SVG graphic, which results in sluggishness. Heres my test case Mouse over the 2 divs. They both use a spotlight effect thats dynamically updated according to the position of the mouse cursor. One of them does it with an SVG (through a data URI), the other one through a CSS radial gradient. The test only works in Chrome , Firefox nightlies and perhaps IE10 (havent tested in Windows). Why? Because Opera doesnt support radial gradients yet (however you can see how slow SVG is in it too), and Firefox before the nightlies used to have a bug with gradients in SVG through data URIs . Also, jsFiddle seems not to work in Webkit nightlies for some reason, but Im too lazy right now to make a self-hosted test case. Thanks a lot to Christian Krebs (lead developer of Opera Dragonfly) who inspired these tests after a discussion we had today regarding CSS gradient performance. Edit: According to some commenters, theyre the same speed on Windows and Linux, so it could be an OSX issue. The only way to know for sure is to post more results, so go ahead and post yours! Also, some commenters say that this is not a fair comparison, because it generates a new SVG every time. I have several arguments to reply to this: We also generate a new gradient every time, so it is fair. You cant manipulate an SVG used for a background, so its not an option for backgrounds. JS doesnt run in it and you dont have access to its DOM. The only way to do that would be to use an inline SVG embedded in HTML and the element() CSS3 function. However, thats only supported by Firefox, so not really a pragmatic option.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 for developers: My Fronteers 2011 workshop</title>
  <link>https://lea.verou.me/2011/08/css3-for-developers-my-fronteers-2011-workshop/</link>
  <pubDate>Sun, 17 May 2026 01:25:03 +0200</pubDate>
  <description>In case you haven’t noticed, in addition to my talk at Fronteers 2011 , I’ll also be holding a full day workshop the day before the conference. The title of that workshop is “ CSS3 for developers ” and I wanted to explain a bit what it’s going to be about and why I chose to target web developers only. Why “for developers”? First of all, even though I do design websites and I really love design (not only web design), I consider myself primarily a developer. So, I think I can communicate better with other devs, rather than designers, since we “speak the same language”. Secondly, most CSS3 talks and workshops are presented by and targeted to, designers. Developers end up feeling left out and in return they tend to consider CSS an inferior technology which isn’t for them. CSS might not be a programming language, but it is code, and to fully master, it requires a very similar skillset to programming. It’s no wonder that most people that actually do research on CSS and/or write the specifications are not designers. Besides, CSS3, in essence, is about creating web applications that download faster and are easier to develop, maintain and edit. There are very few things that can’t be done at all with CSS2.1. CSS3 just allows us to do them better: Less HTTP requests, less kilobytes to download, less presentational JavaScript, more flexibility. CSS3 is mostly about coding speed, flexibility, performance, maintainability. None of these are artistic pursuits, they’re all purely developer goals! What will it be about? It will be about many well-implemented and popular CSS3 features, like border-radius, shadows, gradients, new background properties, selectors, media queries, transforms, transitions etc. The key difference from most CSS3 talks &amp; workshops will be the depth these will be covered in and the different perspective (practical information rather than artistic or “inspirational”). I usually opt in for depth rather than breadth for my talks, and expect the same from this workshop. The feedback I get most frequently for my talks is “I thought I knew everything about topic X, but yet I learned so much!”. I’ll do my best to maintain this reputation for this workshop as well. ;) In addition to learning how CSS3 stuff can be used, information about browser support, fallbacks and performance will be provided. Watch a single person talk for a whole day? Boooooring! Hey, I agree. And it’s not just that: Listening to someone talk about a given topic and trying it out yourself are two very different things. I believe that you only really learn something when you actually use it. That’s why it won’t be done like that. ;) It will be very hands on and there is going to be at least one small exercise per almost everything explained. The exercises are going to be performed in a little web app designed exclusively for this workshop, so that you won’t need to bother with prefixes or write HTML and irrelevant CSS code just to try out a new property. And what’s best, you can take that app at home with you and practice what you learned as much as you want! Of course that means that every attendee will have to bring their own laptop (or borrow one from a friend). Kewl, can I haz ticket? Workshop attendance is priced at €350 (€275 for Fronteers members) and you can get your ticket here: http://fronteers.paydro.net/ To make the experience better and more educational, we limited the number of attendees to 30. That might mean you need to rush: there are currently tickets available, but in a few days there might not be any left! Looking forward to seeing you in Amsterdam! :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 for developers: My Fronteers 2011 workshop</title>
  <link>https://lea.verou.me/2011/08/css3-for-developers-my-fronteers-2011-workshop/</link>
  <pubDate>Sun, 17 May 2026 01:25:03 +0200</pubDate>
  <description>In case you havent noticed, in addition to my talk at Fronteers 2011 , Ill also be holding a full day workshop the day before the conference. The title of that workshop is CSS3 for developers and I wanted to explain a bit what its going to be about and why I chose to target web developers only. Why for developers? First of all, even though I do design websites and I really love design (not only web design), I consider myself primarily a developer. So, I think I can communicate better with other devs, rather than designers, since we speak the same language. Secondly, most CSS3 talks and workshops are presented by and targeted to, designers. Developers end up feeling left out and in return they tend to consider CSS an inferior technology which isnt for them. CSS might not be a programming language, but it is code, and to fully master, it requires a very similar skillset to programming. Its no wonder that most people that actually do research on CSS and/or write the specifications are not designers. Besides, CSS3, in essence, is about creating web applications that download faster and are easier to develop, maintain and edit. There are very few things that cant be done at all with CSS2.1. CSS3 just allows us to do them better: Less HTTP requests, less kilobytes to download, less presentational JavaScript, more flexibility. CSS3 is mostly about coding speed, flexibility, performance, maintainability. None of these are artistic pursuits, theyre all purely developer goals! What will it be about? It will be about many well-implemented and popular CSS3 features, like border-radius, shadows, gradients, new background properties, selectors, media queries, transforms, transitions etc. The key difference from most CSS3 talks &amp; workshops will be the depth these will be covered in and the different perspective (practical information rather than artistic or inspirational). I usually opt in for depth rather than breadth for my talks, and expect the same from this workshop. The feedback I get most frequently for my talks is I thought I knew everything about topic X, but yet I learned so much!. Ill do my best to maintain this reputation for this workshop as well. ;) In addition to learning how CSS3 stuff can be used, information about browser support, fallbacks and performance will be provided. Watch a single person talk for a whole day? Boooooring! Hey, I agree. And its not just that: Listening to someone talk about a given topic and trying it out yourself are two very different things. I believe that you only really learn something when you actually use it. Thats why it wont be done like that. ;) It will be very hands on and there is going to be at least one small exercise per almost everything explained. The exercises are going to be performed in a little web app designed exclusively for this workshop, so that you wont need to bother with prefixes or write HTML and irrelevant CSS code just to try out a new property. And whats best, you can take that app at home with you and practice what you learned as much as you want! Of course that means that every attendee will have to bring their own laptop (or borrow one from a friend). Kewl, can I haz ticket? Workshop attendance is priced at 350 (275 for Fronteers members) and you can get your ticket here: http://fronteers.paydro.net/ To make the experience better and more educational, we limited the number of attendees to 30. That might mean you need to rush: there are currently tickets available, but in a few days there might not be any left! Looking forward to seeing you in Amsterdam! :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from the CSS Summit 2011</title>
  <link>https://lea.verou.me/2011/08/my-experience-from-the-css-summit-2011/</link>
  <pubDate>Sun, 17 May 2026 01:05:22 +0200</pubDate>
  <description>It’s been a few days since this year’s CSS Summit and my talk there. Where most people would assume that public speaking in a “real” conference is more daunting, I was much more nervous about this one, since it was my first talk at an online conference. I wouldn’t be able to see the faces of the audience, so how would I be able to tell if they like it or are bored shitless? Also, the whole idea of me, alone in a room, giving a talk to my laptop sounded kind of awkward, to say the very least. Contrary to my fears, it was a very pleasant experience. In some ways, it’s much better than real-life conferences, the main one being the number of questions you get. In most real-life conferences you should be lucky to get more than 3 or 4 questions. Also, they’re usually at the end, so most attendees forget the questions they had at the beginning and middle of the talk (it happens to me a lot too, when I attend others’ talks). In the CSS Summit, I answered questions after every section of my talk, and there were quite a lot of them. The attendees had a group chat in which they talked about the presentation, posted questions and discussed many other stuff. That group chat was the other thing I really liked. It might surprise some people, but even though I’m not afraid of public speaking, I’m quite shy in some ways and I almost never talk to someone first. So, if I didn’t know anyone at a conference and vice versa, I’d probably sit in a corner alone with nobody to talk to during the breaks. The chat makes it much easier for attendees to get to know each other. On the minus side however, “meeting” somebody in a chat is not by any means the same as really meeting someone f2f in a real-life conference. Regarding my talk, it went surprisingly well. No technical hiccups like some of the other talks, no me going overtime as I was afraid I would (since I had to be slower), no internet connection failing on my part (like it sometimes does lately). I received lots of enthusiastic feedback on both the chat and twitter. I couldn’t even favorite them all, as the tweets were so many! That’s the 3rd good thing about online conferences: People tweet more, since they’re at home with their regular connection and not with a crappy conference wifi or a smartphone on expensive roaming. Here’s a small sample of the feedback I got: https://twitter.com/teleject/status/95872896257372161 https://twitter.com/boblet/status/95874502608691201 https://twitter.com/chriscoyier/status/95875236511236096 https://twitter.com/snookca/status/95875859579277312 https://twitter.com/elizabethyalkut/status/95876076647100416 https://twitter.com/wloescher/status/95877653730557952 https://twitter.com/martuishere/status/95878117872246784 https://twitter.com/PetraGregorova/status/95878976446271488 https://twitter.com/caffeinerush/status/95879094406873088 https://twitter.com/yodasw16/status/95879645743951872 https://twitter.com/matthewcarleton/status/95880868744278017 https://twitter.com/redcrew/status/95881025602863104 https://twitter.com/brandongcarroll/status/95881316796600320 https://twitter.com/digitalCULT/status/95884698315792384 https://twitter.com/candiRSX/status/95885394192769025 https://twitter.com/jewlofthelotus/status/95885472802422784 https://twitter.com/mortendk/status/95885749404176384 https://twitter.com/idoclosecuts/status/95886200342188032 https://twitter.com/megcarpen/status/95886211897495552 https://twitter.com/V\_v\_V/status/95886299902386176 https://twitter.com/iScreem/status/95886472934195200 https://twitter.com/pixelfuture/status/95886741176717312 https://twitter.com/suprMax/status/95887286369132544 https://twitter.com/PetraGregorova/status/95899659159080960 Deborah Edwards-Onoro also put together a write-up for some parts of my presentation. Thank you all!!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from the CSS Summit 2011</title>
  <link>https://lea.verou.me/2011/08/my-experience-from-the-css-summit-2011/</link>
  <pubDate>Sun, 17 May 2026 01:05:22 +0200</pubDate>
  <description>Its been a few days since this years CSS Summit and my talk there. Where most people would assume that public speaking in a real conference is more daunting, I was much more nervous about this one, since it was my first talk at an online conference. I wouldnt be able to see the faces of the audience, so how would I be able to tell if they like it or are bored shitless? Also, the whole idea of me, alone in a room, giving a talk to my laptop sounded kind of awkward, to say the very least. Contrary to my fears, it was a very pleasant experience. In some ways, its much better than real-life conferences, the main one being the number of questions you get. In most real-life conferences you should be lucky to get more than 3 or 4 questions. Also, theyre usually at the end, so most attendees forget the questions they had at the beginning and middle of the talk (it happens to me a lot too, when I attend others talks). In the CSS Summit, I answered questions after every section of my talk, and there were quite a lot of them. The attendees had a group chat in which they talked about the presentation, posted questions and discussed many other stuff. That group chat was the other thing I really liked. It might surprise some people, but even though Im not afraid of public speaking, Im quite shy in some ways and I almost never talk to someone first. So, if I didnt know anyone at a conference and vice versa, Id probably sit in a corner alone with nobody to talk to during the breaks. The chat makes it much easier for attendees to get to know each other. On the minus side however, meeting somebody in a chat is not by any means the same as really meeting someone f2f in a real-life conference. Regarding my talk, it went surprisingly well. No technical hiccups like some of the other talks, no me going overtime as I was afraid I would (since I had to be slower), no internet connection failing on my part (like it sometimes does lately). I received lots of enthusiastic feedback on both the chat and twitter. I couldnt even favorite them all, as the tweets were so many! Thats the 3rd good thing about online conferences: People tweet more, since theyre at home with their regular connection and not with a crappy conference wifi or a smartphone on expensive roaming. Heres a small sample of the feedback I got: https://twitter.com/teleject/status/95872896257372161 https://twitter.com/boblet/status/95874502608691201 https://twitter.com/chriscoyier/status/95875236511236096 https://twitter.com/snookca/status/95875859579277312 https://twitter.com/elizabethyalkut/status/95876076647100416 https://twitter.com/wloescher/status/95877653730557952 https://twitter.com/martuishere/status/95878117872246784 https://twitter.com/PetraGregorova/status/95878976446271488 https://twitter.com/caffeinerush/status/95879094406873088 https://twitter.com/yodasw16/status/95879645743951872 https://twitter.com/matthewcarleton/status/95880868744278017 https://twitter.com/redcrew/status/95881025602863104 https://twitter.com/brandongcarroll/status/95881316796600320 https://twitter.com/digitalCULT/status/95884698315792384 https://twitter.com/candiRSX/status/95885394192769025 https://twitter.com/jewlofthelotus/status/95885472802422784 https://twitter.com/mortendk/status/95885749404176384 https://twitter.com/idoclosecuts/status/95886200342188032 https://twitter.com/megcarpen/status/95886211897495552 https://twitter.com/V\_v\_V/status/95886299902386176 https://twitter.com/iScreem/status/95886472934195200 https://twitter.com/pixelfuture/status/95886741176717312 https://twitter.com/suprMax/status/95887286369132544 https://twitter.com/PetraGregorova/status/95899659159080960 Deborah Edwards-Onoro also put together a write-up for some parts of my presentation. Thank you all!!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Vote for me in The .net awards 2011!</title>
  <link>https://lea.verou.me/2011/07/vote-for-me-in-the-net-awards-2011/</link>
  <pubDate>Sun, 17 May 2026 01:05:21 +0200</pubDate>
  <description>I don’t usually post shameless plugs like that, but I’m so excited about this I decided to make an exception. A few minutes ago I found out that I’m shortlisted in the “Brilliant newcomer” category of The .net awards !!! Thank you so much @ everyone that nominated me and/or plans to vote for me. I really appreciate it guys*! :) * “guys” in that context is used in a gender neutral fashion, I’m not only thanking the men :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Vote for me in The .net awards 2011!</title>
  <link>https://lea.verou.me/2011/07/vote-for-me-in-the-net-awards-2011/</link>
  <pubDate>Sun, 17 May 2026 01:05:21 +0200</pubDate>
  <description>I dont usually post shameless plugs like that, but Im so excited about this I decided to make an exception. A few minutes ago I found out that Im shortlisted in the Brilliant newcomer category of The .net awards !!! Thank you so much @ everyone that nominated me and/or plans to vote for me. I really appreciate it guys*! :) * guys in that context is used in a gender neutral fashion, Im not only thanking the men :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Detecting CSS selectors support + my JSConf EU talk</title>
  <link>https://lea.verou.me/2011/07/detecting-css-selectors-support-my-jsconf-eu-talk/</link>
  <pubDate>Sun, 17 May 2026 01:05:20 +0200</pubDate>
  <description>I’ll start with a little backstory, if you want to jump straight to the meat, skip the next 4 paragraphs. In the past few months, my CSS research has been getting some attention and I’ve been starting to become somewhat well-known in the CSS industry. A little known fact about me is that JavaScript has always been one of my loves, almost as much as CSS (even more than it in the past). Ironically, the first time I was asked to speak in a big conference , it was about JavaScript, even though I ended up choosing to speak about CSS3 instead. Lately, I’ve started wanting to get more into the JavaScript industry as well. I’m quite reluctant to submit speaking proposals myself (every conference or meetup I’ve given a talk so far has asked me to speak, not the other way around) and most JavaScript conferences expect you to submit a proposal yourself. I also couldn’t think of a good topic, something I was passionate about and hasn’t already been extensively covered. This changed a few weeks ago. While I was writing my polyfill , it dawned on me: Polyfills is something that’s JS-related and I’m passionate about! I love studying them, writing them, talking about them. I quickly searched if there were any talks about polyfill writing already and I couldn’t find any. So, I decided to submit a proposal to JSConf EU , even though the call for speakers had passed 10 days ago. When I read @cramforce’s tweet that they had decided on most of the speakers, I spent a few days stressed as hell, checking my inbox every few minutes and hoping that my gut feeling that I would get accepted was right. And it was! 3 days ago I received an email from JSConf EU that my proposal was accepted!! I can’t even begin to describe how happy and excited I am about it. And nervous too: What if they know everything I’m going to say? What if they hate my talk? What if the JavaScript industry is really as sexist as some people claim and they dismiss me because of my gender? I decided to put my fears aside and start working on my slides, as I couldn’t wait until later (even though I have multiple deadlines creeping up on me right now…). A big part of writing polyfills is feature detection. Before trying to implement a feature with JavaScript, you first have to check if it’s already supported. So, a substantial portion of my talk will be about that. How to detect if APIs, HTML elements, CSS properties/values/selectors etc are supported. There are already established solutions and techniques about most of these, except CSS selectors. Modernizr doesn’t detect any, and judging from my Google search nobody has written about any techniques for doing so in a generic fashion. A really simple way to detect CSS selectors support is using document.querySelector() in a try...catch statement. If the selector is not supported, an error will be thrown. However, that’s not really reliable, as the Selectors API is not supported in IE The basic idea is creating a new element with an empty rule and the selector we want to test support for, and then read out the stylesheet through the DOM methods to see if a rule actually exists. I’ve so far tested it in Firefox, Opera and Chrome and it seems to work. I haven’t tested it in IE yet, as I currently have too many apps running to turn on the vm, so it might need a few fixes to work there (or I might be unlucky and the idea might not work at all). You can test it out yourself in this fiddle , just check the console: http://fiddle.jshell.net/leaverou/Pmn8m/show/light/ Apologies if this has already been documented elsewhere, I really couldn’t find anything. Edit: James Long worked on fixing my example’s issues with IE</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Detecting CSS selectors support + my JSConf EU talk</title>
  <link>https://lea.verou.me/2011/07/detecting-css-selectors-support-my-jsconf-eu-talk/</link>
  <pubDate>Sun, 17 May 2026 01:05:20 +0200</pubDate>
  <description>Ill start with a little backstory, if you want to jump straight to the meat, skip the next 4 paragraphs. In the past few months, my CSS research has been getting some attention and Ive been starting to become somewhat well-known in the CSS industry. A little known fact about me is that JavaScript has always been one of my loves, almost as much as CSS (even more than it in the past). Ironically, the first time I was asked to speak in a big conference , it was about JavaScript, even though I ended up choosing to speak about CSS3 instead. Lately, Ive started wanting to get more into the JavaScript industry as well. Im quite reluctant to submit speaking proposals myself (every conference or meetup Ive given a talk so far has asked me to speak, not the other way around) and most JavaScript conferences expect you to submit a proposal yourself. I also couldnt think of a good topic, something I was passionate about and hasnt already been extensively covered. This changed a few weeks ago. While I was writing my polyfill , it dawned on me: Polyfills is something thats JS-related and Im passionate about! I love studying them, writing them, talking about them. I quickly searched if there were any talks about polyfill writing already and I couldnt find any. So, I decided to submit a proposal to JSConf EU , even though the call for speakers had passed 10 days ago. When I read @cramforces tweet that they had decided on most of the speakers, I spent a few days stressed as hell, checking my inbox every few minutes and hoping that my gut feeling that I would get accepted was right. And it was! 3 days ago I received an email from JSConf EU that my proposal was accepted!! I cant even begin to describe how happy and excited I am about it. And nervous too: What if they know everything Im going to say? What if they hate my talk? What if the JavaScript industry is really as sexist as some people claim and they dismiss me because of my gender? I decided to put my fears aside and start working on my slides, as I couldnt wait until later (even though I have multiple deadlines creeping up on me right now). A big part of writing polyfills is feature detection. Before trying to implement a feature with JavaScript, you first have to check if its already supported. So, a substantial portion of my talk will be about that. How to detect if APIs, HTML elements, CSS properties/values/selectors etc are supported. There are already established solutions and techniques about most of these, except CSS selectors. Modernizr doesnt detect any, and judging from my Google search nobody has written about any techniques for doing so in a generic fashion. A really simple way to detect CSS selectors support is using document.querySelector() in a try...catch statement. If the selector is not supported, an error will be thrown. However, thats not really reliable, as the Selectors API is not supported in IE The basic idea is creating a new element with an empty rule and the selector we want to test support for, and then read out the stylesheet through the DOM methods to see if a rule actually exists. Ive so far tested it in Firefox, Opera and Chrome and it seems to work. I havent tested it in IE yet, as I currently have too many apps running to turn on the vm, so it might need a few fixes to work there (or I might be unlucky and the idea might not work at all). You can test it out yourself in this fiddle , just check the console: http://fiddle.jshell.net/leaverou/Pmn8m/show/light/ Apologies if this has already been documented elsewhere, I really couldnt find anything. Edit: James Long worked on fixing my examples issues with IE</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>A polyfill for HTML5 progress element, the obsessive perfectionist way</title>
  <link>https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/</link>
  <pubDate>Sun, 17 May 2026 01:05:19 +0200</pubDate>
  <description>Yesterday, for some reason I don’t remember, I was looking once more at Paul Irish’s excellent list of polyfills on Github . I was really surprised to see that there are none for the element . It seemed really simple: Easy to fake with CSS and only 4 IDL attributes (value, max, position and labels). “Hey, it sounds fun and easy, I’ll do it!”, I thought. I have no idea how in only 1 day this turned into “OMG, my brain is going to explode”. I’ve documented below all the pitfalls I faced. And don’t worry, it has a happy ending: I did finish it. And published it . So, if you’re not interested in long geeky stories, just jump straight to its page . First things first: Controlling the width of the value bar Most progress bars out there use 2 elements: One for the container and one for the value bar. I was pretty stubborn about not using an extra element. I wanted to use pseudo-elements instead and keep the DOM tree as clean as I found it. And there it was, the first problem: How to set the width? CSS3 attr() and calc() are hardly supported and attr() is not even allowed in calc() , so I quickly realized that a pure CSS solution was out of the question. However, if I used JavaScript, how would I set a different width for every progress::before? You can’t set that in an inline style, and assigning every element an ID and adding separate rules seems a bit too intrusive to me. Think about it for a second, what would you do? I realized I had to control the width of the pseudo-element through CSS properties of the parent container somehow. And then it dawned on me: If the pseudoelement has display:block , it will automatically get the parent width, minus the padding and borders. There it was, this was my solution. I just had to set padding-right accordingly, so that the value bar gets the width it needs to be! And I had already given it box-sizing: border-box , as it was in Webkit’s UA stylesheet, so I didn’t have to worry about padding changing the width of the element. The first problem was solved. Becoming dynamic The static part was quite easy indeed. Selecting all elements and using their attributes to set an appropriate padding-right was pretty much run of the mill. But that wasn’t enough. What happens if you set the properties through script? What happens if you set the attributes? The progress bar should update accordingly, it had to be dynamic. A static progress bar is not much of a fallback. It might be acceptable for , since in most interfaces it’s used in a static way. But a progress bar needs to change in order to show um, progress . First step was adding the properties that are in its DOM Interface. “Easy, I’ll add them to the prototype” thought my naïve self. So, I needed to find which prototype, I didn’t want to add them in every HTML element of course. So I eagerly typed Object.prototype.toString.call(document.createElement(&#39;progress&#39;)) in Firebug’s console and it slapped me in the face with an &#39;[object HTMLUnknownElement]&#39; . D’oh! I had forgotten that unknown elements share a common prototype named like that. So, I had to add them to each one individually. I hated that, but since it was the only way, I did it and moved on. Of course, I didn’t just assign a static value to them, otherwise they wouldn’t solve much: The progress bar would still be static. I assigned getters and setters that used the value and max attributes to return what they should. Assigning getters and setters to a property is a whole new problem by itself, as some browsers use __defineGetter__ / __defineSetter__ and some others the ES5 standard Object.defineProperty . But I had solved that one before , so it didn’t slow me down. The getters and setters solved the issue one-way only: If you set the properties, the progress bar and its attributes would be updated. That would be enough for most authors using the polyfill, but no, I wanted it to be perfect . “If you change the attributes, the progress bar and its properties should too!” my annoyingly pedantic inner self insisted. “And what if you dynamically add more elements?”. There are two ways to do stuff when attributes change and elements get added: Polling and mutation events . The advantage of polling is its perfect browser support, which comes at a big cost: It’s horrible performance-wise. Also, polling introduces a delay that could be unacceptable in some cases, especially considering how short the duration of some progress bar use cases is. So, I went with mutation events, even though they are deprecated (seriously W3C? deprecating something, without providing a solid alternative??) and don’t have perfect browser support. After all, it was the only way (I don’t consider polling a real option in this case). Styling After messing around a little, it seemed to work great in Opera 10.63 and Firefox 5, which I had open for my tests. It was time to write some unit tests and check it out in more browsers. Instead, I opted to style it, as a desperate attempt to delay my confrontation with IE8 a bit longer (and for good reason, as it turned out later). Given that CSS is kinda my specialization, I expected styling to be a piece of cake and even relaxing. Instead, it came with it’s fair share of trouble and hard dilemmas. If you notice the native progress bars in OSX, you will see that they use gradients. I mocked up something similar with CSS gradients, which wasn’t easy, as I wanted to keep the hue/saturation information in the background-color only, for easy modifications and Webkit uses a regular gradient with color stops that have different hues and saturations. And then I realised that this was not going to show up at all in IE8-IE9, which were 2 major browsers that my polyfill would target. No gradient may be acceptable in determinate progress bars, but it’s not an option in indeterminate ones: Scrolling diagonal stripes is the convention and there’s no other way to communicate this status to the average user. So I decided to go with the old way of using raster images for gradients (through a data URI). Another painful slap in the face was when I realized that those moving stripes need to be semi-transparent. To do that, my options were: CSS3 animations - no good in my case, as it’s crucial to show up and their browser support isn’t that good SVG with SMIL - Much better browser support than CSS3 animations, but still no go in IE APNG - Only supported by Firefox and Opera, even after all these years I happened to be chatting with Tab Atkins at the moment, and he suggested I go with plain ol’ GIFs. I was originally negative, but after thinking about it I realized that antialiasing is not that crucial in 45deg stripes, especially when they’re moving. I tried it, I liked the result, so I kept it. Phew, that one was easy. The IE8 nightmare After spending a few hours tweaking the gradients and the CSS (yes, hours. I said I’m an obsessive perfectionist, didn’t I?) I finally wrote some unit tests and fired up Virtualbox to test with IE8. I prepared myself for the worst, and secretly hoped I’d be pleasantly surprised. Instead, I faced a developer’s worst nightmare. Two words: Stack overflow. The culprit was a classic IE bug with DOM properties and HTML attrtibutes that I had blissfully forgotten: IE thinks they’re the same. I had added getters and setters (or etters, as I like to call both) to the max and value properties which used the max and value attributes , resulting in infinite recursion in IE8. This was the hardest of all problems, and I never completely solved it: A few unit tests still fail in IE8 because of it, although there’s no infinite recursion any more. Luckily, this bug was fixed in IE9, so the polyfill works flawlessly there. My first idea was the obvious one: to duplicate the values somewhere. In a lookup table, in another property, somewhere. I didn’t quite like the idea, so I kept brainstorming. And then it dawned on me. They’re already duplicated somewhere, and not only it’s not redundant, but actually encouraged: in the WAI-ARIA attributes! To clarify, when progress elements are natively supported, they already have built-in ARIA roles and attributes. However, when they’re not, you should add them yourself, if you want the control to be accessible. From my research, there was a progressbar role, and it required the attributes aria-valuemin , aria-valuemax , aria-valuenow and aria-labelledby . I implemented all but the latter, as it proved too much of a hassle for very few edge cases (how many people put IDs in their labels without using aria-labelledby themselves?). So, aria-valuemax was already duplicating max and aria-valuenow was duplicating value . I changed everything to use those instead. After lots of head-scratching, IE-cursing and feeling that my brain was going to explode all over my laptop, I managed to kinda have it working. I knew in advance that some unit tests would fail, as it doesn’t support mutation events. I eventually gave up when I realized that the last unit test in the “static” category failed because getAttribute(&#39;max&#39;) returned null , since IE had completely removed the attribute from the DOM tree. It was the last straw and made me say “That’s it, I’m done with this piece of shit”. Safari 5 craziness After IE, it was Safari’s turn. I knew that I could only target Safari 5, as Safari 4 doesn’t support etters on DOM elements and Safari 5.1 will probably support progress elements natively, since they’ve been in Webkit for ages. I launched Safari without fear. “How can it possibly not work in Safari? It will probably be fine, maybe just need a one or two little tweaks in the worst case”, I reassured myself thinking. The progress bars were not even showing. At all. My first guess was that it was a one time rendering error. When it persisted after a few reloads, I opened the dev tools to see what the hell happened. I saw a series of errors like this: is not allowed inside . Content ignored. Unmatched encountered. Ignoring tag. At first, I thought the problem was the label. So I made all labels external. And then still got the same errors for the s. And every other element I tried. Even when I put them directly into the , Safari complained that they are not allowed to be inside it! It turned out that this was a bug in a build of Webkit, and coincidentally, this build was the one Safari 5 uses. There wasn’t much to think about in this one: They’re not in the DOM, so I can’t do anything about them. It’s mission impossible. Happy(?) end After IE8’s and Safari5’s cruel rejection, I was quite dispirited. IE8 had already caused me to make my code uglier and more verbose, and now Safari 5 flat out refuses to accept any treatment. It worked flawlessly in Firefox 3.5, but that didn’t cheer me up much. I decided that this has already taken up too much of my time. It’s now the community’s turn. Have any ideas about how further improvement? Maybe some more unit tests? I’ll be waiting for your pull requests! :) Github repo Appendix: Why do some unit tests fail in browsers that natively support ? While developing this, I discovered 2 browser bugs: One in Webkit’s implementation and in for Opera’s. I plan to report these soon.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS reflections for Firefox, with -moz-element() and SVG masks</title>
  <link>https://lea.verou.me/2011/06/css-reflections-for-firefox-with-moz-element-and-svg-masks/</link>
  <pubDate>Sun, 17 May 2026 01:05:19 +0200</pubDate>
  <description>We all know about the proprietary (and imho, horrible) -webkit-box-reflect. However, you can create just as flexible reflections in Firefox as well, by utilizing -moz-element() , some CSS3 and Firefox’s capability to apply SVG effects to HTML elements . And all these are actually standards, so eventually, this will work in all browsers, unlike -webkit-box-reflect , which was never accepted by the CSS WG. First and foremost, have a look at the demo : How it works For every element, we generate an ::after pseudoelement with the same dimensions and a position of being right below our original element. Then, we make it appear the same as our element, by giving it a background of ‑moz-element(#element-id) and no content. Reflections are flipped, so we flip it vertically, by applying transform: scaleY(‑1); If we want the reflection to have a little distance from the element (for example 10px like the demo), we also apply a transform of translateY(10px) We want the reflection to not be as opaque as the real element, so we give it an opacity of around 0.3-0.4 At this point, we already have a decent reflection , and we didn’t even need SVG masks yet. It’s essentially the same result -webkit-box-reflect gives if you don’t specify a mask image. However, to really make it look like a reflection, we apply a mask through an SVG and the mask CSS property. In this demo, the SVG is external, but it could be a data URI, or even embedded in the HTML. Caveats Won’t work with replaced elements (form controls, images etc). If you have borders, it gets a bit more complicated to size it properly Doesn’t degrade gracefully, you still get the pseudoelement in other browsers, so you need to filter it out yourself Bad browser support (currently only Firefox 4+) You need to set the reflection’s background for every element and every element needs an id to use it (but this could be done automatically via script) Further reading element() function SVG effects for CSS Edit: Turns out Paul Rouget did something similar before me, back in August 2010 . The pros of this approach is that it works with replaced elements as well, the cons is that it requires extra markup and JavaScript. Credits: Thanks to Christian Heilmann for helping me debug why SVG masks for HTML elements weren’t originally working for me.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>A polyfill for HTML5 progress element, the obsessive perfectionist way</title>
  <link>https://lea.verou.me/2011/07/a-polyfill-for-html5-progress-element-the-obsessive-perfectionist-way/</link>
  <pubDate>Sun, 17 May 2026 01:05:19 +0200</pubDate>
  <description>Yesterday, for some reason I dont remember, I was looking once more at Paul Irishs excellent list of polyfills on Github . I was really surprised to see that there are none for the element . It seemed really simple: Easy to fake with CSS and only 4 IDL attributes (value, max, position and labels). Hey, it sounds fun and easy, Ill do it!, I thought. I have no idea how in only 1 day this turned into OMG, my brain is going to explode. Ive documented below all the pitfalls I faced. And dont worry, it has a happy ending: I did finish it. And published it . So, if youre not interested in long geeky stories, just jump straight to its page . First things first: Controlling the width of the value bar Most progress bars out there use 2 elements: One for the container and one for the value bar. I was pretty stubborn about not using an extra element. I wanted to use pseudo-elements instead and keep the DOM tree as clean as I found it. And there it was, the first problem: How to set the width? CSS3 attr() and calc() are hardly supported and attr() is not even allowed in calc() , so I quickly realized that a pure CSS solution was out of the question. However, if I used JavaScript, how would I set a different width for every progress::before? You cant set that in an inline style, and assigning every element an ID and adding separate rules seems a bit too intrusive to me. Think about it for a second, what would you do? I realized I had to control the width of the pseudo-element through CSS properties of the parent container somehow. And then it dawned on me: If the pseudoelement has display:block , it will automatically get the parent width, minus the padding and borders. There it was, this was my solution. I just had to set padding-right accordingly, so that the value bar gets the width it needs to be! And I had already given it box-sizing: border-box , as it was in Webkits UA stylesheet, so I didnt have to worry about padding changing the width of the element. The first problem was solved. Becoming dynamic The static part was quite easy indeed. Selecting all elements and using their attributes to set an appropriate padding-right was pretty much run of the mill. But that wasnt enough. What happens if you set the properties through script? What happens if you set the attributes? The progress bar should update accordingly, it had to be dynamic. A static progress bar is not much of a fallback. It might be acceptable for , since in most interfaces its used in a static way. But a progress bar needs to change in order to show um, progress . First step was adding the properties that are in its DOM Interface. Easy, Ill add them to the prototype thought my naïve self. So, I needed to find which prototype, I didnt want to add them in every HTML element of course. So I eagerly typed Object.prototype.toString.call(document.createElement(&#39;progress&#39;)) in Firebugs console and it slapped me in the face with an &#39;[object HTMLUnknownElement]&#39; . Doh! I had forgotten that unknown elements share a common prototype named like that. So, I had to add them to each one individually. I hated that, but since it was the only way, I did it and moved on. Of course, I didnt just assign a static value to them, otherwise they wouldnt solve much: The progress bar would still be static. I assigned getters and setters that used the value and max attributes to return what they should. Assigning getters and setters to a property is a whole new problem by itself, as some browsers use __defineGetter__ / __defineSetter__ and some others the ES5 standard Object.defineProperty . But I had solved that one before , so it didnt slow me down. The getters and setters solved the issue one-way only: If you set the properties, the progress bar and its attributes would be updated. That would be enough for most authors using the polyfill, but no, I wanted it to be perfect . If you change the attributes, the progress bar and its properties should too! my annoyingly pedantic inner self insisted. And what if you dynamically add more elements?. There are two ways to do stuff when attributes change and elements get added: Polling and mutation events . The advantage of polling is its perfect browser support, which comes at a big cost: Its horrible performance-wise. Also, polling introduces a delay that could be unacceptable in some cases, especially considering how short the duration of some progress bar use cases is. So, I went with mutation events, even though they are deprecated (seriously W3C? deprecating something, without providing a solid alternative??) and dont have perfect browser support. After all, it was the only way (I dont consider polling a real option in this case). Styling After messing around a little, it seemed to work great in Opera 10.63 and Firefox 5, which I had open for my tests. It was time to write some unit tests and check it out in more browsers. Instead, I opted to style it, as a desperate attempt to delay my confrontation with IE8 a bit longer (and for good reason, as it turned out later). Given that CSS is kinda my specialization, I expected styling to be a piece of cake and even relaxing. Instead, it came with its fair share of trouble and hard dilemmas. If you notice the native progress bars in OSX, you will see that they use gradients. I mocked up something similar with CSS gradients, which wasnt easy, as I wanted to keep the hue/saturation information in the background-color only, for easy modifications and Webkit uses a regular gradient with color stops that have different hues and saturations. And then I realised that this was not going to show up at all in IE8-IE9, which were 2 major browsers that my polyfill would target. No gradient may be acceptable in determinate progress bars, but its not an option in indeterminate ones: Scrolling diagonal stripes is the convention and theres no other way to communicate this status to the average user. So I decided to go with the old way of using raster images for gradients (through a data URI). Another painful slap in the face was when I realized that those moving stripes need to be semi-transparent. To do that, my options were: CSS3 animations - no good in my case, as its crucial to show up and their browser support isnt that good SVG with SMIL - Much better browser support than CSS3 animations, but still no go in IE APNG - Only supported by Firefox and Opera, even after all these years I happened to be chatting with Tab Atkins at the moment, and he suggested I go with plain ol GIFs. I was originally negative, but after thinking about it I realized that antialiasing is not that crucial in 45deg stripes, especially when theyre moving. I tried it, I liked the result, so I kept it. Phew, that one was easy. The IE8 nightmare After spending a few hours tweaking the gradients and the CSS (yes, hours. I said Im an obsessive perfectionist, didnt I?) I finally wrote some unit tests and fired up Virtualbox to test with IE8. I prepared myself for the worst, and secretly hoped Id be pleasantly surprised. Instead, I faced a developers worst nightmare. Two words: Stack overflow. The culprit was a classic IE bug with DOM properties and HTML attrtibutes that I had blissfully forgotten: IE thinks theyre the same. I had added getters and setters (or etters, as I like to call both) to the max and value properties which used the max and value attributes , resulting in infinite recursion in IE8. This was the hardest of all problems, and I never completely solved it: A few unit tests still fail in IE8 because of it, although theres no infinite recursion any more. Luckily, this bug was fixed in IE9, so the polyfill works flawlessly there. My first idea was the obvious one: to duplicate the values somewhere. In a lookup table, in another property, somewhere. I didnt quite like the idea, so I kept brainstorming. And then it dawned on me. Theyre already duplicated somewhere, and not only its not redundant, but actually encouraged: in the WAI-ARIA attributes! To clarify, when progress elements are natively supported, they already have built-in ARIA roles and attributes. However, when theyre not, you should add them yourself, if you want the control to be accessible. From my research, there was a progressbar role, and it required the attributes aria-valuemin , aria-valuemax , aria-valuenow and aria-labelledby . I implemented all but the latter, as it proved too much of a hassle for very few edge cases (how many people put IDs in their labels without using aria-labelledby themselves?). So, aria-valuemax was already duplicating max and aria-valuenow was duplicating value . I changed everything to use those instead. After lots of head-scratching, IE-cursing and feeling that my brain was going to explode all over my laptop, I managed to kinda have it working. I knew in advance that some unit tests would fail, as it doesnt support mutation events. I eventually gave up when I realized that the last unit test in the static category failed because getAttribute(&#39;max&#39;) returned null , since IE had completely removed the attribute from the DOM tree. It was the last straw and made me say Thats it, Im done with this piece of shit. Safari 5 craziness After IE, it was Safaris turn. I knew that I could only target Safari 5, as Safari 4 doesnt support etters on DOM elements and Safari 5.1 will probably support progress elements natively, since theyve been in Webkit for ages. I launched Safari without fear. How can it possibly not work in Safari? It will probably be fine, maybe just need a one or two little tweaks in the worst case, I reassured myself thinking. The progress bars were not even showing. At all. My first guess was that it was a one time rendering error. When it persisted after a few reloads, I opened the dev tools to see what the hell happened. I saw a series of errors like this: is not allowed inside . Content ignored. Unmatched encountered. Ignoring tag. At first, I thought the problem was the label. So I made all labels external. And then still got the same errors for the s. And every other element I tried. Even when I put them directly into the , Safari complained that they are not allowed to be inside it! It turned out that this was a bug in a build of Webkit, and coincidentally, this build was the one Safari 5 uses. There wasnt much to think about in this one: Theyre not in the DOM, so I cant do anything about them. Its mission impossible. Happy(?) end After IE8s and Safari5s cruel rejection, I was quite dispirited. IE8 had already caused me to make my code uglier and more verbose, and now Safari 5 flat out refuses to accept any treatment. It worked flawlessly in Firefox 3.5, but that didnt cheer me up much. I decided that this has already taken up too much of my time. Its now the communitys turn. Have any ideas about how further improvement? Maybe some more unit tests? Ill be waiting for your pull requests! :) Github repo Appendix: Why do some unit tests fail in browsers that natively support ? While developing this, I discovered 2 browser bugs: One in Webkits implementation and in for Operas. I plan to report these soon.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS reflections for Firefox, with -moz-element() and SVG masks</title>
  <link>https://lea.verou.me/2011/06/css-reflections-for-firefox-with-moz-element-and-svg-masks/</link>
  <pubDate>Sun, 17 May 2026 01:05:19 +0200</pubDate>
  <description>We all know about the proprietary (and imho, horrible) -webkit-box-reflect. However, you can create just as flexible reflections in Firefox as well, by utilizing -moz-element() , some CSS3 and Firefoxs capability to apply SVG effects to HTML elements . And all these are actually standards, so eventually, this will work in all browsers, unlike -webkit-box-reflect , which was never accepted by the CSS WG. First and foremost, have a look at the demo : How it works For every element, we generate an ::after pseudoelement with the same dimensions and a position of being right below our original element. Then, we make it appear the same as our element, by giving it a background of moz-element(#element-id) and no content. Reflections are flipped, so we flip it vertically, by applying transform: scaleY(1); If we want the reflection to have a little distance from the element (for example 10px like the demo), we also apply a transform of translateY(10px) We want the reflection to not be as opaque as the real element, so we give it an opacity of around 0.3-0.4 At this point, we already have a decent reflection , and we didnt even need SVG masks yet. Its essentially the same result -webkit-box-reflect gives if you dont specify a mask image. However, to really make it look like a reflection, we apply a mask through an SVG and the mask CSS property. In this demo, the SVG is external, but it could be a data URI, or even embedded in the HTML. Caveats Wont work with replaced elements (form controls, images etc). If you have borders, it gets a bit more complicated to size it properly Doesnt degrade gracefully, you still get the pseudoelement in other browsers, so you need to filter it out yourself Bad browser support (currently only Firefox 4+) You need to set the reflections background for every element and every element needs an id to use it (but this could be done automatically via script) Further reading element() function SVG effects for CSS Edit: Turns out Paul Rouget did something similar before me, back in August 2010 . The pros of this approach is that it works with replaced elements as well, the cons is that it requires extra markup and JavaScript. Credits: Thanks to Christian Heilmann for helping me debug why SVG masks for HTML elements werent originally working for me.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Pure CSS Tic Tac Toe</title>
  <link>https://lea.verou.me/2011/06/pure-css-tic-tac-toe/</link>
  <pubDate>Sun, 17 May 2026 01:05:18 +0200</pubDate>
  <description>It’s supposed to be used by 2 people taking turns (click twice for the other sign). Basic idea: It uses hidden checkboxes for the states (indeterminate means empty, checked means X, not checked means O) and labels for the visible part When it starts, a little script (the only js in the demo) sets the states of all checkboxes to indeterminate. It uses the :checked and :indeterminate pseudo-classes and sibling combinators to change the states and show who won. Once somebody clicks on a checkbox (or in this case, its label) they change it’s state from indeterminate to either checked or not checked, depending on how many times they click on it. As a bonus, it’s perfectly accessible through the keyboard (although I assume it’s not screen reader accessible). A would be much more appropriate for the markup, but I decided to sacrifice semantics in this case to make the demo simpler. All modern browsers support the indeterminate state in checkboxes (for Opera you will need the latest Opera.Next), however this demo doesn’t work on old Webkit (Chrome and Safari) because of an old bug that made the sibling combinators (+ and ~) static in some cases which has been fixed in the nightlies. It should work in Firefox, Opera.next, Webkit nightlies and IE9, although I haven’t tested in Opera.next and IE9 to verify. Enjoy:</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Pure CSS Tic Tac Toe</title>
  <link>https://lea.verou.me/2011/06/pure-css-tic-tac-toe/</link>
  <pubDate>Sun, 17 May 2026 01:05:18 +0200</pubDate>
  <description>Its supposed to be used by 2 people taking turns (click twice for the other sign). Basic idea: It uses hidden checkboxes for the states (indeterminate means empty, checked means X, not checked means O) and labels for the visible part When it starts, a little script (the only js in the demo) sets the states of all checkboxes to indeterminate. It uses the :checked and :indeterminate pseudo-classes and sibling combinators to change the states and show who won. Once somebody clicks on a checkbox (or in this case, its label) they change its state from indeterminate to either checked or not checked, depending on how many times they click on it. As a bonus, its perfectly accessible through the keyboard (although I assume its not screen reader accessible). A would be much more appropriate for the markup, but I decided to sacrifice semantics in this case to make the demo simpler. All modern browsers support the indeterminate state in checkboxes (for Opera you will need the latest Opera.Next), however this demo doesnt work on old Webkit (Chrome and Safari) because of an old bug that made the sibling combinators (+ and ~) static in some cases which has been fixed in the nightlies. It should work in Firefox, Opera.next, Webkit nightlies and IE9, although I havent tested in Opera.next and IE9 to verify. Enjoy:</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>jQuery Pure: Call for contributors</title>
  <link>https://lea.verou.me/2011/06/jquery-pure/</link>
  <pubDate>Sun, 17 May 2026 01:05:16 +0200</pubDate>
  <description>This post is about an idea I’ve had for ages, but never found the time to actually start working on it. Maybe because it looks like a quite big project if done properly, so it’s scary to do it on my own without any help. jQuery has a huge amount of code that deals with browser bugs and lack of implementations. For example, it needs a full-fledged selector engine, to cater for old browsers that don’t support the Selectors API . Or, it needs code that essentially does what the classList API is supposed to do, because old browsers don’t support it. Same goes for nextElementSibling (the .next() method) and tons of other stuff. However, not everyone needs all this. Some projects don’t need older browsers support, either due to the developer mindset or due to their tech-savvy target group. Also, some people only write demos/proof-of-concepts for modern browsers only and don’t need all this code. Same goes for intranet apps that are only designed for a particular modern browser. Last but not least, this code bloat makes the jQuery library hard to study for educational purposes. However, even in a browser that supports all the modern stuff, the jQuery API is still more concise than the native methods. Besides, there are tons of plugins that depend on it, so if you decide to implement everything in native JavaScript, you can’t use them. What I want to build is a fork of jQuery that is refactored so that all the extra code for working around browser bugs removed and all the code replaced by native functionality, where possible. All the ugliness removed, leaving a small, concise abstraction that only uses the current standards. Something like jQuery: The good parts . It could also serve as a benchmark for browser standards support. The API will work in the exact same way and pass all unit tests (in modern browsers, in cases where they are not buggy) so that almost every plugin built on top of it will continue to function just as well. However, the jQuery library itself will be much smaller, with more elegant and easy to understand code. So, who’s with me? Do you find such an idea interesting and useful? Would you want to contribute? If so, leave a comment below or send me an email (it’s in the about page ). Also, please let me know if you can think of any other uses, or if there’s already something like that that I’ve missed.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>jQuery Pure: Call for contributors</title>
  <link>https://lea.verou.me/2011/06/jquery-pure/</link>
  <pubDate>Sun, 17 May 2026 01:05:16 +0200</pubDate>
  <description>This post is about an idea Ive had for ages, but never found the time to actually start working on it. Maybe because it looks like a quite big project if done properly, so its scary to do it on my own without any help. jQuery has a huge amount of code that deals with browser bugs and lack of implementations. For example, it needs a full-fledged selector engine, to cater for old browsers that dont support the Selectors API . Or, it needs code that essentially does what the classList API is supposed to do, because old browsers dont support it. Same goes for nextElementSibling (the .next() method) and tons of other stuff. However, not everyone needs all this. Some projects dont need older browsers support, either due to the developer mindset or due to their tech-savvy target group. Also, some people only write demos/proof-of-concepts for modern browsers only and dont need all this code. Same goes for intranet apps that are only designed for a particular modern browser. Last but not least, this code bloat makes the jQuery library hard to study for educational purposes. However, even in a browser that supports all the modern stuff, the jQuery API is still more concise than the native methods. Besides, there are tons of plugins that depend on it, so if you decide to implement everything in native JavaScript, you cant use them. What I want to build is a fork of jQuery that is refactored so that all the extra code for working around browser bugs removed and all the code replaced by native functionality, where possible. All the ugliness removed, leaving a small, concise abstraction that only uses the current standards. Something like jQuery: The good parts . It could also serve as a benchmark for browser standards support. The API will work in the exact same way and pass all unit tests (in modern browsers, in cases where they are not buggy) so that almost every plugin built on top of it will continue to function just as well. However, the jQuery library itself will be much smaller, with more elegant and easy to understand code. So, whos with me? Do you find such an idea interesting and useful? Would you want to contribute? If so, leave a comment below or send me an email (its in the about page ). Also, please let me know if you can think of any other uses, or if theres already something like that that Ive missed.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from Web Directions @media &amp; Standards.next</title>
  <link>https://lea.verou.me/2011/06/my-experience-from-web-directions-media-standards-next/</link>
  <pubDate>Sun, 17 May 2026 01:05:15 +0200</pubDate>
  <description>Last week, I was in London to give 2 talks. The first one was last Thursday, in one of the conferences I wanted to go ever since I learned my first CSS properties: Web directions @media . The second one was 2 days later in a smaller event called Standards.next . Web Directions @media I managed to get my @media talk early on schedule, so I could relax afterwards and enjoy the rest of the conference. Before I saw the feedback on twitter I thought they hated it, since the audience was silent and didn’t laugh at any of my jokes and asked no questions afterwards. However, I was wrong: The tweets about it were enthusiastic! Here’s a small sample: https://twitter.com/KrokoHunter/status/73686696381779968 https://twitter.com/adrmakow/status/73688461537849344 https://twitter.com/KrokoHunter/status/73691398058147840 https://twitter.com/xtinafowler/status/73691708138848256 https://twitter.com/stefsull/status/73692171684941825 https://twitter.com/ubelly/status/73692212772343808 https://twitter.com/pauliom/status/73694312315092992 https://twitter.com/mfujica/status/73694696517545984 https://twitter.com/johnallsopp/status/73694876524490752 https://twitter.com/johnallsopp/status/73695196986085376 https://twitter.com/prawnstar/status/73697565970935808 https://twitter.com/KrokoHunter/status/73700078921056256 https://twitter.com/matmannion/status/73700794624516096 https://twitter.com/farinab/status/73700896722268160 https://twitter.com/GlennCahill87/status/73701420704075776 https://twitter.com/NIMRweb/status/73701806122868736 https://twitter.com/xtinafowler/status/73702048620744704 https://twitter.com/KrokoHunter/status/73728517489172480 https://twitter.com/KrokoHunter/status/74227907878334464 https://twitter.com/blagdaross/status/74429856514965504 https://twitter.com/sparrk/status/74556639608442880 https://twitter.com/anttio/status/74575465905520641 https://twitter.com/sirbenjaminnunn/status/74900149448609792 You can play with the HTML version of my slides or view them on slideshare : Mastering CSS3 gradients View more presentations from Lea Verou I really enjoyed some of the other talks in @media, especially: Stephanie Sullivan ’s “Modern CSS” Bruce Lawson ’s “Multimedia in HTML5” Nicole Sullivan ’s “Performance of CSS3 and HTML5” Standards.next The morning before my Standards.next talk, I woke up with a sore throat, a running nose and a blocked ear. I even thought about cancelling my talk, but I’m one of those people that have to be dying to change their schedule. So I went, and I’m glad I did, as I got to attend Peter Gasston ’s incredible talk on CSS3 layout. I really learned so much stuff from that! As for my talk (“CSS Secrets: 10 things you might not know about CSS3”), it went fine after all. I had some trouble hearing the questions, due to the blocked ear, but nothing too bad. I had trouble with my last demo, as I got confused and used background-origin: padding-box; instead of content-box , but nobody there hated me because of it, like I was afraid would happen if I ever screwed up one of my demos :) That event was much smaller (it took place in a small room in a pub), so the tweets were much fewer, but still very positive: https://twitter.com/patrick\_h\_lauke/status/74496751716929536 https://twitter.com/stopsatgreen/status/74497512261693440 https://twitter.com/mfujica/status/74499137558687744 https://twitter.com/danielknell/status/74499496746299392 https://twitter.com/brucel/status/74500503098245121 https://twitter.com/patrick\_h\_lauke/status/74501557349138432 https://twitter.com/danielknell/status/74502234251071488 https://twitter.com/mfujica/status/74504295927648257 https://twitter.com/designjuju/status/74504416534855680 https://twitter.com/mfujica/status/74504823881469952 https://twitter.com/twitrnick/status/74506406191046656 https://twitter.com/patrick\_h\_lauke/status/74506910807764992 https://twitter.com/stopsatgreen/status/74508394974810113 https://twitter.com/stefsull/status/74508645513175040 https://twitter.com/albybarber/status/74532879832584194 https://twitter.com/jackosborne/status/74608125289828352 I found out afterwards that one particular lady in the audience complained about my pronunciation of the words “fuchsia” and “ems”. That’s what I would’ve said to her if I heard: “Here’s some breaking news to you: Not everyone is a native english speaker. Shocking, isn’t it? I would really be interested to hear your pronunciation if you ever did a presentation in Greek. KKTHXBAI” Overall, I had a great time in London. I hadn’t been there for more than 10 years, so I had forgotten how beautiful city it is. I loved attending and speaking at both of those events, and I would like to thank Maxine Sherrin and John Allsopp for inviting me to @media and Bruce Lawson for inviting me at Standards.next.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Get your hash — the bulletproof way</title>
  <link>https://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/</link>
  <pubDate>Sun, 17 May 2026 01:05:15 +0200</pubDate>
  <description>This is probably one of the things that everyone thinks they know how to do but many end up doing it wrong. After coming accross yet one more super fragile snippet of code for this, I decided a blog post was in order. The problem You want to remove the pound sign (#) from location.hash . For example, when the hash is &quot;#foo&quot; , you want to get a string containing &quot;foo&quot; . That’s really simple, right? Tricky cases What most developers seem to miss is that in modern, JavaScript-heavy applications, a hash can contain any unicode character. It doesn’t necessarily have to correspond to the value of an actual id attribute in the page. And even when it does, ID attributes can now contain almost any unicode character . Another thing sometimes forgotten is that there might be no hash in the page. Even in a URL that ends in #, location.hash is actually equal to &quot;&quot; (the empty string) and not &quot;#&quot; . Naive approaches This one is the most recent, found in a book I was tech reviewing: var hash = location.hash.match(/#(\w+)/)[1]; which has quite a few issues: Returns wrong results when there is any non-latin or non-alphanumeric character in the hash. For example, for the hash #foo@o#bar$%huh hello , just &quot;foo&quot; would be returned. Throws a TypeError when location.hash is empty, since .match() will return null . Other variations of this pattern I’ve seen include using explicitly defined character classes instead of \w , adding an anchor ( ^ ) before the pound sign (which is an excellent idea for performance) and checking if .match() actually returned something before using its result. However, they usually also fall into at least one of the 2 aforementioned issues. Another approach a friend of mine once used was this: var hash = location.hash.split(‘#’)[1]; This also has its issues, which are ironically less than the first one, even though it seems a far more naive approach. With the same test hash, it would at least get the &quot;foo@o&quot; part, which means it only fails when the hash contains a pound sign When there’s no hash, it doesn’t throw an error, although it returns undefined instead of the empty string. Getting it right The approach I usually use is far simpler than both of the above and probably looks too loose: var hash = location.hash.substring(1); However, let’s examine it a bit: With our weird test hash, it actually returns the correct result: “foo@o#bar$%huh hello” When no hash exists, it correctly returns the empty string “But it assumes there’s a pound sign at the start of the string!” I almost hear some of you cry. Well, that could be a real concern, if we were dealing with an arbitrary string. In that case, we would have to check if there’s actually a pound sign first or if the string even exists. However, with location.hash the only case when that is not true, is when there is no hash. And we got that case covered. ;) Edit: As pointed out in the comments, you may also use location.hash.slice(1) instead of substring . I kinda prefer it, since it’s 4 bytes shorter. If however you’re obsessed with RegExps and want to do it with them no matter what, this is just as bulletproof and almost as short: var hash = location.hash.replace(/^#/, ‘’); If for some reason (OCD?) you want to do it with .match() no matter what, you could do this: var match = location.hash.match(/^#?(.*)$/)[1]; In that case, since the pound sign is optional, since .match() never returns null . And no, the pound sign never erroneously becomes part of the returned hash, because of the way regex engines work. “This is too basic, what a waste of my time!” Sorry for that. I know that for some of you, this is elementary. But the guy who wrote that book is very knowledgable (the book is really good, apart from that code snippet) so I thought this means there are many good developers out there who get this wrong, so this post was needed to be written. If you’re not one of them, you can take it as a compliment. “Hey, you missed something too!” In that case, I’d love to find out what it is, so please leave a comment! :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from Web Directions @media &amp; Standards.next</title>
  <link>https://lea.verou.me/2011/06/my-experience-from-web-directions-media-standards-next/</link>
  <pubDate>Sun, 17 May 2026 01:05:15 +0200</pubDate>
  <description>Last week, I was in London to give 2 talks. The first one was last Thursday, in one of the conferences I wanted to go ever since I learned my first CSS properties: Web directions @media . The second one was 2 days later in a smaller event called Standards.next . Web Directions @media I managed to get my @media talk early on schedule, so I could relax afterwards and enjoy the rest of the conference. Before I saw the feedback on twitter I thought they hated it, since the audience was silent and didnt laugh at any of my jokes and asked no questions afterwards. However, I was wrong: The tweets about it were enthusiastic! Heres a small sample: https://twitter.com/KrokoHunter/status/73686696381779968 https://twitter.com/adrmakow/status/73688461537849344 https://twitter.com/KrokoHunter/status/73691398058147840 https://twitter.com/xtinafowler/status/73691708138848256 https://twitter.com/stefsull/status/73692171684941825 https://twitter.com/ubelly/status/73692212772343808 https://twitter.com/pauliom/status/73694312315092992 https://twitter.com/mfujica/status/73694696517545984 https://twitter.com/johnallsopp/status/73694876524490752 https://twitter.com/johnallsopp/status/73695196986085376 https://twitter.com/prawnstar/status/73697565970935808 https://twitter.com/KrokoHunter/status/73700078921056256 https://twitter.com/matmannion/status/73700794624516096 https://twitter.com/farinab/status/73700896722268160 https://twitter.com/GlennCahill87/status/73701420704075776 https://twitter.com/NIMRweb/status/73701806122868736 https://twitter.com/xtinafowler/status/73702048620744704 https://twitter.com/KrokoHunter/status/73728517489172480 https://twitter.com/KrokoHunter/status/74227907878334464 https://twitter.com/blagdaross/status/74429856514965504 https://twitter.com/sparrk/status/74556639608442880 https://twitter.com/anttio/status/74575465905520641 https://twitter.com/sirbenjaminnunn/status/74900149448609792 You can play with the HTML version of my slides or view them on slideshare : Mastering CSS3 gradients View more presentations from Lea Verou I really enjoyed some of the other talks in @media, especially: Stephanie Sullivan s Modern CSS Bruce Lawson s Multimedia in HTML5 Nicole Sullivan s Performance of CSS3 and HTML5 Standards.next The morning before my Standards.next talk, I woke up with a sore throat, a running nose and a blocked ear. I even thought about cancelling my talk, but Im one of those people that have to be dying to change their schedule. So I went, and Im glad I did, as I got to attend Peter Gasston s incredible talk on CSS3 layout. I really learned so much stuff from that! As for my talk (CSS Secrets: 10 things you might not know about CSS3), it went fine after all. I had some trouble hearing the questions, due to the blocked ear, but nothing too bad. I had trouble with my last demo, as I got confused and used background-origin: padding-box; instead of content-box , but nobody there hated me because of it, like I was afraid would happen if I ever screwed up one of my demos :) That event was much smaller (it took place in a small room in a pub), so the tweets were much fewer, but still very positive: https://twitter.com/patrick\_h\_lauke/status/74496751716929536 https://twitter.com/stopsatgreen/status/74497512261693440 https://twitter.com/mfujica/status/74499137558687744 https://twitter.com/danielknell/status/74499496746299392 https://twitter.com/brucel/status/74500503098245121 https://twitter.com/patrick\_h\_lauke/status/74501557349138432 https://twitter.com/danielknell/status/74502234251071488 https://twitter.com/mfujica/status/74504295927648257 https://twitter.com/designjuju/status/74504416534855680 https://twitter.com/mfujica/status/74504823881469952 https://twitter.com/twitrnick/status/74506406191046656 https://twitter.com/patrick\_h\_lauke/status/74506910807764992 https://twitter.com/stopsatgreen/status/74508394974810113 https://twitter.com/stefsull/status/74508645513175040 https://twitter.com/albybarber/status/74532879832584194 https://twitter.com/jackosborne/status/74608125289828352 I found out afterwards that one particular lady in the audience complained about my pronunciation of the words fuchsia and ems. Thats what I wouldve said to her if I heard: Heres some breaking news to you: Not everyone is a native english speaker. Shocking, isnt it? I would really be interested to hear your pronunciation if you ever did a presentation in Greek. KKTHXBAI Overall, I had a great time in London. I hadnt been there for more than 10 years, so I had forgotten how beautiful city it is. I loved attending and speaking at both of those events, and I would like to thank Maxine Sherrin and John Allsopp for inviting me to @media and Bruce Lawson for inviting me at Standards.next.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Get your hash the bulletproof way</title>
  <link>https://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/</link>
  <pubDate>Sun, 17 May 2026 01:05:15 +0200</pubDate>
  <description>This is probably one of the things that everyone thinks they know how to do but many end up doing it wrong. After coming accross yet one more super fragile snippet of code for this, I decided a blog post was in order. The problem You want to remove the pound sign (#) from location.hash . For example, when the hash is &quot;#foo&quot; , you want to get a string containing &quot;foo&quot; . Thats really simple, right? Tricky cases What most developers seem to miss is that in modern, JavaScript-heavy applications, a hash can contain any unicode character. It doesnt necessarily have to correspond to the value of an actual id attribute in the page. And even when it does, ID attributes can now contain almost any unicode character . Another thing sometimes forgotten is that there might be no hash in the page. Even in a URL that ends in #, location.hash is actually equal to &quot;&quot; (the empty string) and not &quot;#&quot; . Naive approaches This one is the most recent, found in a book I was tech reviewing: var hash = location.hash.match(/#(\w+)/)[1]; which has quite a few issues: Returns wrong results when there is any non-latin or non-alphanumeric character in the hash. For example, for the hash #foo@o#bar$%huh hello , just &quot;foo&quot; would be returned. Throws a TypeError when location.hash is empty, since .match() will return null . Other variations of this pattern Ive seen include using explicitly defined character classes instead of \w , adding an anchor ( ^ ) before the pound sign (which is an excellent idea for performance) and checking if .match() actually returned something before using its result. However, they usually also fall into at least one of the 2 aforementioned issues. Another approach a friend of mine once used was this: var hash = location.hash.split(#)[1]; This also has its issues, which are ironically less than the first one, even though it seems a far more naive approach. With the same test hash, it would at least get the &quot;foo@o&quot; part, which means it only fails when the hash contains a pound sign When theres no hash, it doesnt throw an error, although it returns undefined instead of the empty string. Getting it right The approach I usually use is far simpler than both of the above and probably looks too loose: var hash = location.hash.substring(1); However, lets examine it a bit: With our weird test hash, it actually returns the correct result: foo@o#bar$%huh hello When no hash exists, it correctly returns the empty string But it assumes theres a pound sign at the start of the string! I almost hear some of you cry. Well, that could be a real concern, if we were dealing with an arbitrary string. In that case, we would have to check if theres actually a pound sign first or if the string even exists. However, with location.hash the only case when that is not true, is when there is no hash. And we got that case covered. ;) Edit: As pointed out in the comments, you may also use location.hash.slice(1) instead of substring . I kinda prefer it, since its 4 bytes shorter. If however youre obsessed with RegExps and want to do it with them no matter what, this is just as bulletproof and almost as short: var hash = location.hash.replace(/^#/, ); If for some reason (OCD?) you want to do it with .match() no matter what, you could do this: var match = location.hash.match(/^#?(.*)$/)[1]; In that case, since the pound sign is optional, since .match() never returns null . And no, the pound sign never erroneously becomes part of the returned hash, because of the way regex engines work. This is too basic, what a waste of my time! Sorry for that. I know that for some of you, this is elementary. But the guy who wrote that book is very knowledgable (the book is really good, apart from that code snippet) so I thought this means there are many good developers out there who get this wrong, so this post was needed to be written. If youre not one of them, you can take it as a compliment. Hey, you missed something too! In that case, Id love to find out what it is, so please leave a comment! :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Change URL hash without page jump</title>
  <link>https://lea.verou.me/2011/05/change-url-hash-without-page-jump/</link>
  <pubDate>Sun, 17 May 2026 01:05:14 +0200</pubDate>
  <description>In modern complex layouts, sometimes the point where a hash will transport you to will be entirely different than the one you actually wanted. If you prevent the default event, you will save yourself from the page jump, but the hash won’t change either. You can accept the regular behavior and change scrollTop after the jump, but the user will still see a distracting flicker. Chris Coyier found a great workaround last year but it’s not meant for every case. A different solution Turns out we can take advantage of the History API to do that quite easily. It’s just one line of code: history.pushState(null, null, ‘#myhash’); and we can combine it with the old method of setting location.hash to cater for older browsers as well: if(history.pushState) { history.pushState(null, null, ‘#myhash’); } else { location.hash = ‘#myhash’; } Browser support? The History API is supported by: Firefox 4+ Safari 5+ Chrome 8+ Coming soon in Opera Enjoy :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Change URL hash without page jump</title>
  <link>https://lea.verou.me/2011/05/change-url-hash-without-page-jump/</link>
  <pubDate>Sun, 17 May 2026 01:05:14 +0200</pubDate>
  <description>In modern complex layouts, sometimes the point where a hash will transport you to will be entirely different than the one you actually wanted. If you prevent the default event, you will save yourself from the page jump, but the hash wont change either. You can accept the regular behavior and change scrollTop after the jump, but the user will still see a distracting flicker. Chris Coyier found a great workaround last year but its not meant for every case. A different solution Turns out we can take advantage of the History API to do that quite easily. Its just one line of code: history.pushState(null, null, #myhash); and we can combine it with the old method of setting location.hash to cater for older browsers as well: if(history.pushState) { history.pushState(null, null, #myhash); } else { location.hash = #myhash; } Browser support? The History API is supported by: Firefox 4+ Safari 5+ Chrome 8+ Coming soon in Opera Enjoy :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from Geek Meet</title>
  <link>https://lea.verou.me/2011/05/my-experience-from-geek-meet/</link>
  <pubDate>Sun, 17 May 2026 01:05:13 +0200</pubDate>
  <description>I decided to start writing a blog post after every talk I give, to be able to go back and remember what I thought about each event, what feedback my talk got etc. And I’m starting with Geek Meet May 2011 . The event Geek Meet is a meetup organized in Stockholm by Robert Nyman . It has hosted talks by many industry leaders like Jake Archibald , Bruce Lawson , Molly Holzschlag , Chris Mills, Remy Sharp , Christian Heilmann and more. It’s free to attend and has sponsors so it can afford to offer free food, drinks and speaker accommodation. My experience I was very surprised to hear that the event was sold out just 18 minutes after Robert’s announcement! According to him, that set a new record for it! This event was kinda challenging in many ways. I was the only speaker, so if I failed, everyone would notice. Also, I had to give 2 talks and one of them was brand new, which is always stressful. However, the crowd there was awesome! Not only they were very relaxed, but they had a great sense of humor too. I don’t think I had ever been in an event that was so relaxed. And their reaction to my talks was so encouraging, I don’t think I have ever heard such loud clapping in my life! I’m saving the feedback I got here, to bottle the feeling:</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My experience from Geek Meet</title>
  <link>https://lea.verou.me/2011/05/my-experience-from-geek-meet/</link>
  <pubDate>Sun, 17 May 2026 01:05:13 +0200</pubDate>
  <description>I decided to start writing a blog post after every talk I give, to be able to go back and remember what I thought about each event, what feedback my talk got etc. And Im starting with Geek Meet May 2011 . The event Geek Meet is a meetup organized in Stockholm by Robert Nyman . It has hosted talks by many industry leaders like Jake Archibald , Bruce Lawson , Molly Holzschlag , Chris Mills, Remy Sharp , Christian Heilmann and more. Its free to attend and has sponsors so it can afford to offer free food, drinks and speaker accommodation. My experience I was very surprised to hear that the event was sold out just 18 minutes after Roberts announcement! According to him, that set a new record for it! This event was kinda challenging in many ways. I was the only speaker, so if I failed, everyone would notice. Also, I had to give 2 talks and one of them was brand new, which is always stressful. However, the crowd there was awesome! Not only they were very relaxed, but they had a great sense of humor too. I dont think I had ever been in an event that was so relaxed. And their reaction to my talks was so encouraging, I dont think I have ever heard such loud clapping in my life! Im saving the feedback I got here, to bottle the feeling:</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>StronglyTyped: A library for strongly typed properties &amp; constants in JavaScript</title>
  <link>https://lea.verou.me/2011/05/strongly-typed-javascript/</link>
  <pubDate>Sun, 17 May 2026 01:05:12 +0200</pubDate>
  <description>I’ll start by saying I love the loosely typed nature of JavaScript. When I had to work with strongly typed languages like Java, it always seemed like an unnecessary hassle. On the contrary, my boyfriend even though very proficient with HTML, CSS and SVG, comes from a strong Java background and hates loosely typed scripting languages. So, to tempt him into JS and keep him away from heavy abstractions like Objective-J, I wrote a little library that allows you to specify strongly typed properties (and since global variables are also properties of the window object, those as well) of various types (real JS types like Boolean , Number , String etc or even made up ones like Integer ) and constants (final properties in Java). It uses ES5 getters and setters to do that and falls back to regular, loosely typed properties in non-supporting browsers. Also, as a bonus, you get cross-browser Function.prototype.bind and Array.prototype.forEach and a robust type checking function: StronglyTyped.is(type, value) . Example: Strongly typed properties You define strongly typed properties by using the corresponding methods of the StronglyTyped object. For example, the following snippet defines a boolean property called “foo” on an object literal: var o = {}; StronglyTyped.boolean(o, ‘foo’, true); console.log(o.foo); // prints true o.foo = false; console.log(o.foo); // prints false o.foo = ‘bar’; // TypeError: foo must be of type Boolean. bar is not. Example: Constants You define constants by using the constant method of the StronglyTyped object. For example, the following snippet defines a global MAGIC_NUMBER constant: var o = {}; StronglyTyped.constant(window, ‘MAGIC_NUMBER’, 3.1415926535); console.log(MAGIC_NUMBER); // prints 3.1415926535 MAGIC_NUMBER = 4; console.log(MAGIC_NUMBER); // prints 3.1415926535 Please note that constants only become read-only after they first get a non-undefined value. For example: StronglyTyped.constant(window, ‘MAGIC_NUMBER’); console.log(MAGIC_NUMBER); // prints undefined MAGIC_NUMBER = undefined; console.log(MAGIC_NUMBER); // prints undefined MAGIC_NUMBER = 3.1415926535; console.log(MAGIC_NUMBER); // prints 3.1415926535 MAGIC_NUMBER = 4; console.log(MAGIC_NUMBER); // prints 3.1415926535 Supported types The property types currently supported by StronglyTyped are: Array Boolean Date Function Integer Number RegExp String null and undefined are valid in every type. NaN and Infinity values are accepted in both the Number and the Integer types. If you want to use a type that’s not among the above but either is native to the browser (for example Element ) or a global object, you can use the generic method StronglyTyped.property(type, object, property [, initialValue]) : var o = {}; StronglyTyped.property(‘Element’, o, ‘foo’, document.body); console.log(o.foo); // prints a representation of the element o.foo = document.head; console.log(o.foo); // prints a representation of the element o.foo = 5; // TypeError: foo must be of type Element. 5 is not. Browser support It should work on every browser that supports Object.defineProperty or __defineGetter__ and __defineSetter__ . As you can see from kangax’s awesome compatibility tables for Object.defineProperty and __define(G|S)etter__ , those are: Firefox 3.5+ IE8 (only on DOM elements) IE9+ Opera 10.5+ Chrome 5+ Safari 4+ Konqueror 4.4+ However, it’s only verified to work in: Firefox 4 (Win and OSX) IE9+ Opera 11.10 for OSX, Opera 11 for Windows Chrome (Win and OSX) Safari 5 (Win and OSX) This doesn’t mean it won’t work in the rest , just that it hasn’t been tested there (yet). You can load the unit tests (sort of…) in a browser you want to test and let me know about the results. :) Naice! Can I haz? As usual, you can get it from Github: Github repo Credits Thanks a lot to Max ( @suprMax ) for Windows testing!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>StronglyTyped: A library for strongly typed properties &amp; constants in JavaScript</title>
  <link>https://lea.verou.me/2011/05/strongly-typed-javascript/</link>
  <pubDate>Sun, 17 May 2026 01:05:12 +0200</pubDate>
  <description>Ill start by saying I love the loosely typed nature of JavaScript. When I had to work with strongly typed languages like Java, it always seemed like an unnecessary hassle. On the contrary, my boyfriend even though very proficient with HTML, CSS and SVG, comes from a strong Java background and hates loosely typed scripting languages. So, to tempt him into JS and keep him away from heavy abstractions like Objective-J, I wrote a little library that allows you to specify strongly typed properties (and since global variables are also properties of the window object, those as well) of various types (real JS types like Boolean , Number , String etc or even made up ones like Integer ) and constants (final properties in Java). It uses ES5 getters and setters to do that and falls back to regular, loosely typed properties in non-supporting browsers. Also, as a bonus, you get cross-browser Function.prototype.bind and Array.prototype.forEach and a robust type checking function: StronglyTyped.is(type, value) . Example: Strongly typed properties You define strongly typed properties by using the corresponding methods of the StronglyTyped object. For example, the following snippet defines a boolean property called foo on an object literal: var o = {}; StronglyTyped.boolean(o, foo, true); console.log(o.foo); // prints true o.foo = false; console.log(o.foo); // prints false o.foo = bar; // TypeError: foo must be of type Boolean. bar is not. Example: Constants You define constants by using the constant method of the StronglyTyped object. For example, the following snippet defines a global MAGIC_NUMBER constant: var o = {}; StronglyTyped.constant(window, MAGIC_NUMBER, 3.1415926535); console.log(MAGIC_NUMBER); // prints 3.1415926535 MAGIC_NUMBER = 4; console.log(MAGIC_NUMBER); // prints 3.1415926535 Please note that constants only become read-only after they first get a non-undefined value. For example: StronglyTyped.constant(window, MAGIC_NUMBER); console.log(MAGIC_NUMBER); // prints undefined MAGIC_NUMBER = undefined; console.log(MAGIC_NUMBER); // prints undefined MAGIC_NUMBER = 3.1415926535; console.log(MAGIC_NUMBER); // prints 3.1415926535 MAGIC_NUMBER = 4; console.log(MAGIC_NUMBER); // prints 3.1415926535 Supported types The property types currently supported by StronglyTyped are: Array Boolean Date Function Integer Number RegExp String null and undefined are valid in every type. NaN and Infinity values are accepted in both the Number and the Integer types. If you want to use a type thats not among the above but either is native to the browser (for example Element ) or a global object, you can use the generic method StronglyTyped.property(type, object, property [, initialValue]) : var o = {}; StronglyTyped.property(Element, o, foo, document.body); console.log(o.foo); // prints a representation of the element o.foo = document.head; console.log(o.foo); // prints a representation of the element o.foo = 5; // TypeError: foo must be of type Element. 5 is not. Browser support It should work on every browser that supports Object.defineProperty or __defineGetter__ and __defineSetter__ . As you can see from kangaxs awesome compatibility tables for Object.defineProperty and __define(G|S)etter__ , those are: Firefox 3.5+ IE8 (only on DOM elements) IE9+ Opera 10.5+ Chrome 5+ Safari 4+ Konqueror 4.4+ However, its only verified to work in: Firefox 4 (Win and OSX) IE9+ Opera 11.10 for OSX, Opera 11 for Windows Chrome (Win and OSX) Safari 5 (Win and OSX) This doesnt mean it wont work in the rest , just that it hasnt been tested there (yet). You can load the unit tests (sort of) in a browser you want to test and let me know about the results. :) Naice! Can I haz? As usual, you can get it from Github: Github repo Credits Thanks a lot to Max ( @suprMax ) for Windows testing!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Rule filtering based on specific selector(s) support</title>
  <link>https://lea.verou.me/2011/05/rule-filtering-based-on-specific-selectors-support/</link>
  <pubDate>Sun, 17 May 2026 01:05:11 +0200</pubDate>
  <description>I’ve been using this trick for quite a while, but I never thought to blog about it. However, I recently realized that it might not be as common as I thought, so it might be a good idea to document it in a blog post. If you follow the discussions on www-style, you might have noticed the proposal for a @supports rule to query property and value support. Some people suggested that it should also test for selectors, for example whether a certain pseudo-class is supported. However, you can do that today, albeit in a limited manner (no OR and NOT support). The main principle that you need to keep in mind is that browsers are expected to drop rules with selectors they don’t understand, even partially . So, if only one selector in a group cannot be parsed, the whole rule will be dropped. This means we can construct selector “tests”, which are use cases of the selector whose support we want to test, that will not match anything, even if the selector is supported. Then, we include that selector in the beginning of our selector group. If all this is unclear, don’t worry, as there’s an example coming next :) Example Suppose you want to apply the following CSS (for rudimentary custom checkboxes): input[type=“checkbox”] { position:absolute; clip: rect(0,0,0,0); clip: rect(0 0 0 0); } input[type=“checkbox”] + label::before { content: url(‘checkbox.png’); } input[type=“checkbox”]:checked + label::before { content: url(‘checkbox-checked.png’); } only in browsers that support the attribute equality selector, the :checked pseudo-class and the ::before pseudo-element. We need to try to think of a selector that includes all of them but matches nothing. One such selector would be #foo[type=&quot;checkbox&quot;]:checked::before . Even in supporting browsers, this matches nothing as there’s no element with id=“foo”. We can reduce the test for every rule to conserve bandwidth: For example, we don’t need to include tests for the attribute selector in any of them, since they are present anyway in all three rules. Also, we may eliminate ::before from the second test and we don’t need any test for the 3rd one, since it includes all features we want to test for. To sum up: #foo:checked::before, input[type=“checkbox”] { position:absolute; clip: rect(0,0,0,0); clip: rect(0 0 0 0); } #foo:checked, input[type=“checkbox”] + label::before { content: url(‘checkbox.png’); } input[type=“checkbox”]:checked + label::before { content: url(‘checkbox-checked.png’); } An important caveat of this technique is that Internet Explorer up to version 7 will split selectors before parsing them, so it will completely ignore our filters :( (Thanks to Ryan Seddon for finding that out). Disclaimer: The original idea about custom checkboxes belongs to Ryan Seddon , although his code was quite different.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Rule filtering based on specific selector(s) support</title>
  <link>https://lea.verou.me/2011/05/rule-filtering-based-on-specific-selectors-support/</link>
  <pubDate>Sun, 17 May 2026 01:05:11 +0200</pubDate>
  <description>Ive been using this trick for quite a while, but I never thought to blog about it. However, I recently realized that it might not be as common as I thought, so it might be a good idea to document it in a blog post. If you follow the discussions on www-style, you might have noticed the proposal for a @supports rule to query property and value support. Some people suggested that it should also test for selectors, for example whether a certain pseudo-class is supported. However, you can do that today, albeit in a limited manner (no OR and NOT support). The main principle that you need to keep in mind is that browsers are expected to drop rules with selectors they dont understand, even partially . So, if only one selector in a group cannot be parsed, the whole rule will be dropped. This means we can construct selector tests, which are use cases of the selector whose support we want to test, that will not match anything, even if the selector is supported. Then, we include that selector in the beginning of our selector group. If all this is unclear, dont worry, as theres an example coming next :) Example Suppose you want to apply the following CSS (for rudimentary custom checkboxes): input[type=checkbox] { position:absolute; clip: rect(0,0,0,0); clip: rect(0 0 0 0); } input[type=checkbox] + label::before { content: url(checkbox.png); } input[type=checkbox]:checked + label::before { content: url(checkbox-checked.png); } only in browsers that support the attribute equality selector, the :checked pseudo-class and the ::before pseudo-element. We need to try to think of a selector that includes all of them but matches nothing. One such selector would be #foo[type=&quot;checkbox&quot;]:checked::before . Even in supporting browsers, this matches nothing as theres no element with id=foo. We can reduce the test for every rule to conserve bandwidth: For example, we dont need to include tests for the attribute selector in any of them, since they are present anyway in all three rules. Also, we may eliminate ::before from the second test and we dont need any test for the 3rd one, since it includes all features we want to test for. To sum up: #foo:checked::before, input[type=checkbox] { position:absolute; clip: rect(0,0,0,0); clip: rect(0 0 0 0); } #foo:checked, input[type=checkbox] + label::before { content: url(checkbox.png); } input[type=checkbox]:checked + label::before { content: url(checkbox-checked.png); } An important caveat of this technique is that Internet Explorer up to version 7 will split selectors before parsing them, so it will completely ignore our filters :( (Thanks to Ryan Seddon for finding that out). Disclaimer: The original idea about custom checkboxes belongs to Ryan Seddon , although his code was quite different.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 patterns gallery and a new pattern</title>
  <link>https://lea.verou.me/2011/04/css3-patterns-gallery-and-a-new-pattern/</link>
  <pubDate>Sun, 17 May 2026 01:05:10 +0200</pubDate>
  <description>I finally got around to doing what I wanted to do for quite a few months: Create a gallery with all the basic patterns I was able to create with CSS3 gradients. Here it is: CSS3 Pattern Gallery Also, it includes a brand new pattern, which is the hardest one I have ever made so far: Japanese cubes. Thanks to David Storey for challenging me about it. Supported browsers: Firefox 4 (the patterns themselves work on 3.6 too but the gallery doesn’t due to a JS limitation) Opera 11.10 IE10 Google Chrome Webkit nightlies However bear in mind that every implementation has its limitations so a few of them won’t work in all the aforementioned browsers (for example Opera doesn’t support radial gradients and Firefox doesn’t support explicitly sized ones).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 patterns gallery and a new pattern</title>
  <link>https://lea.verou.me/2011/04/css3-patterns-gallery-and-a-new-pattern/</link>
  <pubDate>Sun, 17 May 2026 01:05:10 +0200</pubDate>
  <description>I finally got around to doing what I wanted to do for quite a few months: Create a gallery with all the basic patterns I was able to create with CSS3 gradients. Here it is: CSS3 Pattern Gallery Also, it includes a brand new pattern, which is the hardest one I have ever made so far: Japanese cubes. Thanks to David Storey for challenging me about it. Supported browsers: Firefox 4 (the patterns themselves work on 3.6 too but the gallery doesnt due to a JS limitation) Opera 11.10 IE10 Google Chrome Webkit nightlies However bear in mind that every implementation has its limitations so a few of them wont work in all the aforementioned browsers (for example Opera doesnt support radial gradients and Firefox doesnt support explicitly sized ones).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Invert a whole webpage with CSS only</title>
  <link>https://lea.verou.me/2011/04/invert-a-whole-webpage-with-css-only/</link>
  <pubDate>Sun, 17 May 2026 01:05:09 +0200</pubDate>
  <description>I recently saw Paul Irish’s jQuery invert page plugin . It inverts every color on a webpage including images or CSS. This reminded me of the invert color keyword that’s allowed on outlines (and sadly only supported by Opera and IE9+). So I wondered how it could be exploited to achieve the same effect through CSS alone. Turned out to be quite simple actually: body:before { content:“”; position:fixed; top:50%; left: 50%; z-index:9999; width:1px; height: 1px; outline:2999px solid invert; } Not even pointer-events:none; is needed, since outlines don’t receive pointer events anyway, and there’s no issue with scrollbars since they don’t contribute to scrolling. So this is not even CSS3, it’s just plain ol’ CSS 2.1. And here’s a bookmarklet to inject it into any given page: [Invert page](javascript:(function(){var%20style=document.createElement(‘style’);style.innerHTML=‘body:before%20{%20content:%22%22;%20position:fixed;%20top:50%25;%20left:50%25;%20z-index:9999;%20width:1px;%20height:%201px;%20outline:2999px%20solid%20invert;%20}’;document.body.appendChild(style)})();) **Note:**This will only work on Opera and IE9+ since they’re currently the only ones supporting the color keyword ‘invert’ on outlines. However, it’s probably possible to add Firefox support too with SVG filters, since they support them on HTML elements as well. As for why would someone want to invert a page… I guess it could be useful for people that can read white text on dark backgrounds more easily, April fools jokes, konami code fun and stuff like that. Update: Mozilla is planning to never support invert because there’s a loophole in the CSS 2.1 spec that allows them to do that. However, you can push them to support it by voting on the relevant issue .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Create complex RegExps more easily</title>
  <link>https://lea.verou.me/2011/03/create-complex-regexps-more-easily/</link>
  <pubDate>Sun, 17 May 2026 01:05:09 +0200</pubDate>
  <description>When I was writing my linear-gradient() to -webkit-gradient() converter , I knew in advance that I would have to use a quite large regular expression to validate and parse the input. Such a regex would be incredibly hard to read and fix potential issues, so I tried to find a way to cut the process down in reusable parts. Turns out JavaScript regular expression objects have a .source property that can be used in the RegExp constructor to create a new RegExp out of another one. So I wrote a new function that takes a string with identifiers for regexp replacements in and replaces them with the corresponding sub-regexps, taken from an object literal as a second argument: /** * Create complex regexps in an easy-to-read way * @param str {String} Final regex with for replacements * @param replacements {Object} Object with the replacements * @param flags {String} Just like the flags argument in the RegExp constructor */ RegExp.create = function(str, replacements, flags) { for(var id in replacements) { var replacement = replacements\[id\], idRegExp = RegExp(&#39; + id + &#39;, &#39;gi&#39;); if(replacement.source) { replacement = replacement.source.replace(/^\\^|\\$$/g, &#39;&#39;); } // Don&#39;t add extra parentheses if they already exist str = str.replace(RegExp(&#39;\\\\(&#39; + idRegExp.source + &#39;\\\\)&#39;, &#39;gi&#39;), &#39;(&#39; + replacement + &#39;)&#39;); str = str.replace(idRegExp, &#39;(?:&#39; + replacement + &#39;)&#39;); } return RegExp(str, flags); }; If you don’t like adding a function to the RegExp object, you can name it however you want. Here’s how I used it for my linear-gradient() parser: self.regex = {}; self.regex.number = /^-?\[0-9\]\*\\.?\[0-9\]+$/; self.regex.keyword = /^(?:top\\s+|bottom\\s+)?(?:right|left)|(?:right\\s+|left\\s+)?(?:top|bottom)$/; self.regex.direction = RegExp.create(&#39;^(?:|deg|0)$&#39;, { keyword: self.regex.keyword, number: self.regex.number }); self.regex.color = RegExp.create(&#39;(?:||)&#39;, { keyword: /^(?:red|tan|grey|gray|lime|navy|blue|teal|aqua|cyan|gold|peru|pink|plum|snow|\[a-z\]{5,20})$/, func: RegExp.create(&#39;^(?:rgb|hsl)a?\\\\((?:\\\\s\*%?\\\\s\*,?\\\\s\*){3,4}\\\\)$&#39;, { number: self.regex.number }), hex: /^#(?:\[0-9a-f\]{1,2}){3}$/ }); self.regex.percentage = RegExp.create(&#39;^(?:%|0)$&#39;, { number: self.regex.number }); self.regex.length = RegExp.create(&#39;|0&#39;, { number: self.regex.number, unit: /%|px|mm|cm|in|em|rem|en|ex|ch|vm|vw|vh/ }); self.regex.colorStop = RegExp.create(&#39;\\\\s\*?&#39;, { color: self.regex.color, length: self.regex.length }, &#39;g&#39;); self.regex.linearGradient = RegExp.create(&#39;^linear-gradient\\\\(\\\\s\*(?:()\\\\s\*,)?\\\\s\*(\\\\s\*(?:,\\\\s\*\\\\s\*)+)\\\\)$&#39;, { direction: self.regex.direction, colorStop: self.regex.colorStop }, &#39;i&#39;); (self in this case was a local variable, not the window object)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Invert a whole webpage with CSS only</title>
  <link>https://lea.verou.me/2011/04/invert-a-whole-webpage-with-css-only/</link>
  <pubDate>Sun, 17 May 2026 01:05:09 +0200</pubDate>
  <description>I recently saw Paul Irishs jQuery invert page plugin . It inverts every color on a webpage including images or CSS. This reminded me of the invert color keyword thats allowed on outlines (and sadly only supported by Opera and IE9+). So I wondered how it could be exploited to achieve the same effect through CSS alone. Turned out to be quite simple actually: body:before { content:; position:fixed; top:50%; left: 50%; z-index:9999; width:1px; height: 1px; outline:2999px solid invert; } Not even pointer-events:none; is needed, since outlines dont receive pointer events anyway, and theres no issue with scrollbars since they dont contribute to scrolling. So this is not even CSS3, its just plain ol CSS 2.1. And heres a bookmarklet to inject it into any given page: [Invert page](javascript:(function(){var%20style=document.createElement(style);style.innerHTML=body:before%20{%20content:%22%22;%20position:fixed;%20top:50%25;%20left:50%25;%20z-index:9999;%20width:1px;%20height:%201px;%20outline:2999px%20solid%20invert;%20};document.body.appendChild(style)})();) **Note:**This will only work on Opera and IE9+ since theyre currently the only ones supporting the color keyword invert on outlines. However, its probably possible to add Firefox support too with SVG filters, since they support them on HTML elements as well. As for why would someone want to invert a page I guess it could be useful for people that can read white text on dark backgrounds more easily, April fools jokes, konami code fun and stuff like that. Update: Mozilla is planning to never support invert because theres a loophole in the CSS 2.1 spec that allows them to do that. However, you can push them to support it by voting on the relevant issue .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Create complex RegExps more easily</title>
  <link>https://lea.verou.me/2011/03/create-complex-regexps-more-easily/</link>
  <pubDate>Sun, 17 May 2026 01:05:09 +0200</pubDate>
  <description>When I was writing my linear-gradient() to -webkit-gradient() converter , I knew in advance that I would have to use a quite large regular expression to validate and parse the input. Such a regex would be incredibly hard to read and fix potential issues, so I tried to find a way to cut the process down in reusable parts. Turns out JavaScript regular expression objects have a .source property that can be used in the RegExp constructor to create a new RegExp out of another one. So I wrote a new function that takes a string with identifiers for regexp replacements in and replaces them with the corresponding sub-regexps, taken from an object literal as a second argument: /** * Create complex regexps in an easy-to-read way * @param str {String} Final regex with for replacements * @param replacements {Object} Object with the replacements * @param flags {String} Just like the flags argument in the RegExp constructor */ RegExp.create = function(str, replacements, flags) { for(var id in replacements) { var replacement = replacements\[id\], idRegExp = RegExp(&#39; + id + &#39;, &#39;gi&#39;); if(replacement.source) { replacement = replacement.source.replace(/^\\^|\\$$/g, &#39;&#39;); } // Don&#39;t add extra parentheses if they already exist str = str.replace(RegExp(&#39;\\\\(&#39; + idRegExp.source + &#39;\\\\)&#39;, &#39;gi&#39;), &#39;(&#39; + replacement + &#39;)&#39;); str = str.replace(idRegExp, &#39;(?:&#39; + replacement + &#39;)&#39;); } return RegExp(str, flags); }; If you dont like adding a function to the RegExp object, you can name it however you want. Heres how I used it for my linear-gradient() parser: self.regex = {}; self.regex.number = /^-?\[0-9\]\*\\.?\[0-9\]+$/; self.regex.keyword = /^(?:top\\s+|bottom\\s+)?(?:right|left)|(?:right\\s+|left\\s+)?(?:top|bottom)$/; self.regex.direction = RegExp.create(&#39;^(?:|deg|0)$&#39;, { keyword: self.regex.keyword, number: self.regex.number }); self.regex.color = RegExp.create(&#39;(?:||)&#39;, { keyword: /^(?:red|tan|grey|gray|lime|navy|blue|teal|aqua|cyan|gold|peru|pink|plum|snow|\[a-z\]{5,20})$/, func: RegExp.create(&#39;^(?:rgb|hsl)a?\\\\((?:\\\\s\*%?\\\\s\*,?\\\\s\*){3,4}\\\\)$&#39;, { number: self.regex.number }), hex: /^#(?:\[0-9a-f\]{1,2}){3}$/ }); self.regex.percentage = RegExp.create(&#39;^(?:%|0)$&#39;, { number: self.regex.number }); self.regex.length = RegExp.create(&#39;|0&#39;, { number: self.regex.number, unit: /%|px|mm|cm|in|em|rem|en|ex|ch|vm|vw|vh/ }); self.regex.colorStop = RegExp.create(&#39;\\\\s\*?&#39;, { color: self.regex.color, length: self.regex.length }, &#39;g&#39;); self.regex.linearGradient = RegExp.create(&#39;^linear-gradient\\\\(\\\\s\*(?:()\\\\s\*,)?\\\\s\*(\\\\s\*(?:,\\\\s\*\\\\s\*)+)\\\\)$&#39;, { direction: self.regex.direction, colorStop: self.regex.colorStop }, &#39;i&#39;); (self in this case was a local variable, not the window object)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Convert standard gradient syntax to -webkit-gradient and others</title>
  <link>https://lea.verou.me/2011/03/convert-standard-gradient-syntax-to-webkit-gradient-and-others/</link>
  <pubDate>Sun, 17 May 2026 01:05:08 +0200</pubDate>
  <description>I hate -webkit-gradient() with a passion. Its syntax is cumbersome and it’s really limited: No angle support, no s in color stop positions, no implied color stop positions, no elliptical gradients… So, I was really happy, when Webkit implemented the standard syntax this January. However, we’re still stuck with the horrid -webkit-gradient() for quite a while, since older Webkit browsers that don’t support it are widely used at this time. Today, I decided to finally spare myself the hassle of converting my standard gradient syntax to -webkit-gradient() by hand. Tasks like that shouldn’t be handled by a human. So, I coded a little script to do the chore. Hope it helps you too: View demo It currently only supports linear gradients, but I plan to add radial ones in the future. Also, when I get around to cleaning up the code a bit, I’ll add it on Github. (Hope I didn’t leave in any very stupid bug, it’s really late here and I’m half asleep.)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Convert standard gradient syntax to -webkit-gradient and others</title>
  <link>https://lea.verou.me/2011/03/convert-standard-gradient-syntax-to-webkit-gradient-and-others/</link>
  <pubDate>Sun, 17 May 2026 01:05:08 +0200</pubDate>
  <description>I hate -webkit-gradient() with a passion. Its syntax is cumbersome and its really limited: No angle support, no s in color stop positions, no implied color stop positions, no elliptical gradients So, I was really happy, when Webkit implemented the standard syntax this January. However, were still stuck with the horrid -webkit-gradient() for quite a while, since older Webkit browsers that dont support it are widely used at this time. Today, I decided to finally spare myself the hassle of converting my standard gradient syntax to -webkit-gradient() by hand. Tasks like that shouldnt be handled by a human. So, I coded a little script to do the chore. Hope it helps you too: View demo It currently only supports linear gradients, but I plan to add radial ones in the future. Also, when I get around to cleaning up the code a bit, Ill add it on Github. (Hope I didnt leave in any very stupid bug, its really late here and Im half asleep.)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Beveled corners &amp; negative border-radius with CSS3 gradients</title>
  <link>https://lea.verou.me/2011/03/beveled-corners-negative-border-radius-with-css3-gradients/</link>
  <pubDate>Sun, 17 May 2026 01:05:06 +0200</pubDate>
  <description>Just found out how to do beveled corners and simulate negative border radius without images, by utilizing CSS gradients once again. It’s amazing how many CSS problems can be solved with gradients alone. Read the text in the dabblet below to find out how (or just check the code): It also falls back to a solid color background if CSS gradients are not supported. It will work on Firefox 3.6+, Chrome, Safari, Opera 11.10+ and IE10+. PS: For my twitter friends, I had already written this when the robbers came and I was about to post it. I might have been really calm, but not as much as making CSS experiments the same day I was robbed and threatened by a gun :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Beveled corners &amp; negative border-radius with CSS3 gradients</title>
  <link>https://lea.verou.me/2011/03/beveled-corners-negative-border-radius-with-css3-gradients/</link>
  <pubDate>Sun, 17 May 2026 01:05:06 +0200</pubDate>
  <description>Just found out how to do beveled corners and simulate negative border radius without images, by utilizing CSS gradients once again. Its amazing how many CSS problems can be solved with gradients alone. Read the text in the dabblet below to find out how (or just check the code): It also falls back to a solid color background if CSS gradients are not supported. It will work on Firefox 3.6+, Chrome, Safari, Opera 11.10+ and IE10+. PS: For my twitter friends, I had already written this when the robbers came and I was about to post it. I might have been really calm, but not as much as making CSS experiments the same day I was robbed and threatened by a gun :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On CSS preprocessors</title>
  <link>https://lea.verou.me/2011/03/on-css-preprocessors/</link>
  <pubDate>Sun, 17 May 2026 01:05:05 +0200</pubDate>
  <description>Lately there has been a rise in the usage of CSS preprocessors such as LESS and SASS , which makes sense given the simultaneous increase of CSS3 usage. I’ve frequently argued with fellow front-end web developers about whether they should be used or not and I decided to finally put my thoughts in writing. To start, I can fully understand the advantage of using such preprocessors over vanilla CSS3. I hate listing all the vendor prefixes, and not being able to use variables, mixins or nesting just like the next web developer. All this syntactic sugar can simplify your workflow by a great deal and make writing CSS3 incredibly fun. However, I still refrain from using them, and I’ll explain why below. Losing track of CSS filesize When I’m writing CSS, I try to keep the filesize as small as possible. I’m not a filesize hypochondriac, I try to balance filesize and readability and I prefer to err on the side of the latter. I’m not one of those people that will use #000 instead of black just to save a byte and I use lots of indents and newlines (later minification takes care of that). However, in cases when the readability impact is small and the filesize impact is large (and minification won’t help), I will do the optimization. For example, consider the following case: Let’s suppose you have 3 rules ( #foo , #bar and #baz ) that will both use the same CSS rotate transformation, among other CSS declarations. Using a mixin is simple (using the LESS syntax in this example): .rotate (@degrees: 10deg) { -moz-transform: rotate(@degrees); -ms-transform: rotate(@degrees); -o-transform: rotate(@degrees); -webkit-transform: rotate(@degrees); transform: rotate(@degrees); } #foo { font-size: 150%; .rotate(40deg); } #bar { background: silver; .rotate(40deg); } #baz { background: white; .rotate(40deg); } Sweet, huh? And only 370 bytes. However, what the end user downloads is this beast: #foo { font-size: 150%; -moz-transform: rotate(40deg); -ms-transform: rotate(40deg); -o-transform: rotate(40deg); -webkit-transform: rotate(40deg); transform: rotate(40deg); } #bar { background: silver; -moz-transform: rotate(40deg); -ms-transform: rotate(40deg); -o-transform: rotate(40deg); -webkit-transform: rotate(40deg); transform: rotate(40deg); } #baz { background: white; -moz-transform: rotate(40deg); -ms-transform: rotate(40deg); -o-transform: rotate(40deg); -webkit-transform: rotate(40deg); transform: rotate(40deg); } which is almost double the filesize (600 bytes). It could have easily been this: #foo, #bar, #baz { -moz-transform: rotate(40deg); -ms-transform: rotate(40deg); -o-transform: rotate(40deg); -webkit-transform: rotate(40deg); transform: rotate(40deg); } #foo { font-size: 150%; } #bar { background: silver; } #baz { background: white; } which at 290 bytes, is even smaller than the first one. The differences would be even bigger if you had to specify a different transform-origin. Of course you can still do such optimizations when using CSS preprocessors, but since you don’t have the ugliness in front of you and the file you’re working with remains small, it’s easy to forget and just do what’s easy. You lose sight of the big picture. But it’s the big picture (or big file, in this case ;)) that your users eventually download. Same goes for nesting: Instead of actually putting some thought into the selectors you choose, you can just nest and let the preprocessor sort it out, usually in the straightforward but unavoidably verbose way. LESS is better in this aspect, since it also offers a client-side version, so the user downloads the small file you wrote, and all the expansion is done in their machine. However, this has the (big, IMO) disadvantage that all your CSS becomes dependent on JavaScript to work and that your users have to download the LESS code, which isn’t that small: 33KB minified which is way larger than most stylesheets (granted, if you gzip, it will be smaller, but this is true for stylesheets as well). Maintenance woes Eventually, CSS will start supporting all this sweetness. Tab Atkins has already drafted a proposal and soon Webkit nightlies will implement the functionality. After that, I think it’s safe to assume that within 2 years Firefox and Opera will also implement the (by then) standard and within 1-2 more even IE. Then we’ll need another 2-3 years to be able to start using it (adoption rates of new browser versions will have increased too). This means that in as little as 6 years, we might be able to use CSS variables, mixins and nesting in vanilla CSS. All the code written for today’s preprocessors will eventually have to be rewritten. Maybe even sooner, since when a standard is published, I think it’s safe to assume (or hope) that the new versions of CSS preprocessors will deprecate their old syntax and start supporting and recommending the standard way, effectively becoming polyfills (which I definitely support). So, coding for a CSS preprocessor today feels a bit like building castles on sand. Debugging woes (thanks to Jesper Ek) Preprocessors make debugging CSS harder, since the CSS you see in Web Inspectors like Firebug or Dragonfly is not the CSS you wrote. The line numbers don’t match any more and the CSS itself is different. A lighter form of the same problem also occurs with minifiers, but you can delay using them until you’re done with the site. With CSS preprocessors, you have to use them from the beginning if you want to really take advantage of them. Also, when I develop my CSS, I want to be able to instantly preview the changes in the file by just refreshing the browser. With preprocessors this becomes harder (although not impossible). Generic concerns with such abstractions With every new syntax, comes more effort required by someone to start working on our code. We either have to only collaborate with people proficient in the CSS preprocessor of our choice, or teach them its syntax. So we are either restricted in our choice of collaborators or need to spend extra time for training, both of which are nuisances. Also, what happens if the preprocessor stops being updated? Granted, most (if not all) are open source, but the community’s interest might shift to something else. Many open source projects have eventually died due to lack of interest. And let’s not forget the law of leaky abstractions … Yes, both concerns are valid for every framework, in every language, but at least PHP frameworks or JavaScript libraries are more needed than CSS preprocessors, so it’s a tradeoff is that’s worth it. For CSS preprocessors, I’m not so sure. Conclusion &amp; disclaimer I have to admit that even though I’ve read quite a bit on CSS preprocessors and talked with fellow web developers about them, I don’t have hands-on experience with them. Maybe I will change my mind if I actually do so. Besides, I think that if someone uses a CSS preprocessor carefully, with knowledge of the points mentioned above, it can actually turn out to be beneficial. However personally, I prefer to wait at least until they start supporting the (future) standard syntax, whenever that happens.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On CSS preprocessors</title>
  <link>https://lea.verou.me/2011/03/on-css-preprocessors/</link>
  <pubDate>Sun, 17 May 2026 01:05:05 +0200</pubDate>
  <description>Lately there has been a rise in the usage of CSS preprocessors such as LESS and SASS , which makes sense given the simultaneous increase of CSS3 usage. Ive frequently argued with fellow front-end web developers about whether they should be used or not and I decided to finally put my thoughts in writing. To start, I can fully understand the advantage of using such preprocessors over vanilla CSS3. I hate listing all the vendor prefixes, and not being able to use variables, mixins or nesting just like the next web developer. All this syntactic sugar can simplify your workflow by a great deal and make writing CSS3 incredibly fun. However, I still refrain from using them, and Ill explain why below. Losing track of CSS filesize When Im writing CSS, I try to keep the filesize as small as possible. Im not a filesize hypochondriac, I try to balance filesize and readability and I prefer to err on the side of the latter. Im not one of those people that will use #000 instead of black just to save a byte and I use lots of indents and newlines (later minification takes care of that). However, in cases when the readability impact is small and the filesize impact is large (and minification wont help), I will do the optimization. For example, consider the following case: Lets suppose you have 3 rules ( #foo , #bar and #baz ) that will both use the same CSS rotate transformation, among other CSS declarations. Using a mixin is simple (using the LESS syntax in this example): .rotate (@degrees: 10deg) { -moz-transform: rotate(@degrees); -ms-transform: rotate(@degrees); -o-transform: rotate(@degrees); -webkit-transform: rotate(@degrees); transform: rotate(@degrees); } #foo { font-size: 150%; .rotate(40deg); } #bar { background: silver; .rotate(40deg); } #baz { background: white; .rotate(40deg); } Sweet, huh? And only 370 bytes. However, what the end user downloads is this beast: #foo { font-size: 150%; -moz-transform: rotate(40deg); -ms-transform: rotate(40deg); -o-transform: rotate(40deg); -webkit-transform: rotate(40deg); transform: rotate(40deg); } #bar { background: silver; -moz-transform: rotate(40deg); -ms-transform: rotate(40deg); -o-transform: rotate(40deg); -webkit-transform: rotate(40deg); transform: rotate(40deg); } #baz { background: white; -moz-transform: rotate(40deg); -ms-transform: rotate(40deg); -o-transform: rotate(40deg); -webkit-transform: rotate(40deg); transform: rotate(40deg); } which is almost double the filesize (600 bytes). It could have easily been this: #foo, #bar, #baz { -moz-transform: rotate(40deg); -ms-transform: rotate(40deg); -o-transform: rotate(40deg); -webkit-transform: rotate(40deg); transform: rotate(40deg); } #foo { font-size: 150%; } #bar { background: silver; } #baz { background: white; } which at 290 bytes, is even smaller than the first one. The differences would be even bigger if you had to specify a different transform-origin. Of course you can still do such optimizations when using CSS preprocessors, but since you dont have the ugliness in front of you and the file youre working with remains small, its easy to forget and just do whats easy. You lose sight of the big picture. But its the big picture (or big file, in this case ;)) that your users eventually download. Same goes for nesting: Instead of actually putting some thought into the selectors you choose, you can just nest and let the preprocessor sort it out, usually in the straightforward but unavoidably verbose way. LESS is better in this aspect, since it also offers a client-side version, so the user downloads the small file you wrote, and all the expansion is done in their machine. However, this has the (big, IMO) disadvantage that all your CSS becomes dependent on JavaScript to work and that your users have to download the LESS code, which isnt that small: 33KB minified which is way larger than most stylesheets (granted, if you gzip, it will be smaller, but this is true for stylesheets as well). Maintenance woes Eventually, CSS will start supporting all this sweetness. Tab Atkins has already drafted a proposal and soon Webkit nightlies will implement the functionality. After that, I think its safe to assume that within 2 years Firefox and Opera will also implement the (by then) standard and within 1-2 more even IE. Then well need another 2-3 years to be able to start using it (adoption rates of new browser versions will have increased too). This means that in as little as 6 years, we might be able to use CSS variables, mixins and nesting in vanilla CSS. All the code written for todays preprocessors will eventually have to be rewritten. Maybe even sooner, since when a standard is published, I think its safe to assume (or hope) that the new versions of CSS preprocessors will deprecate their old syntax and start supporting and recommending the standard way, effectively becoming polyfills (which I definitely support). So, coding for a CSS preprocessor today feels a bit like building castles on sand. Debugging woes (thanks to Jesper Ek) Preprocessors make debugging CSS harder, since the CSS you see in Web Inspectors like Firebug or Dragonfly is not the CSS you wrote. The line numbers dont match any more and the CSS itself is different. A lighter form of the same problem also occurs with minifiers, but you can delay using them until youre done with the site. With CSS preprocessors, you have to use them from the beginning if you want to really take advantage of them. Also, when I develop my CSS, I want to be able to instantly preview the changes in the file by just refreshing the browser. With preprocessors this becomes harder (although not impossible). Generic concerns with such abstractions With every new syntax, comes more effort required by someone to start working on our code. We either have to only collaborate with people proficient in the CSS preprocessor of our choice, or teach them its syntax. So we are either restricted in our choice of collaborators or need to spend extra time for training, both of which are nuisances. Also, what happens if the preprocessor stops being updated? Granted, most (if not all) are open source, but the communitys interest might shift to something else. Many open source projects have eventually died due to lack of interest. And lets not forget the law of leaky abstractions Yes, both concerns are valid for every framework, in every language, but at least PHP frameworks or JavaScript libraries are more needed than CSS preprocessors, so its a tradeoff is thats worth it. For CSS preprocessors, Im not so sure. Conclusion &amp; disclaimer I have to admit that even though Ive read quite a bit on CSS preprocessors and talked with fellow web developers about them, I dont have hands-on experience with them. Maybe I will change my mind if I actually do so. Besides, I think that if someone uses a CSS preprocessor carefully, with knowledge of the points mentioned above, it can actually turn out to be beneficial. However personally, I prefer to wait at least until they start supporting the (future) standard syntax, whenever that happens.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>WD @media talk subject change</title>
  <link>https://lea.verou.me/2011/03/wd-media-talk-subject-change/</link>
  <pubDate>Sun, 17 May 2026 01:05:03 +0200</pubDate>
  <description>I recently changed my Web Directions @media talk title &amp; abstract to something more specialized. Instead of discussing under-hyped CSS3 features in general I will only focus on one CSS3 feature (more hyped than the ones I was planning to show, but all the hype is only about very basic use cases): CSS3 Gradients: Mastering CSS3 Gradients With most browsers adding increasing support, and the simplicity of providing fallbacks for those that don’t, CSS3 gradients are something we can start to use right now. They benefit our users with faster websites and ourselves with more time in our hands to spend in other things, since they are easy to create, edit and update. A very powerful feature that can also be utilized for a surprising number of design effects, even ones that don’t resemble gradients at all. In this talk, Lea will explore CSS3 gradients in great depth and it’s almost guaranteed that no matter your expertise level, you will walk out having learned new things. I tested a draft of this talk with a meetup group in Oslo ( Framsia ) and it went very well. I got reviews like “I was amazed that you managed to speak almost an hour of CSS3 gradients and still keep the crowd interested” (thanks Legendre!). Even Bruce Lawson , who happened to be there, told me he didn’t know like 70% of the material presented! :) I’m looking forward to it since it’s a topic I’m passionate about, and I hope to see you there! Don’t forget that you can use the coupon code WDVEROU when registering to take £50 off the current price. PS: I don’t like the title very much, so if you have anything more witty to suggest, feel free. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>WD @media talk subject change</title>
  <link>https://lea.verou.me/2011/03/wd-media-talk-subject-change/</link>
  <pubDate>Sun, 17 May 2026 01:05:03 +0200</pubDate>
  <description>I recently changed my Web Directions @media talk title &amp; abstract to something more specialized. Instead of discussing under-hyped CSS3 features in general I will only focus on one CSS3 feature (more hyped than the ones I was planning to show, but all the hype is only about very basic use cases): CSS3 Gradients: Mastering CSS3 Gradients With most browsers adding increasing support, and the simplicity of providing fallbacks for those that dont, CSS3 gradients are something we can start to use right now. They benefit our users with faster websites and ourselves with more time in our hands to spend in other things, since they are easy to create, edit and update. A very powerful feature that can also be utilized for a surprising number of design effects, even ones that dont resemble gradients at all. In this talk, Lea will explore CSS3 gradients in great depth and its almost guaranteed that no matter your expertise level, you will walk out having learned new things. I tested a draft of this talk with a meetup group in Oslo ( Framsia ) and it went very well. I got reviews like I was amazed that you managed to speak almost an hour of CSS3 gradients and still keep the crowd interested (thanks Legendre!). Even Bruce Lawson , who happened to be there, told me he didnt know like 70% of the material presented! :) Im looking forward to it since its a topic Im passionate about, and I hope to see you there! Dont forget that you can use the coupon code WDVEROU when registering to take 50 off the current price. PS: I dont like the title very much, so if you have anything more witty to suggest, feel free. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Custom drop downs with CSS3</title>
  <link>https://lea.verou.me/2011/03/custom-select-drop-downs-with-css3/</link>
  <pubDate>Sun, 17 May 2026 00:45:20 +0200</pubDate>
  <description>The CSS3 Basic UI module defines pointer-events as: The pointer-events property allows authors to control whether or when an element may be the target of user pointing device (pointer, e.g. mouse) events. This property is used to specify under which circumstance (if any) a pointer event should go “through” an element and target whatever is “underneath” that element instead. This also applies to other “hit testing” behaviors such as dynamic pseudo-classes (:hover, :active, :focus), hyperlinks, and Document.elementFromPoint(). The property was originally SVG-only, but eventually browsers and the W3C adopted a more limited version for HTML elements too. It can be used in many use cases that weren’t possible before (or the solution was overly complicated), one of them being to create custom-looking drop downs, by overlaying an element over the native drop down arrow (to create the custom one) and disallowing pointer events on it. Here’s a quick example: -webkit-appearance: none was needed in Webkit to turn off the native OSX appearance (in OSX and maybe Safari on Windows, I didn’t test that). However, since that also removes the native drop down arrow, our custom arrow now obscures part of the text, so we had to add a 30px padding-right to the select element, only in Webkit. You can easily detect if pointer-events is supported via JS and only apply this it if it is (eg by adding or removing a class from the body element): if(!(‘pointerEvents’ in document.body.style)) { … } However, there is one caveat in this: Opera does include pointerEvents in HTML elements as well, but it does not actually support the property on HTML. There’s a more elaborate feature detection script here as a Modernizr plugin (but the code is quite short, so you can adapt it to your needs). Also, don’t try to replicate the behavior in JavaScript for browsers that don’t support this: it’s impossible to open a drop down with JavaScript. Or, to put it differently, if you manage to do it, you’ll probably be the first to. Everything I could think of failed and I spent hours yesterday searching for a way, but no avail. References W3C specification MDN article</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Custom drop downs with CSS3</title>
  <link>https://lea.verou.me/2011/03/custom-select-drop-downs-with-css3/</link>
  <pubDate>Sun, 17 May 2026 00:45:20 +0200</pubDate>
  <description>The CSS3 Basic UI module defines pointer-events as: The pointer-events property allows authors to control whether or when an element may be the target of user pointing device (pointer, e.g. mouse) events. This property is used to specify under which circumstance (if any) a pointer event should go through an element and target whatever is underneath that element instead. This also applies to other hit testing behaviors such as dynamic pseudo-classes (:hover, :active, :focus), hyperlinks, and Document.elementFromPoint(). The property was originally SVG-only, but eventually browsers and the W3C adopted a more limited version for HTML elements too. It can be used in many use cases that werent possible before (or the solution was overly complicated), one of them being to create custom-looking drop downs, by overlaying an element over the native drop down arrow (to create the custom one) and disallowing pointer events on it. Heres a quick example: -webkit-appearance: none was needed in Webkit to turn off the native OSX appearance (in OSX and maybe Safari on Windows, I didnt test that). However, since that also removes the native drop down arrow, our custom arrow now obscures part of the text, so we had to add a 30px padding-right to the select element, only in Webkit. You can easily detect if pointer-events is supported via JS and only apply this it if it is (eg by adding or removing a class from the body element): if(!(pointerEvents in document.body.style)) { } However, there is one caveat in this: Opera does include pointerEvents in HTML elements as well, but it does not actually support the property on HTML. Theres a more elaborate feature detection script here as a Modernizr plugin (but the code is quite short, so you can adapt it to your needs). Also, dont try to replicate the behavior in JavaScript for browsers that dont support this: its impossible to open a drop down with JavaScript. Or, to put it differently, if you manage to do it, youll probably be the first to. Everything I could think of failed and I spent hours yesterday searching for a way, but no avail. References W3C specification MDN article</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Checkerboard pattern with CSS3</title>
  <link>https://lea.verou.me/2011/02/checkerboard-pattern-with-css3/</link>
  <pubDate>Sun, 17 May 2026 00:45:19 +0200</pubDate>
  <description>A while ago, I wrote a post on creating simple patterns with CSS3 gradients . A common pattern I was unable to create was that of a regular, non-rotated checkerboard. However, I noticed today that by giving a different background-position to every triangle in the pattern tile, a checkerboard can be easily created: View in Gecko or Webkit. Webkit seems to have an odd rendering bug, so it needed a background-size override and it still doesn’t look perfect. Oh well, reported the bug and moved on.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Checkerboard pattern with CSS3</title>
  <link>https://lea.verou.me/2011/02/checkerboard-pattern-with-css3/</link>
  <pubDate>Sun, 17 May 2026 00:45:19 +0200</pubDate>
  <description>A while ago, I wrote a post on creating simple patterns with CSS3 gradients . A common pattern I was unable to create was that of a regular, non-rotated checkerboard. However, I noticed today that by giving a different background-position to every triangle in the pattern tile, a checkerboard can be easily created: View in Gecko or Webkit. Webkit seems to have an odd rendering bug, so it needed a background-size override and it still doesnt look perfect. Oh well, reported the bug and moved on.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Incrementable length values in text fields</title>
  <link>https://lea.verou.me/2011/02/incrementable-length-values-in-text-fields/</link>
  <pubDate>Sun, 17 May 2026 00:45:18 +0200</pubDate>
  <description>I always loved that Firebug and Dragonfly feature that allows you to increment or decrement a value by pressing the up and down keyboard arrows when the caret is over it. I wished my Front Trends slides supported it in the editable examples, it would make presenting so much easier. So, I decided to implement the functionality, to use it in my next talk. If you still have no idea what I’m talking about, you can see a demo here: View demo You may configure it so that it only does that when modifiers (alt, ctrl and/or shift) are used by providing a second argument to the constructor and/or change the units supported by filling in the third argument. However, bear in mind that holding down the Shift key will make it increment by ±10 instead of ±1 and that’s not configurable (it would add too much unneeded complexity, I’m not even sure whether it’s a good idea to make the other thing configurable either). You may download it or fork it from it’s Github repo . And if you feel creative, you may improve it by fixing an Opera bug I gave up on: When the down arrow is pressed, the caret moves to the end of the string, despite the code telling it not to.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Convert PHP serialized data to Unicode</title>
  <link>https://lea.verou.me/2011/02/convert-php-serialized-data-to-unicode/</link>
  <pubDate>Sun, 17 May 2026 00:45:18 +0200</pubDate>
  <description>I recently had to convert a database of a large Greek website from single-byte Greek to Unicode (UTF-8). One of the problems I faced was the stored PHP serialized data: As PHP stores the length of the data (in bytes) inside the serialized string, the stored serialized strings could not be unserialized after the conversion. I didn’t want anyone to go through the frustration I went through while searching for a solution, so here is a little function I wrote to recount the string lengths, since I couldn’t find anything on this: function recount_serialized_bytes($text) { mb_internal_encoding(&quot;UTF-8&quot;); mb_regex_encoding(&quot;UTF-8&quot;); mb_ereg_search_init($text, &#39;s:[0-9]+:&quot;&#39;); $offset = 0; while(preg_match(&#39;/s:([0-9]+):&quot;/u&#39;, $text, $matches, PREG_OFFSET_CAPTURE, $offset) || preg_match(&#39;/s:([0-9]+):&quot;/u&#39;, $text, $matches, PREG_OFFSET_CAPTURE, ++$offset)) { $number = $matches[1][0]; $pos = $matches[1][1]; $digits = strlen(&quot;$number&quot;); $pos_chars = mb_strlen(substr($text, 0, $pos)) + 2 + $digits; $str = mb_substr($text, $pos_chars, $number); $new_number = strlen($str); $new_digits = strlen($new_number); if($number != $new_number) { // Change stored number $text = substr_replace($text, $new_number, $pos, $digits); $pos += $new_digits - $digits; } $offset = $pos + 2 + $new_number; } return $text; } My initial approach was to do it with regular expressions, but the PHP serialized data format is not a regular language and cannot be properly parsed with regular expressions. All approaches fail on edge cases, and I had lots of edge cases in my data (I even had nested serialized strings!). Note that this will only work when converting from single-byte encoded data , since it assumes the stored lengths are the string lengths in characters. Admittedly, it’s not my best code, it could be optimized in many ways. It was something I had to write quickly and was only going to be used by me in a one-time conversion process. However, it works smoothly and has been tested with lots of different serialized data. I know that not many people will find it useful, but it’s going to be a lifesaver for the few ones that need it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Incrementable length values in text fields</title>
  <link>https://lea.verou.me/2011/02/incrementable-length-values-in-text-fields/</link>
  <pubDate>Sun, 17 May 2026 00:45:18 +0200</pubDate>
  <description>I always loved that Firebug and Dragonfly feature that allows you to increment or decrement a value by pressing the up and down keyboard arrows when the caret is over it. I wished my Front Trends slides supported it in the editable examples, it would make presenting so much easier. So, I decided to implement the functionality, to use it in my next talk. If you still have no idea what Im talking about, you can see a demo here: View demo You may configure it so that it only does that when modifiers (alt, ctrl and/or shift) are used by providing a second argument to the constructor and/or change the units supported by filling in the third argument. However, bear in mind that holding down the Shift key will make it increment by 10 instead of 1 and thats not configurable (it would add too much unneeded complexity, Im not even sure whether its a good idea to make the other thing configurable either). You may download it or fork it from its Github repo . And if you feel creative, you may improve it by fixing an Opera bug I gave up on: When the down arrow is pressed, the caret moves to the end of the string, despite the code telling it not to.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Convert PHP serialized data to Unicode</title>
  <link>https://lea.verou.me/2011/02/convert-php-serialized-data-to-unicode/</link>
  <pubDate>Sun, 17 May 2026 00:45:18 +0200</pubDate>
  <description>I recently had to convert a database of a large Greek website from single-byte Greek to Unicode (UTF-8). One of the problems I faced was the stored PHP serialized data: As PHP stores the length of the data (in bytes) inside the serialized string, the stored serialized strings could not be unserialized after the conversion. I didnt want anyone to go through the frustration I went through while searching for a solution, so here is a little function I wrote to recount the string lengths, since I couldnt find anything on this: function recount_serialized_bytes($text) { mb_internal_encoding(&quot;UTF-8&quot;); mb_regex_encoding(&quot;UTF-8&quot;); mb_ereg_search_init($text, &#39;s:[0-9]+:&quot;&#39;); $offset = 0; while(preg_match(&#39;/s:([0-9]+):&quot;/u&#39;, $text, $matches, PREG_OFFSET_CAPTURE, $offset) || preg_match(&#39;/s:([0-9]+):&quot;/u&#39;, $text, $matches, PREG_OFFSET_CAPTURE, ++$offset)) { $number = $matches[1][0]; $pos = $matches[1][1]; $digits = strlen(&quot;$number&quot;); $pos_chars = mb_strlen(substr($text, 0, $pos)) + 2 + $digits; $str = mb_substr($text, $pos_chars, $number); $new_number = strlen($str); $new_digits = strlen($new_number); if($number != $new_number) { // Change stored number $text = substr_replace($text, $new_number, $pos, $digits); $pos += $new_digits - $digits; } $offset = $pos + 2 + $new_number; } return $text; } My initial approach was to do it with regular expressions, but the PHP serialized data format is not a regular language and cannot be properly parsed with regular expressions. All approaches fail on edge cases, and I had lots of edge cases in my data (I even had nested serialized strings!). Note that this will only work when converting from single-byte encoded data , since it assumes the stored lengths are the string lengths in characters. Admittedly, its not my best code, it could be optimized in many ways. It was something I had to write quickly and was only going to be used by me in a one-time conversion process. However, it works smoothly and has been tested with lots of different serialized data. I know that not many people will find it useful, but its going to be a lifesaver for the few ones that need it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Styling elements based on sibling count</title>
  <link>https://lea.verou.me/2011/01/styling-children-based-on-their-number-with-css3/</link>
  <pubDate>Sun, 17 May 2026 00:45:17 +0200</pubDate>
  <description>The original idea belongs to André Luís , but I think it could be improved to be much less verbose. André’s solution is like this: /* one item */ li:nth-child(1):nth-last-child(1) { width: 100%; } /* two items */ li:nth-child(1):nth-last-child(2), li:nth-child(2):nth-last-child(1) { width: 50%; } /* three items */ li:nth-child(1):nth-last-child(3), li:nth-child(2):nth-last-child(2), li:nth-child(3):nth-last-child(1) { width: 33.3333%; } /* four items */ li:nth-child(1):nth-last-child(4), li:nth-child(2):nth-last-child(3), li:nth-child(3):nth-last-child(2), li:nth-child(4):nth-last-child(1) { width: 25%; } It’s based on the relationship between :nth-child and :nth-last-child. As you can see, the number of total rules is O(N) and the number of selectors in every rule is also O(N). However, what you really want, is to just target the first element. The others can be targeted with just a sibling selector. With my improvement, the number of total rules is still O(N), but the number of selectors in every rule becomes just 2, making this trick practical for far larger numbers of children: /* one item */ li:first-child:nth-last-child(1) { width: 100%; } /* two items */ li:first-child:nth-last-child(2), li:first-child:nth-last-child(2) ~ li { width: 50%; } /* three items */ li:first-child:nth-last-child(3), li:first-child:nth-last-child(3) ~ li { width: 33.3333%; } /* four items */ li:first-child:nth-last-child(4), li:first-child:nth-last-child(4) ~ li { width: 25%; } And here’s a fiddle to prove it: Yes, I know that with Flexbox and the other layout modules, techniques such as these are soon becoming obsolete, but I think they are still useful right now. I’m also aware that you can emulate this particular example with table display modes, but a) Table display modes have other implications that are sometimes undesirable and b) Widths are just an example, you could come up with other ways to style the elements based on their total count, which can’t be emulated by CSS tables.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Styling elements based on sibling count</title>
  <link>https://lea.verou.me/2011/01/styling-children-based-on-their-number-with-css3/</link>
  <pubDate>Sun, 17 May 2026 00:45:17 +0200</pubDate>
  <description>The original idea belongs to André Luís , but I think it could be improved to be much less verbose. Andrés solution is like this: /* one item */ li:nth-child(1):nth-last-child(1) { width: 100%; } /* two items */ li:nth-child(1):nth-last-child(2), li:nth-child(2):nth-last-child(1) { width: 50%; } /* three items */ li:nth-child(1):nth-last-child(3), li:nth-child(2):nth-last-child(2), li:nth-child(3):nth-last-child(1) { width: 33.3333%; } /* four items */ li:nth-child(1):nth-last-child(4), li:nth-child(2):nth-last-child(3), li:nth-child(3):nth-last-child(2), li:nth-child(4):nth-last-child(1) { width: 25%; } Its based on the relationship between :nth-child and :nth-last-child. As you can see, the number of total rules is O(N) and the number of selectors in every rule is also O(N). However, what you really want, is to just target the first element. The others can be targeted with just a sibling selector. With my improvement, the number of total rules is still O(N), but the number of selectors in every rule becomes just 2, making this trick practical for far larger numbers of children: /* one item */ li:first-child:nth-last-child(1) { width: 100%; } /* two items */ li:first-child:nth-last-child(2), li:first-child:nth-last-child(2) ~ li { width: 50%; } /* three items */ li:first-child:nth-last-child(3), li:first-child:nth-last-child(3) ~ li { width: 33.3333%; } /* four items */ li:first-child:nth-last-child(4), li:first-child:nth-last-child(4) ~ li { width: 25%; } And heres a fiddle to prove it: Yes, I know that with Flexbox and the other layout modules, techniques such as these are soon becoming obsolete, but I think they are still useful right now. Im also aware that you can emulate this particular example with table display modes, but a) Table display modes have other implications that are sometimes undesirable and b) Widths are just an example, you could come up with other ways to style the elements based on their total count, which cant be emulated by CSS tables.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>I&#39;m speaking at @media Web Directions ’11!</title>
  <link>https://lea.verou.me/2011/01/im-speaking-at-atmedia-web-directions-11/</link>
  <pubDate>Sun, 17 May 2026 00:45:16 +0200</pubDate>
  <description>Just a quick note to let you know I’m speaking at this year’s @media Web Directions conference, which will take place during May 26–27 in London, UK. I’m very excited about this, since I always considered @media one of the top front-end conferences in the industry :) The title and abstract of my talk is as follows: CSS3 at the Outer Rim By now most of you know how to use the core CSS3 features in your designs to embed custom fonts and easily create rounded corners, drop shadows, and scalable designs with media queries. But there is still a large area of CSS3 that remains unexplored by most web designers and developers. In this talk Lea will present many CSS3 features that are useful but underrated, as well as uncommon ways of utilising the CSS3 features you already know about, in order to do much more with even fewer images and less code. Although it’s on the design track, I expect it to appeal to both developers and designers. You can use the coupon code WDVEROU to take £50 off the current price. ;) Hope to see you there! :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>I&#39;m speaking at @media Web Directions 11!</title>
  <link>https://lea.verou.me/2011/01/im-speaking-at-atmedia-web-directions-11/</link>
  <pubDate>Sun, 17 May 2026 00:45:16 +0200</pubDate>
  <description>Just a quick note to let you know Im speaking at this years @media Web Directions conference, which will take place during May 2627 in London, UK. Im very excited about this, since I always considered @media one of the top front-end conferences in the industry :) The title and abstract of my talk is as follows: CSS3 at the Outer Rim By now most of you know how to use the core CSS3 features in your designs to embed custom fonts and easily create rounded corners, drop shadows, and scalable designs with media queries. But there is still a large area of CSS3 that remains unexplored by most web designers and developers. In this talk Lea will present many CSS3 features that are useful but underrated, as well as uncommon ways of utilising the CSS3 features you already know about, in order to do much more with even fewer images and less code. Although its on the design track, I expect it to appeal to both developers and designers. You can use the coupon code WDVEROU to take 50 off the current price. ;) Hope to see you there! :D</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Yet another redesign</title>
  <link>https://lea.verou.me/2011/01/yet-another-redesign/</link>
  <pubDate>Sun, 17 May 2026 00:45:15 +0200</pubDate>
  <description>I had grown sick of my previous blog style and its various bugs (since it was put together in just a few hours), so I decided to make a new, more minimalistic one. Best viewed in browsers that support CSS gradients, like Firefox, Safari and Chrome. I also finally got around to making a logo for myself, although I’m not sure I’ll keep it. I also switched to HTML5, using Toolbox as a base. I want to make a few more changes, but I have to go to sleep sometime :p I also started using DISQUS for the blog comments. I like it when a blog I read has it (since it offers a few features I find convenient, like comment editing for instance), so I wanted to offer it to my readers too. It’s a shame that in some of their buttons they haven’t added the standard CSS3 border-radius declarations, but only the prefixed proprietary ones, so they’re square in Opera (and probably IE9). I’m fed up with seeing this in websites, TOPSY ’s widget also does it. However, their carelessness will backfire soon, when browsers stop supporting the prefixed versions *evil grin*</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Yet another redesign</title>
  <link>https://lea.verou.me/2011/01/yet-another-redesign/</link>
  <pubDate>Sun, 17 May 2026 00:45:15 +0200</pubDate>
  <description>I had grown sick of my previous blog style and its various bugs (since it was put together in just a few hours), so I decided to make a new, more minimalistic one. Best viewed in browsers that support CSS gradients, like Firefox, Safari and Chrome. I also finally got around to making a logo for myself, although Im not sure Ill keep it. I also switched to HTML5, using Toolbox as a base. I want to make a few more changes, but I have to go to sleep sometime :p I also started using DISQUS for the blog comments. I like it when a blog I read has it (since it offers a few features I find convenient, like comment editing for instance), so I wanted to offer it to my readers too. Its a shame that in some of their buttons they havent added the standard CSS3 border-radius declarations, but only the prefixed proprietary ones, so theyre square in Opera (and probably IE9). Im fed up with seeing this in websites, TOPSY s widget also does it. However, their carelessness will backfire soon, when browsers stop supporting the prefixed versions *evil grin*</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Checkerboard, striped &amp; other background patterns with CSS3 gradients</title>
  <link>https://lea.verou.me/2010/12/checkered-stripes-other-background-patterns-with-css3-gradients/</link>
  <pubDate>Sun, 17 May 2026 00:45:14 +0200</pubDate>
  <description>You’re probably familiar with CSS3 gradients by now, including the closer to the standard Mozilla syntax and the ugly verbose Webkit one. I assume you know how to add multiple color stops, make your gradients angled or create radial gradients. What you might not be aware of, is that CSS3 gradients can be used to create many kinds of commonly needed patterns, including checkered patterns, stripes and more. View demo (Works in Webkit, Firefox 3.6+, Opera 11.50+ and IE10+) The main idea behind the technique is the following, taken from the CSS3 Images spec : If multiple color-stops have the same position, they produce an infinitesimal transition from the one specified first in the rule to the one specified last. In effect, the color suddenly changes at that position rather than smoothly transitioning. I guess this makes it obvious how to create the tile for the stripes (unless you’ve never created a striped background before, but teaching you this is beyond the scope of this post). For example the gradient for the horizontal stripes is: background-color: #0ae; background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(.5, rgba(255, 255, 255, .2)), color-stop(.5, transparent), to(transparent)); background-image: -moz-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent); background-image: -o-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent); background-image: linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent); Why transparent instead of the actual colors we want? For flexibility. background-color serves two purposes here: Setting the color of half the stripes and serving as a fallback for browsers that don’t support gradients. However, without anything else, the tile will occupy the whole container. To control the size of each tile, you can use background-size: -webkit-background-size: 50px 50px; -moz-background-size: 50px 50px; background-size: 50px 50px; To create the picnic-style pattern, you just overlay horizontal stripes on vertical stripes. The hardest one to figure out was the checkered pattern. It consists of two 45° linear gradients and two -45° linear gradients, each containing ¼ of the dark squares. I still haven’t managed to think of a way to create a regular checkerboard (not at 45°) without needing an unacceptably large number of gradients. It will be very easily possible if conical gradients start being supported (currently they’re not even in the spec yet). Can you think of any other popular patterns that can be created with CSS3 and no images? If so, let me know with a comment. Cheers! :) Added afterwards: Other patterns There are far more pattern designs possible with CSS3 gradients than I originally thought. For more details, see this later post .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Checkerboard, striped &amp; other background patterns with CSS3 gradients</title>
  <link>https://lea.verou.me/2010/12/checkered-stripes-other-background-patterns-with-css3-gradients/</link>
  <pubDate>Sun, 17 May 2026 00:45:14 +0200</pubDate>
  <description>Youre probably familiar with CSS3 gradients by now, including the closer to the standard Mozilla syntax and the ugly verbose Webkit one. I assume you know how to add multiple color stops, make your gradients angled or create radial gradients. What you might not be aware of, is that CSS3 gradients can be used to create many kinds of commonly needed patterns, including checkered patterns, stripes and more. View demo (Works in Webkit, Firefox 3.6+, Opera 11.50+ and IE10+) The main idea behind the technique is the following, taken from the CSS3 Images spec : If multiple color-stops have the same position, they produce an infinitesimal transition from the one specified first in the rule to the one specified last. In effect, the color suddenly changes at that position rather than smoothly transitioning. I guess this makes it obvious how to create the tile for the stripes (unless youve never created a striped background before, but teaching you this is beyond the scope of this post). For example the gradient for the horizontal stripes is: background-color: #0ae; background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(.5, rgba(255, 255, 255, .2)), color-stop(.5, transparent), to(transparent)); background-image: -moz-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent); background-image: -o-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent); background-image: linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent); Why transparent instead of the actual colors we want? For flexibility. background-color serves two purposes here: Setting the color of half the stripes and serving as a fallback for browsers that dont support gradients. However, without anything else, the tile will occupy the whole container. To control the size of each tile, you can use background-size: -webkit-background-size: 50px 50px; -moz-background-size: 50px 50px; background-size: 50px 50px; To create the picnic-style pattern, you just overlay horizontal stripes on vertical stripes. The hardest one to figure out was the checkered pattern. It consists of two 45 linear gradients and two -45 linear gradients, each containing of the dark squares. I still havent managed to think of a way to create a regular checkerboard (not at 45) without needing an unacceptably large number of gradients. It will be very easily possible if conical gradients start being supported (currently theyre not even in the spec yet). Can you think of any other popular patterns that can be created with CSS3 and no images? If so, let me know with a comment. Cheers! :) Added afterwards: Other patterns There are far more pattern designs possible with CSS3 gradients than I originally thought. For more details, see this later post .</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>rgba.php v1.2: Improved URL syntax, now at Github</title>
  <link>https://lea.verou.me/2010/12/rgba-php-v1-2-improved-url-syntax-now-at-github/</link>
  <pubDate>Sun, 17 May 2026 00:45:13 +0200</pubDate>
  <description>I wrote the first version of rgba.php as a complement to an article on RGBA that I posted on Februrary 2009 . Many people seemed to like the idea and started using it. With their valuable input, I made many changes and released v.1.1 (1.1.1 shortly after I posted the article due to another little fix) on October 2009. More than a year after, quite a lot of people still ask me about it and use it, so I decided to make a github repo for it and release a new version, with a much easier to use syntax for the URL, which lets you just copy and paste the color instead of rewriting it: background: url(‘rgba.php/rgba(255, 255, 255, 0.3)’); background: rgba(255, 255, 255, 0.3); instead of: background: url(‘rgba.php?r=255&amp;g=255&amp;b=255&amp;a=30’); background: rgba(255, 255, 255, 0.3); I also made a quick about/demo page for it . Enjoy :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>rgba.php v1.2: Improved URL syntax, now at Github</title>
  <link>https://lea.verou.me/2010/12/rgba-php-v1-2-improved-url-syntax-now-at-github/</link>
  <pubDate>Sun, 17 May 2026 00:45:13 +0200</pubDate>
  <description>I wrote the first version of rgba.php as a complement to an article on RGBA that I posted on Februrary 2009 . Many people seemed to like the idea and started using it. With their valuable input, I made many changes and released v.1.1 (1.1.1 shortly after I posted the article due to another little fix) on October 2009. More than a year after, quite a lot of people still ask me about it and use it, so I decided to make a github repo for it and release a new version, with a much easier to use syntax for the URL, which lets you just copy and paste the color instead of rewriting it: background: url(rgba.php/rgba(255, 255, 255, 0.3)); background: rgba(255, 255, 255, 0.3); instead of: background: url(rgba.php?r=255&amp;g=255&amp;b=255&amp;a=30); background: rgba(255, 255, 255, 0.3); I also made a quick about/demo page for it . Enjoy :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Tag editing UIs</title>
  <link>https://lea.verou.me/2010/11/tag-editing-uis/</link>
  <pubDate>Sun, 17 May 2026 00:45:12 +0200</pubDate>
  <description>I had to build the edit tags interface for an application I’m working on, so I took a good look at how these are implemented across many popular applications nowadays. It seems there are a few patterns that are used over and over, and I’m unsure which one is the most preferable by users, they all have their advantages and disadvantages. In this post I’m going to describe these patterns and list some of the pros and cons I think they have. For simplicity, I will focus on the tag editing interface itself, ignoring any tag suggestions and other extra features. Pattern #1: Input field to add new tags, delete button for existing ones Used by: Wordpress, flickr, foursquare Pros: One click deletion of tags Cons: Impossible to edit a tag, you have to remove it and add the corrected version Hard to delete many tags at once Disconnected new and existing tags, making it hard to get the bigger picture foursquare’s implementation was the worst I’ve tested: There’s no (discoverable?) way to delete or edit a tag and when you add one via the text field it doesn’t get cleared which is confusing because it makes it seem like an edit tags field although it’s an add tags field, as I found out the hard way (by creating a “pizza, pasta” tag instead of 2 tags: pizza and pasta). Pattern #2: One text field to edit, delete or add new tags Used by: delicious, Google reader, stackoverflow, reddit Pros: Lets the user edit tags too, in addition to adding and deleting Easy to delete many tags at once All tags at one place Cons: More cumbersome to delete a tag A bit more prone to mistakes than guided interfaces Pattern #3: Hybrid approach: Text field for all, existing tags seem to be inside and have a delete button Used by: last.fm Pros: All tags in one place One click deletion Easy to delete many tags too Cons: There’s no editing in last.fm ’s implementation, but the pattern easily allows for that, for example by using contentEditable on the tag s last.fm chooses to implement this by faking the tags being inside an input field: Technically they’re implemented just like in pattern #1 above, with the difference that they visually appear to be inside the same box and every time a user inserts a comma (which is the tag separator) the tag they just typed is removed from the text field and a new link with a delete button is created just before the text field, which is much smaller than it looks. Which pattern is the best? As with most UI questions, I don’t think there’s a definite answer to that. It heavily depends on the audience too: A more technically inclined user might be more comfortable with the 2nd approach since it’s the least restrictive one. The average casual internet user might prefer the 3rd approach. I don’t think there’s any case where pattern #1 is better than pattern #3, except when development time is a concern (pattern #1 is a bit easier to implement, although #2 is the easiest of all). Another pattern? My initial attempt for the application I’m building was to use a hybrid approach of #2 and #3: When the user clicked on “Edit tags”, the tag container would get a contentEditable attribute and the idea was that every time a comma or any other non-permitted character would be inserted a new tag would be created (or if we were in the middle of one, it would get split into 2). That would have all the advantages of #2 and #3, except one-click deletion. It would also have the advantage that the user is directly editing the interface, which is usually a good idea usability-wise. I hate to admit I gave up on it for the time being, because it proved harder to implement than it seemed and I had to move on, so I went with #2. I might revisit it sometime in the future though if I still think it’s a good idea and nobody has done so by then.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Tag editing UIs</title>
  <link>https://lea.verou.me/2010/11/tag-editing-uis/</link>
  <pubDate>Sun, 17 May 2026 00:45:12 +0200</pubDate>
  <description>I had to build the edit tags interface for an application Im working on, so I took a good look at how these are implemented across many popular applications nowadays. It seems there are a few patterns that are used over and over, and Im unsure which one is the most preferable by users, they all have their advantages and disadvantages. In this post Im going to describe these patterns and list some of the pros and cons I think they have. For simplicity, I will focus on the tag editing interface itself, ignoring any tag suggestions and other extra features. Pattern #1: Input field to add new tags, delete button for existing ones Used by: Wordpress, flickr, foursquare Pros: One click deletion of tags Cons: Impossible to edit a tag, you have to remove it and add the corrected version Hard to delete many tags at once Disconnected new and existing tags, making it hard to get the bigger picture foursquares implementation was the worst Ive tested: Theres no (discoverable?) way to delete or edit a tag and when you add one via the text field it doesnt get cleared which is confusing because it makes it seem like an edit tags field although its an add tags field, as I found out the hard way (by creating a pizza, pasta tag instead of 2 tags: pizza and pasta). Pattern #2: One text field to edit, delete or add new tags Used by: delicious, Google reader, stackoverflow, reddit Pros: Lets the user edit tags too, in addition to adding and deleting Easy to delete many tags at once All tags at one place Cons: More cumbersome to delete a tag A bit more prone to mistakes than guided interfaces Pattern #3: Hybrid approach: Text field for all, existing tags seem to be inside and have a delete button Used by: last.fm Pros: All tags in one place One click deletion Easy to delete many tags too Cons: Theres no editing in last.fm s implementation, but the pattern easily allows for that, for example by using contentEditable on the tag s last.fm chooses to implement this by faking the tags being inside an input field: Technically theyre implemented just like in pattern #1 above, with the difference that they visually appear to be inside the same box and every time a user inserts a comma (which is the tag separator) the tag they just typed is removed from the text field and a new link with a delete button is created just before the text field, which is much smaller than it looks. Which pattern is the best? As with most UI questions, I dont think theres a definite answer to that. It heavily depends on the audience too: A more technically inclined user might be more comfortable with the 2nd approach since its the least restrictive one. The average casual internet user might prefer the 3rd approach. I dont think theres any case where pattern #1 is better than pattern #3, except when development time is a concern (pattern #1 is a bit easier to implement, although #2 is the easiest of all). Another pattern? My initial attempt for the application Im building was to use a hybrid approach of #2 and #3: When the user clicked on Edit tags, the tag container would get a contentEditable attribute and the idea was that every time a comma or any other non-permitted character would be inserted a new tag would be created (or if we were in the middle of one, it would get split into 2). That would have all the advantages of #2 and #3, except one-click deletion. It would also have the advantage that the user is directly editing the interface, which is usually a good idea usability-wise. I hate to admit I gave up on it for the time being, because it proved harder to implement than it seemed and I had to move on, so I went with #2. I might revisit it sometime in the future though if I still think its a good idea and nobody has done so by then.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The curious case of border-radius:50%</title>
  <link>https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/</link>
  <pubDate>Sun, 17 May 2026 00:45:11 +0200</pubDate>
  <description>Admittedly, percentages in border-radius are not one of the most common use cases. Some even consider them an edge case, since most people seem to set border-radius in pixels or --rarely-- ems. And since it’s not used very frequently, it’s still quite buggy. A bit of a chicken and egg case actually: Is it buggy because it’s used rarely or is it used rarely because it’s buggy? My vote would go to the first, so the purpose of this post is to let people know about why percentages in border-radius are incredibly useful and to highlight the various browser whims when it comes to rendering them. Specification Before we go into discussing implementations, let’s first examine what the right thing to do is, i.e. what the specification says : Percentages: Refer to corresponding dimension of the border box. The two length or percentage values of the ‘border-*-radius’ properties define the radii of a quarter ellipse that defines the shape of the corner of the outer border edge (see the diagram below). The first value is the horizontal radius, the second the vertical radius. If the second value is omitted it is copied from the first. If either length is zero, the corner is square, not rounded. Percentages for the horizontal radius refer to the width of the border box, whereas percentages for the vertical radius refer to the height of the border box . Why is that useful? It’s the only way of utilizing border-radius to draw a circle or ellipse, i.e. a rounded shape without any straight lines whatsoever (without knowing the dimensions in advance). As you will see below, Firefox used to have a bug, or actually a different interpretation of the spec, which I think is a quite commonly needed use case, even more than ellipses: It always drew a regular curve for the corners (quarter of a circle) with the maximum possible radii . This is a very commonly needed shape in UI design. If you’re using OSX, you’re seeing it everywhere: the buttons, the scrollbars, even Skype (notice the blue or grey shading around the usernames in a chat). As I’m writing this post, I can see the same shape in the buttons of Wordpress’ admin panel. And as the current spec stands, there’s no way to do that. You have to know the height (or width, if you want a vertical shape) in advance, which even when possible, makes border-radius depend on the value of other attributes (such as line-height) and you have to remember to change it every time you change those, which causes maintenance headaches. And what’s worse is that the Backgrounds &amp; Borders module is almost done, so it’s quite unlikely that this will change anytime soon. :( As noted in this comment by David Baron , that assumption wasn’t exactly correct about Firefox’s old rendering. It just resolved % as relative to width in every case (kinda like percentages in margins) and when the height was smaller than the width, it applied the rules for radii that are too big, which say to reduce it equally. A straightforward deduction is that we do have a standards-compliant way to get the behavior from old versions of Firefox, in every browser: Just specify a very big radius, like 9999px. Different implementations, different bugs Firefox 4 beta 6 As I mentioned above, Gecko up to Firefox version 4 beta 6 always draws a regular curve for the corners with the largest radii applicable, resulting in a shape that is either a perfect circle or a rectangle with a semicircle on top and bottom (if height &gt; width) or right and left (if width &gt; height). Minefield (latest Gecko nightlies) In the latest nightlies this bug is fixed, and it follows the spec to the letter. I can’t help but wonder if this was a bug, a misinterpretation of the spec or a deliberate disagreement with it. WebKit nightlies Webkit was late to support percentages in border-radius, but it seems to be the first (it or IE9, I’m not sure) to follow the spec to the letter --concerning corner radii at least-- and renders an ellipse (horizontal radius = width/2, vertical radius = height/2) no matter what. Webkit however seems to be having serious trouble with borders, rendering them with variable width strokes (!). Opera 11 Presto (Opera) is the weirdest when it comes to rendering a percentage border-radius. I can’t figure out the algorithm it uses to determine the radii of the corners even if it was to save my life, it even changes according to window size in my testcases! Since I’ve been using border-radius:50% regularly, I’ve had the pleasure of observing Opera’s rendering in many different designs and I still can’t find a pattern. It’s particularly funny when rendering the little fuchsia comment bubbles in the homepage of my blog: Every one of them has a different radius, even if they are about the same size. It even got one of them right and rendered it as an ellipse once! Internet Explorer 9 Trident (IE9) , along with the latest Gecko nightly is the only 100% correct one when it comes to rendering the testcases, which is not surprising since the IE team boasted quite a lot for their bulletproof border-radius implementation. Well, their CSS3 support might be a bit lacking, but at least the bits they actually implement aren’t buggy. Kudos for that. Link to testcases Note: Of course all bugs mentioned above have been reported to the respective browser vendors (except the Gecko one that is already fixed in the nightlies).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>The curious case of border-radius:50%</title>
  <link>https://lea.verou.me/2010/10/the-curious-case-of-border-radius50/</link>
  <pubDate>Sun, 17 May 2026 00:45:11 +0200</pubDate>
  <description>Admittedly, percentages in border-radius are not one of the most common use cases. Some even consider them an edge case, since most people seem to set border-radius in pixels or --rarely-- ems. And since its not used very frequently, its still quite buggy. A bit of a chicken and egg case actually: Is it buggy because its used rarely or is it used rarely because its buggy? My vote would go to the first, so the purpose of this post is to let people know about why percentages in border-radius are incredibly useful and to highlight the various browser whims when it comes to rendering them. Specification Before we go into discussing implementations, lets first examine what the right thing to do is, i.e. what the specification says : Percentages: Refer to corresponding dimension of the border box. The two length or percentage values of the border-*-radius properties define the radii of a quarter ellipse that defines the shape of the corner of the outer border edge (see the diagram below). The first value is the horizontal radius, the second the vertical radius. If the second value is omitted it is copied from the first. If either length is zero, the corner is square, not rounded. Percentages for the horizontal radius refer to the width of the border box, whereas percentages for the vertical radius refer to the height of the border box . Why is that useful? Its the only way of utilizing border-radius to draw a circle or ellipse, i.e. a rounded shape without any straight lines whatsoever (without knowing the dimensions in advance). As you will see below, Firefox used to have a bug, or actually a different interpretation of the spec, which I think is a quite commonly needed use case, even more than ellipses: It always drew a regular curve for the corners (quarter of a circle) with the maximum possible radii . This is a very commonly needed shape in UI design. If youre using OSX, youre seeing it everywhere: the buttons, the scrollbars, even Skype (notice the blue or grey shading around the usernames in a chat). As Im writing this post, I can see the same shape in the buttons of Wordpress admin panel. And as the current spec stands, theres no way to do that. You have to know the height (or width, if you want a vertical shape) in advance, which even when possible, makes border-radius depend on the value of other attributes (such as line-height) and you have to remember to change it every time you change those, which causes maintenance headaches. And whats worse is that the Backgrounds &amp; Borders module is almost done, so its quite unlikely that this will change anytime soon. :( As noted in this comment by David Baron , that assumption wasnt exactly correct about Firefoxs old rendering. It just resolved % as relative to width in every case (kinda like percentages in margins) and when the height was smaller than the width, it applied the rules for radii that are too big, which say to reduce it equally. A straightforward deduction is that we do have a standards-compliant way to get the behavior from old versions of Firefox, in every browser: Just specify a very big radius, like 9999px. Different implementations, different bugs Firefox 4 beta 6 As I mentioned above, Gecko up to Firefox version 4 beta 6 always draws a regular curve for the corners with the largest radii applicable, resulting in a shape that is either a perfect circle or a rectangle with a semicircle on top and bottom (if height &gt; width) or right and left (if width &gt; height). Minefield (latest Gecko nightlies) In the latest nightlies this bug is fixed, and it follows the spec to the letter. I cant help but wonder if this was a bug, a misinterpretation of the spec or a deliberate disagreement with it. WebKit nightlies Webkit was late to support percentages in border-radius, but it seems to be the first (it or IE9, Im not sure) to follow the spec to the letter --concerning corner radii at least-- and renders an ellipse (horizontal radius = width/2, vertical radius = height/2) no matter what. Webkit however seems to be having serious trouble with borders, rendering them with variable width strokes (!). Opera 11 Presto (Opera) is the weirdest when it comes to rendering a percentage border-radius. I cant figure out the algorithm it uses to determine the radii of the corners even if it was to save my life, it even changes according to window size in my testcases! Since Ive been using border-radius:50% regularly, Ive had the pleasure of observing Operas rendering in many different designs and I still cant find a pattern. Its particularly funny when rendering the little fuchsia comment bubbles in the homepage of my blog: Every one of them has a different radius, even if they are about the same size. It even got one of them right and rendered it as an ellipse once! Internet Explorer 9 Trident (IE9) , along with the latest Gecko nightly is the only 100% correct one when it comes to rendering the testcases, which is not surprising since the IE team boasted quite a lot for their bulletproof border-radius implementation. Well, their CSS3 support might be a bit lacking, but at least the bits they actually implement arent buggy. Kudos for that. Link to testcases Note: Of course all bugs mentioned above have been reported to the respective browser vendors (except the Gecko one that is already fixed in the nightlies).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My FT2010 slides and CSSS: My presentation framework</title>
  <link>https://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/</link>
  <pubDate>Sun, 17 May 2026 00:45:10 +0200</pubDate>
  <description>About a week ago, I was in Warsaw, Poland to give my first talk at a big conference, Front Trends 2010 . As every first-time speaker, I was extremely nervous and worried that everything would go bad. That my talk would be boring or too basic or that I would just freeze at stage, unable to say a word. It was a 2-hour talk with a break in between, so I was also terrified that nobody would show up the second hour. Contrary to my fears and insecurities, it went better than I could have ever hoped. The feedback on twitter and in general was enthusiastic! There wasn’t a single negative comment. Even people I look up to, like Tantek Çelik , PPK , Jake Archibald or Robert Nyman had something good to say! And instead of nobody showing up the second hour, the audience almost doubled! At this point, I would like to thank Christian Heilmann for helping me become less nervous before my talk by going through all my slides with me and offering his invaluable advice for every part (I forgot to follow most of it, but it really helped in my attitude). I can’t thank you enough Christian! Many attendees asked me for my slides and presentation framework. You can find my slides online here or download them. However, before you follow those links, read below : I originally ran my presentation in Firefox 4 beta so I was testing mainly in that and Minefield (Firefox’s nightly releases). It supports other browsers too (Chrome 7, Opera 10.6+), but it still displays better in Firefox or Minefield and is (surprisingly) faster in them. Opera has issues with a few unicode characters I used in some places and won’t display Helvetica Neue even if it’s installed (@font-face is not an option with that font, for legal reasons) Any non-Gecko browser will not display CSS gradients , since Gecko is the only engine so far that supports the standard syntax. Therefore the gradient demos and the multiple backgrounds demo won’t work in non-Gecko browsers. Some slides are a bit slow on Webkit . The first slide is extremely slow in it, you have been warned. Opera and Webkit have (different) bugs with border-radius: 50%, so some things using it will look funny. I have only tested in OSX browsers . I have no idea how it will perform on Windows or Linux distros yet. It’s a 2-hour talk and the presentation was designed to run locally. It’s not small and it will take a while to load . That’s due to the images used, as you can easily see from the zip archive. The editable examples many of you liked are based on this CSS mindfuck by Anne van Kesteren . It’s smart and convenient, but beware: It breaks really, really easily. It’s good for changing the code realtime, but it will most likely break if you try to add extra code. In case you’re not feeling very adventurous today, or you’re just using a computer with only unsupported browsers, here’s the presentation as a series of images (not interactive, but still the same info): CSS3: A practical introduction (FT2010 talk) By popular demand, I’m also releasing my presentation framework, for which in the meantime I found a name ( CSSS , inspired by S5), designed a logo and made a simpler, sample presentation with a different, simpler theme. I released it in a public repo on Github (finally got around to learning the basics of Github and loved it!). Please note that this is a very first version and I haven’t been able to test it much, especially on Windows, since my Mac is quite new and I keep postponing to install some virtualization software. A friend reported that Firefox 3.6 on Windows has serious issues with it, although it runs fine on my FF3.6 copy for Mac. It doesn’t work at all in IE, even IE9, as I don’t yet have IE to test it out. Please report any issues on Github’s bug tracker and eventually I --or someone else, you’re all welcome :p-- will fix them (don’t forget to mention exact browser version and OS). If you’re using Safari, press Ctrl+H for something cool ;) (it works on the others too, but it’s slower and not smooth) Some may ask: “If CSS3 degrades so gracefully and we can use it today as you told us in your talk, then why all these issues with different browsers in CSSS or your FT presentation?” . First of all, these are not everyday use cases. Projects like CSSS or my FT presentation are quite experimental, use a lot of CSS3, including many edge cases and I could have devoted more time to make them degrade more gracefully, but given the target audience, I don’t think it’s worth it much. It’s expected that there might be rendering problems in some browsers or that they might be slow, browsers need edge cases to highlight problems in their implementations of the new stuff before it’s finalized. Every time I experiment with CSS3, I find at least one browser bug, which I generally try to report (don’t let that scare you though, as I said, I have a penchant for edge cases). You may have also noticed I redesigned my blog. As you may have noticed, I have fallen in love with that Rainbow Wood wallpaper by Luke Roberts and I just had to put it in my blog too :P The new design has a few issues with Opera at the moment, but I hope to fix them soon. It will also look better to those that have Helvetica Neue installed.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On attr() and calc()</title>
  <link>https://lea.verou.me/2010/09/on-attr-and-calc/</link>
  <pubDate>Sun, 17 May 2026 00:45:10 +0200</pubDate>
  <description>I recently posted my first suggestion to www-style , the official W3 mailing list for CSS development. It was about allowing attr() values inside calc() . In this post I’ll describe in greater detail why I believe this is necessary, since not everyone follows www-style. If anyone has something to add in the discussion, you may post in the list, it’s public. attr() As you can easily find out in the specification , the W3 is planning for attr() to play a much bigger role in tomorrow’s CSS than it played in CSS 2.1, where it was originally defined , which opens up exciting possibilities. In a nutshell, we’re going to be able to use attr() in any property, for any type of value, let it be , , or anything else. If the type is not obvious, we’re able to define it, via the second parameter and include a fallback value in the 3rd one. We might even be able to do things like float: attr(X ); (keywords are still under consideration). calc() On the other hand, as you’re probably already aware of, since calc() is one of the hyped CSS3 features, we’re finally going to be able to do calculations with different types of units, for example calc(100% - 30px) , which is something web designers requested for years. calc(attr()) You can easily see from the grammar presented in the specification for calc() that it does not allow attr() values to be used as operands in the calculations. To me, this is an obvious oversight. Since attr() values can be used anywhere, including where lengths and numbers are allowed, not being able to use them in calc() is absurd. As David Storey pointed out , this could be enormously useful when used in conjunction with the new form control attributes (min, max, step and the like) or HTML5 custom data attributes (data-x). Philosophically, it makes perfect sense that attr() should be allowed anywhere a or or or … is. We can’t expect attributes to only hold semantic and not presentational data, but expect these data to be ready to be utilized for presentation purposes, without any calculations whatsoever . The first use case I can think of is the one that inspired me to suggest this. A while ago, I was researching CSS-based bar charts and progress bars. It turned out that there is no practical and purely semantic solution for specifying the bar widths. Either you have to include inline styles or you bloat your CSS with countless classes or ids , one for each width or —even worse— bar. In cases where you just want to use the displayed percentage of the bar as its width as well, attr() can actually help. However, as you can see, this is not always the case. Most of the times the bar values are not percentages or you want to also perform calculations on the percentage, for example include padding (because usually you display the number as well) or cut it in half to prevent the bar chart from appearing very big, etc, in which calc() combined with attr() could be a lifesaver. One could argue that bar charts and progress bars are not legitimate CSS use cases but hacks that work around the lack of cross-browser SVG support, and it’s very possible that they are right (although the addition of elements like in HTML5 is by itself an argument for the opposite). However, the use cases are not limited to that. Αny kind of stylistic treatment that is supposed to convey some kind of fraction or number (progress, temperature, distance etc) will benefit from keeping the actual data in a data-x attribute and utilize them via attr() and calc(). Admittedly, coming up with more generic use cases is not very easy, since they greatly depend on the particular application. However, the same difficulty arises when trying to come up with use cases for the attr() function by itself when used for the numerical types ( , etc), in properties other than content. Perhaps this is the reason that not even the specification contains any practical examples for it either. I guess almost any real-life use case for attr(*, number|integer|length|angle|frequency|em|px|…, *) is also a use case for this. So far I’m optimistic about it, since almost all participants in the discussion were positive. However, calc() has already started being implemented (by Mozilla), so as time goes by, it will be increasingly harder to make changes to its grammar. What do you think? How would you use it if it’s implemented? Edit: Sometime in Spring 2012, the issue was brought up again, and the CSS WG agreed that attr() should be permitted in calc() . Now it’s just a matter of browsers catching up to the spec. :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>My FT2010 slides and CSSS: My presentation framework</title>
  <link>https://lea.verou.me/2010/10/my-ft2010-slides-and-csss-my-presentation-framework/</link>
  <pubDate>Sun, 17 May 2026 00:45:10 +0200</pubDate>
  <description>About a week ago, I was in Warsaw, Poland to give my first talk at a big conference, Front Trends 2010 . As every first-time speaker, I was extremely nervous and worried that everything would go bad. That my talk would be boring or too basic or that I would just freeze at stage, unable to say a word. It was a 2-hour talk with a break in between, so I was also terrified that nobody would show up the second hour. Contrary to my fears and insecurities, it went better than I could have ever hoped. The feedback on twitter and in general was enthusiastic! There wasnt a single negative comment. Even people I look up to, like Tantek Çelik , PPK , Jake Archibald or Robert Nyman had something good to say! And instead of nobody showing up the second hour, the audience almost doubled! At this point, I would like to thank Christian Heilmann for helping me become less nervous before my talk by going through all my slides with me and offering his invaluable advice for every part (I forgot to follow most of it, but it really helped in my attitude). I cant thank you enough Christian! Many attendees asked me for my slides and presentation framework. You can find my slides online here or download them. However, before you follow those links, read below : I originally ran my presentation in Firefox 4 beta so I was testing mainly in that and Minefield (Firefoxs nightly releases). It supports other browsers too (Chrome 7, Opera 10.6+), but it still displays better in Firefox or Minefield and is (surprisingly) faster in them. Opera has issues with a few unicode characters I used in some places and wont display Helvetica Neue even if its installed (@font-face is not an option with that font, for legal reasons) Any non-Gecko browser will not display CSS gradients , since Gecko is the only engine so far that supports the standard syntax. Therefore the gradient demos and the multiple backgrounds demo wont work in non-Gecko browsers. Some slides are a bit slow on Webkit . The first slide is extremely slow in it, you have been warned. Opera and Webkit have (different) bugs with border-radius: 50%, so some things using it will look funny. I have only tested in OSX browsers . I have no idea how it will perform on Windows or Linux distros yet. Its a 2-hour talk and the presentation was designed to run locally. Its not small and it will take a while to load . Thats due to the images used, as you can easily see from the zip archive. The editable examples many of you liked are based on this CSS mindfuck by Anne van Kesteren . Its smart and convenient, but beware: It breaks really, really easily. Its good for changing the code realtime, but it will most likely break if you try to add extra code. In case youre not feeling very adventurous today, or youre just using a computer with only unsupported browsers, heres the presentation as a series of images (not interactive, but still the same info): CSS3: A practical introduction (FT2010 talk) By popular demand, Im also releasing my presentation framework, for which in the meantime I found a name ( CSSS , inspired by S5), designed a logo and made a simpler, sample presentation with a different, simpler theme. I released it in a public repo on Github (finally got around to learning the basics of Github and loved it!). Please note that this is a very first version and I havent been able to test it much, especially on Windows, since my Mac is quite new and I keep postponing to install some virtualization software. A friend reported that Firefox 3.6 on Windows has serious issues with it, although it runs fine on my FF3.6 copy for Mac. It doesnt work at all in IE, even IE9, as I dont yet have IE to test it out. Please report any issues on Githubs bug tracker and eventually I --or someone else, youre all welcome :p-- will fix them (dont forget to mention exact browser version and OS). If youre using Safari, press Ctrl+H for something cool ;) (it works on the others too, but its slower and not smooth) Some may ask: If CSS3 degrades so gracefully and we can use it today as you told us in your talk, then why all these issues with different browsers in CSSS or your FT presentation? . First of all, these are not everyday use cases. Projects like CSSS or my FT presentation are quite experimental, use a lot of CSS3, including many edge cases and I could have devoted more time to make them degrade more gracefully, but given the target audience, I dont think its worth it much. Its expected that there might be rendering problems in some browsers or that they might be slow, browsers need edge cases to highlight problems in their implementations of the new stuff before its finalized. Every time I experiment with CSS3, I find at least one browser bug, which I generally try to report (dont let that scare you though, as I said, I have a penchant for edge cases). You may have also noticed I redesigned my blog. As you may have noticed, I have fallen in love with that Rainbow Wood wallpaper by Luke Roberts and I just had to put it in my blog too :P The new design has a few issues with Opera at the moment, but I hope to fix them soon. It will also look better to those that have Helvetica Neue installed.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On attr() and calc()</title>
  <link>https://lea.verou.me/2010/09/on-attr-and-calc/</link>
  <pubDate>Sun, 17 May 2026 00:45:10 +0200</pubDate>
  <description>I recently posted my first suggestion to www-style , the official W3 mailing list for CSS development. It was about allowing attr() values inside calc() . In this post Ill describe in greater detail why I believe this is necessary, since not everyone follows www-style. If anyone has something to add in the discussion, you may post in the list, its public. attr() As you can easily find out in the specification , the W3 is planning for attr() to play a much bigger role in tomorrows CSS than it played in CSS 2.1, where it was originally defined , which opens up exciting possibilities. In a nutshell, were going to be able to use attr() in any property, for any type of value, let it be , , or anything else. If the type is not obvious, were able to define it, via the second parameter and include a fallback value in the 3rd one. We might even be able to do things like float: attr(X ); (keywords are still under consideration). calc() On the other hand, as youre probably already aware of, since calc() is one of the hyped CSS3 features, were finally going to be able to do calculations with different types of units, for example calc(100% - 30px) , which is something web designers requested for years. calc(attr()) You can easily see from the grammar presented in the specification for calc() that it does not allow attr() values to be used as operands in the calculations. To me, this is an obvious oversight. Since attr() values can be used anywhere, including where lengths and numbers are allowed, not being able to use them in calc() is absurd. As David Storey pointed out , this could be enormously useful when used in conjunction with the new form control attributes (min, max, step and the like) or HTML5 custom data attributes (data-x). Philosophically, it makes perfect sense that attr() should be allowed anywhere a or or or is. We cant expect attributes to only hold semantic and not presentational data, but expect these data to be ready to be utilized for presentation purposes, without any calculations whatsoever . The first use case I can think of is the one that inspired me to suggest this. A while ago, I was researching CSS-based bar charts and progress bars. It turned out that there is no practical and purely semantic solution for specifying the bar widths. Either you have to include inline styles or you bloat your CSS with countless classes or ids , one for each width or even worse bar. In cases where you just want to use the displayed percentage of the bar as its width as well, attr() can actually help. However, as you can see, this is not always the case. Most of the times the bar values are not percentages or you want to also perform calculations on the percentage, for example include padding (because usually you display the number as well) or cut it in half to prevent the bar chart from appearing very big, etc, in which calc() combined with attr() could be a lifesaver. One could argue that bar charts and progress bars are not legitimate CSS use cases but hacks that work around the lack of cross-browser SVG support, and its very possible that they are right (although the addition of elements like in HTML5 is by itself an argument for the opposite). However, the use cases are not limited to that. ny kind of stylistic treatment that is supposed to convey some kind of fraction or number (progress, temperature, distance etc) will benefit from keeping the actual data in a data-x attribute and utilize them via attr() and calc(). Admittedly, coming up with more generic use cases is not very easy, since they greatly depend on the particular application. However, the same difficulty arises when trying to come up with use cases for the attr() function by itself when used for the numerical types ( , etc), in properties other than content. Perhaps this is the reason that not even the specification contains any practical examples for it either. I guess almost any real-life use case for attr(*, number|integer|length|angle|frequency|em|px|, *) is also a use case for this. So far Im optimistic about it, since almost all participants in the discussion were positive. However, calc() has already started being implemented (by Mozilla), so as time goes by, it will be increasingly harder to make changes to its grammar. What do you think? How would you use it if its implemented? Edit: Sometime in Spring 2012, the issue was brought up again, and the CSS WG agreed that attr() should be permitted in calc() . Now its just a matter of browsers catching up to the spec. :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Automatic login via notification emails?</title>
  <link>https://lea.verou.me/2010/08/automatic-login-via-notification-emails/</link>
  <pubDate>Sun, 17 May 2026 00:45:09 +0200</pubDate>
  <description>A couple hours ago, I received a notification email from Goodreads and unlike usually, I decided to actually visit the site (by the way, I believe that Goodreads, i.e. a last.fm for books, is an awesome idea but poorly implemented).When I did, I was quite annoyed to find out that I wasn’t already logged in, so I had to remember which one of my many passwords I had used for it and try them one by one. This is not a Goodreads fail, but a fairly common nuisance, since most (if not all) social websites behave that way. “What if there was some magic involved?” Bill Scott &amp; Theresa Neil advise interaction designers to ask themselves in a book I’m currently reading (highly recommended by the way). Well, I guess, if there was some magic involved, the site would “understand” that my click was initiated from an email and would automatically log me in and let me view whatever I was trying to . What’s the point of asking for a password if the user can prove they have access to the associated email account? Such access is usually all that’s needed for someone to break into an account, theirs or not (via the forgotten password feature). So, it doesn’t help security much, just makes it slightly more time-consuming for potential impostors, while turning legitimate users with a weak memory (like yours truly) away from the site. I’m not sure whether it’s a good or a stupid idea, I’m not really suggesting it, just expressing a thought. :) I have some concerns myself too: It’s definitely harder to implement . All links sent in notification emails must contain some special token, like reset password links do (I’ve never seen it implemented otherwise). The tokens in reset password links expire after a while, so probably these should too, for security reasons. And what happens after that? A regular login is required? Doesn’t this render the whole idea a bit pointless, since notification emails are frequently read 1+ days after they’re sent? Usually a frequent user receives a bunch of email notifications per day. Isn’t it a bit too risky to have dozens of such powerful emails floating around in your inbox? On the other hand, it doesn’t seem more dangerous than using the “remember me” feature while logging in: Anyone that manages to get ahold of your laptop for a minute is able to use your account in most SN sites, one way or another. However, the “remember me” feature is a classic case where usability triumphed security , at least in cases where the computer isn’t shared. Thinking of the “remember me” feature gives me another idea: It could be optional and active by default . Perhaps with a link to easily deactivate the feature in every such email. On the other hand, more options = more confusion. Also, to avoid the issues stated in #3, this feature could be activated only if the user in question was inactive for a while. Frequent users don’t need it that much and even if they did, they don’t run away so easily, so it’s not as crucial. What do you think? Mostly useful or mostly evil?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Lea Verou @ Front-Trends 2010</title>
  <link>https://lea.verou.me/2010/08/lea-verou-at-front-trends-2010/</link>
  <pubDate>Sun, 17 May 2026 00:45:09 +0200</pubDate>
  <description>Just a quick note to let you know that I’m speaking in this year’s Front-Trends conference, which will take place in Warsaw, Poland on October 21-22. Front-Trends is a new conference (starting this year) but the organizers have managed to put together an impressive line-up ( Crockford , PPK , Paul Bakaus , Dmitry Baranovskiy , Tantek Çelik , Robert Nyman and more). My talk will introduce many aspects of CSS3, some of them in good depth (eg. selectors). Here is the official abstract: Pragmatic CSS3 With browsers constantly adding support for CSS3, especially now that even IE jumped in the game, it’s quickly becoming a necessary tool of the trade. CSS3 offers exciting possibilities and changes the way that we design and develop websites. In this 2-hour practical session, full of real world use cases, you will learn: Everything you ever wanted to know about CSS3 selectors Transparency and new color formats, including RGBA New ways to work with backgrounds, including CSS gradients, multiple background images and natively supported CSS sprites Rounded corners and border images Box and text shadows Transforms, transitions and their potential downsides New values, including calc(), attr() and new units Browser support information and techniques to take advantage of the exciting new stuff with respect to browsers of the past, to create experiences that are enjoyable for everyone Tickets are very cheap (Just €198 ) but they’re selling quite fast, so if you want to come, hurry up !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Automatic login via notification emails?</title>
  <link>https://lea.verou.me/2010/08/automatic-login-via-notification-emails/</link>
  <pubDate>Sun, 17 May 2026 00:45:09 +0200</pubDate>
  <description>A couple hours ago, I received a notification email from Goodreads and unlike usually, I decided to actually visit the site (by the way, I believe that Goodreads, i.e. a last.fm for books, is an awesome idea but poorly implemented).When I did, I was quite annoyed to find out that I wasnt already logged in, so I had to remember which one of my many passwords I had used for it and try them one by one. This is not a Goodreads fail, but a fairly common nuisance, since most (if not all) social websites behave that way. What if there was some magic involved? Bill Scott &amp; Theresa Neil advise interaction designers to ask themselves in a book Im currently reading (highly recommended by the way). Well, I guess, if there was some magic involved, the site would understand that my click was initiated from an email and would automatically log me in and let me view whatever I was trying to . Whats the point of asking for a password if the user can prove they have access to the associated email account? Such access is usually all thats needed for someone to break into an account, theirs or not (via the forgotten password feature). So, it doesnt help security much, just makes it slightly more time-consuming for potential impostors, while turning legitimate users with a weak memory (like yours truly) away from the site. Im not sure whether its a good or a stupid idea, Im not really suggesting it, just expressing a thought. :) I have some concerns myself too: Its definitely harder to implement . All links sent in notification emails must contain some special token, like reset password links do (Ive never seen it implemented otherwise). The tokens in reset password links expire after a while, so probably these should too, for security reasons. And what happens after that? A regular login is required? Doesnt this render the whole idea a bit pointless, since notification emails are frequently read 1+ days after theyre sent? Usually a frequent user receives a bunch of email notifications per day. Isnt it a bit too risky to have dozens of such powerful emails floating around in your inbox? On the other hand, it doesnt seem more dangerous than using the remember me feature while logging in: Anyone that manages to get ahold of your laptop for a minute is able to use your account in most SN sites, one way or another. However, the remember me feature is a classic case where usability triumphed security , at least in cases where the computer isnt shared. Thinking of the remember me feature gives me another idea: It could be optional and active by default . Perhaps with a link to easily deactivate the feature in every such email. On the other hand, more options = more confusion. Also, to avoid the issues stated in #3, this feature could be activated only if the user in question was inactive for a while. Frequent users dont need it that much and even if they did, they dont run away so easily, so its not as crucial. What do you think? Mostly useful or mostly evil?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Lea Verou @ Front-Trends 2010</title>
  <link>https://lea.verou.me/2010/08/lea-verou-at-front-trends-2010/</link>
  <pubDate>Sun, 17 May 2026 00:45:09 +0200</pubDate>
  <description>Just a quick note to let you know that Im speaking in this years Front-Trends conference, which will take place in Warsaw, Poland on October 21-22. Front-Trends is a new conference (starting this year) but the organizers have managed to put together an impressive line-up ( Crockford , PPK , Paul Bakaus , Dmitry Baranovskiy , Tantek Çelik , Robert Nyman and more). My talk will introduce many aspects of CSS3, some of them in good depth (eg. selectors). Here is the official abstract: Pragmatic CSS3 With browsers constantly adding support for CSS3, especially now that even IE jumped in the game, its quickly becoming a necessary tool of the trade. CSS3 offers exciting possibilities and changes the way that we design and develop websites. In this 2-hour practical session, full of real world use cases, you will learn: Everything you ever wanted to know about CSS3 selectors Transparency and new color formats, including RGBA New ways to work with backgrounds, including CSS gradients, multiple background images and natively supported CSS sprites Rounded corners and border images Box and text shadows Transforms, transitions and their potential downsides New values, including calc(), attr() and new units Browser support information and techniques to take advantage of the exciting new stuff with respect to browsers of the past, to create experiences that are enjoyable for everyone Tickets are very cheap (Just 198 ) but theyre selling quite fast, so if you want to come, hurry up !</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Organizing a university course on modern Web development</title>
  <link>https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/</link>
  <pubDate>Sun, 17 May 2026 00:45:08 +0200</pubDate>
  <description>About a year ago, prof. Vasilis Vassalos of Athens University of Economics and Business approached me and asked for my help in a new course they were preparing for their Computer Science department , which would introduce 4th year undergrads to various web development aspects. Since I was always complaining about how outdated higher education is when it comes to web development, I saw it as my chance to help things change for the better, so I agreed without a second thought. This is one of the main reasons I didn’t have time to write many blog posts for the past months: This activity took up all my spare time. However, it proved to be an interesting and enlightening experience, in more than one ways. In this blog post I’ll describe the dilemmas we faced, the decisions we made and the insights I gained throughout these 6 months, with the hope that they’ll prove to be useful for anyone involved in something similar. Table of contents Content Homework Labs Personal aftermath Content The goals of a university course differ from the ones of a professional seminar or conference session in many ways, the key one being that most of its students will (professionally) utilize the things they learned in the future and not right after they walk away from class. So, the stuff being taught must be useful even after a couple years have passed. Also, issues of the present might not be issues of the future and what isn’t possible today (due to browser support issues) will probably be tomorrow. These observations led us to decide against teaching proprietary stuff. Instead, we only included things which come with a specification that has reached a fairly stable state (with the exception of very widespread non-standard stuff, such as innerHTML ). We also decided not to address workarounds and browser incompatibilities at all , since these would probably be out of date in a few years. Also because, if we teach everything else right, they should be able to learn these by themselves, if needed (we did teach feature detection techniques though, those are timeless ;-)). We also included many cutting edge topics (CSS3, HTML5, ES5, SVG…) since we believe that they will be necessary tools of the trade tomorrow . To be pragmatic however, we did not teach stuff that no browser has implemented yet , besides perhaps a brief mention. To make things easier for the students, we used Firefox 3.6 for everything. We tested their assignments there, we used it to present something in the labs etc. Why Firefox? It’s at a quite good level of standards compliance and implements many modern technologies &amp; features Fewer bugs (Webkit implements stuff faster, but in more buggy ways) It has the best development tools (Firebug) With Brendan Eich being Mozilla’s CTO, we all know how progressive Firefox is when it comes to JavaScript. Of course, this doesn’t mean it’s the only right choice. Google Chrome for example would be another good pick. Another useful observation was that 4th year Computer Science students already know programming quite well, especially Java. So, we did not need to go through the basics of programming syntax like introductory books or seminars frequently do. Consequently, we skipped explaining how control structures or operators work in JavaScript or PHP and just focused on their differences from Java and other languages. Another dilemma we faced was whether we should teach stuff on popular frameworks and whether we should allow them in the homeworks. We decided against allowing them in the homeworks because I believe that someone must not use a framework just to skip learning about the intricacies of a language. They should be used after the basics have been consolidated, in order to save time. Also because if everyone skips learning and just uses an abstraction to do the heavy lifting from the very beginning, who will write the abstractions after all? Another reason was that a large portion of every JavaScript framework is about handling cross-browser differences. However, these had no place in our course, so a JS framework wasn’t as necessary as it is in day to day web development. Regarding teaching them, we thought it would be a good idea to introduce students to the popular JS &amp; PHP frameworks in the last lectures , but there was no time left. Maybe next year. To sum up, the course content ended up being (I’m listing client-side matters more extensively, since they are also the focus of this blog): General stuff about web application architecture and how the HTTP protocol works We presented a small web application example (an AJAX shopping cart) in order for the students to get an idea about how everything clicks together Markup languages SGML DTDs HTML and XHTML Basic structure of an (X)HTML document Content model, block vs inline elements Basic HTML elements headings &amp; paragraphs lists (ordered, unordered, definition lists) tables grouping elements (div &amp; span) Doctypes, the HTML5 doctype The incentives behind XHTML &amp; the future ((X)HTML 5) (X)HTML Validation HTML forms How forms work, GET vs POST Form controls, shared attributes The various input types (+ the new ones HTML5 brings) Other form controls (buttons, lists, textareas) Basic form accessibility (labels &amp; fieldsets) Working with Multimedia (old methods, HTML5 video &amp; audio elements, comparison) XML and XPath, XQuery, XSLT CSS CSS standards CSS rules Validation Adding CSS to a page (linking/embedding methods) Media targeting (The media attribute, @media rules, media queries) CSS selectors Introduction to the DOM Basic selectors (Universal selector, Type selector, Class selector, Id selector) Classes vs Ids Attribute selectors (all 6) Pseudo-classes (including most of the CSS3 ones) Pseudo-elements Simple selectors &amp; simple selector sequences Combinators (all 4) Selector grouping XML namespaces &amp; CSS Cascading &amp; Inheritance The problem: Conflicts Specificity Origin !important Inheritance The special value inherit Properties &amp; values Keywords Numerical values &amp; units Colors (including CSS3 colors) How shorthands work Unsupported values &amp; providing fallbacks Box model width &amp; height Block level &amp; inline level elements (reminder from the HTML lectures) The display property border padding margin Positioning The position property Positioning types (absolute, relative, fixed) z-index float Problems with floats, the clear property Generated content ::before and ::after Static generated content Dynamic generated content (attributes &amp; counters) JavaScript Adding JS to a document Separation of concerns A first, annotated, example (a simple script that generates tables of content from headings) Basic syntax rules (including semicolons &amp; semicolon insertion) Variables Operators (including typeof, the comma operator, strict operators, differences of &amp;&amp;/|| in JS) Primitives (String, Number, Boolean, null, undefined) Conversion across primitives Objects The in &amp; delete operators for…in loops Native objects for primitives (eg the literal 5 vs new Number(5)) The global object Functions (including function expressions vs function declarations) this &amp; changing execution context Arrays (including .forEach() traversal) Regular expressions in JavaScript OOP in JavaScript OOP concepts in JS Constructors Inheritance Encapsulation (private, priviledged &amp; public properties) Method overloading JavaScript shortcomings when it comes to OOP for…in loops, inherited properties &amp; [[Enumerable]], .hasOwnProperty() Type detection based on [[Class]] detection (using Object.prototype.toString()) DOM Traversal Node types Selecting elements (getElementById, getElementsByClassName, getElementsByName, querySelector, using XPath to select elements) DOM Manipulation innerHTML, advantages &amp; criticism Events Binding &amp; Removing event handlers Traditional event binding Capturing &amp; bubbling Event objects Event delegation Firing events Custom events What if there’s no mouse? Client side storage Cookies via HTTP headers, cookies in JavaScript Problems with cookies Web storage (localStorage, sessionStorage) Client-side databases BOM The window object, window names Opening new windows Cross-window communication Closing windows, Focusing on windows Cross-origin window communication location &amp; it’s components The history , screen &amp; navigator objects User Agent strings Why you shouldn’t use browser detection Built-in modal windows (alert, confirm, prompt) JavaScript &amp; CSS CSS modification (className &amp; classList, inline styles) CSSStyleDeclaration objects The document.styleSheets collection Switching stylesheets StyleSheet objects CSSStyleRule objects Computed style, getting the computed style Asynchronous execution Timeouts &amp; Intervals Background workers Graphics creation (canvas) A brief mention of WebGL (we also showed the video of Google’s web based DOOM game) Best practices When JS is disabled Feature detection Regular expressions Ajax (including data interchange formats, like JSON, other async data transmission techniques, including dynamic script loading &amp; JSONP, usability concerns) SVG Server side web development PHP (also covering OOP in PHP extensively) Database driven websites State &amp; session management REST SOAP Web application security Note: For brevity reasons, the lists above do not include introductory stuff such as: What’s X? A brief history of X Why use X? etc Lessons learned It’s very hard to momentarily change your mindset and try to imagine that you live in a modern, fully standards-based web development world, where old browsers, proprietary stuff, hacks and compatibility workarounds have no place. A world where IE doesn’t exist. However, it’s the world that all our material assumed, for the reasons stated above. And it’s beautiful, so much that it becomes addictive and makes you hate all these bugs &amp; incompatibilities that we have to face today even more. Homework The students were given 3 assignments throughout the semester, each covering: 1st assignment: HTML, CSS, XPath, XSLT 2nd assignment: JavaScript, Ajax, SVG 3rd assignment: Server side web dev + CSS, JavaScript, Ajax These homeworks accounted for 30% of their final grade (10% each), which probably should have been more. We searched for exercises on these topics from other universities but couldn’t find anything, so we made our own. I’ve translated them, in case someone finds them useful, given that there’s a great shortage of such material in the intertubes. You can get them through the links below, along with their complementary files. 1st assignment [ pdf ] [ files ] I think 1.A and 1.B are excellent exercises to make the students fully understand how CSS selectors work and avoid them resulting to only use the 4-5 basic ones just because they don’t understand the rest (like many web developers do). It’s a pity that many of them resulted to online scripts for the conversion (but luckily it was easy to spot: These answers were way more verbose than the corresponding “handmade” ones, and in some cases even incorrect!) I also think 1.C is an excellent exercise for cascading &amp; inheritance practice. Some of the cases were even quite tricky (for instance, the way specificity works for :not() or how grouping works if one of the selectors is invalid) and treated almost all factors that someone should know to predict which rule …overrules. It’s important however that the student justifies the answer, because otherwise they can just test it in a browser and write down the result, without understanding why. I’m not sure yet if freeform questions were a good idea, but (hopefully) they got them to practice their critical thinking and do some research (we hadn’t presented :checked and :lang() in class). We didn’t expect many to get the 3rd one right, but we were pleasantly surprised. What I like in 3.A is that I believe it enforces the Separation of Concerns guideline, since they cannot alter the HTML file (something even professionals commonly do to get something done, the quick &amp; dirty way…) so they have to move all presentation to the CSS file. It also contained a quite tricky part: Maintaining state without JavaScript, by utilizing the :checked pseudo-class and some combinators (a technique made popular quite recently by Ryan Seddon ). Obviously, this is not a good way to change views in a photo gallery (too much wasted bandwidth), but it was perfect as a CSS exercise. To my surprise, more than half of the students got it right, which indicates that we probably did a good job explaining CSS Selectors :) 2nd assignment [ pdf ] [ files ] I like exercise 1 because it teaches them how they can take somebody else’s work, extend it and make it more generic and useful. This is something that’s frequently done in web development. By the way, the deviation in the solutions was quite interesting. Others had implemented a recursive algorithm, others approached it in an Object Oriented manner and others took the classic iterative route. Exercise 2 lets them practice event delegation, unobtrusive progressive enhancement via JavaScript, decisions to improve performance (and still, it’s unbelievable how many students made choices that were obviously terrible performance-wise. I still remember one script that created another DOM element on every mouseover !) Exercise 3 combines many of the technologies they learned in the previous lectures. It also lets them practice their critical thinking by comparing the methods afterwards. Most students picked the CSS method, which would also be my choice, for such a simple bar chart (however, anything rational got full points, I don’t think there’s a correct answer here, it depends on many factors). I like exercise 4 because it introduces them to the concept of writing JavaScript that is intended to be used by other developers, and not just in a particular project (along with 2 perhaps). However, none of the students fully understood what it was about. All of them fired the HTTP request when ajaxForm() was called and most of them also implemented callback() and errorCallback(), which wasn’t supposed to be their job. Exercise 5, besides serving well as regular JavaScript practice, it also lets them learn more about cutting edge technologies such as localStorage , Web databases or offline web apps . 3rd assignment [ pdf ] [ files ] In this assignment, the students practiced in PHP, combined everything else they’ve learned and understood better how everything clicks together to bring a fully-fledged web application to life. We didn’t get many submissions, since most students were busy with other assignments these days but most of the ones we got were awesome, I had an extremely hard time picking the best one. Lessons learned Most mistakes are not very original: They tend to appear over and over again in unrelated assignments. Most of them are caused either by ambiguities in the description or because the student didn’t bother to read all of it. Also, the most frequent excuse for not doing something right is “it wasn’t in the description!”. So, they have to be as detailed as possible, including even stuff that’s obvious to someone more experienced. Plagiarism is not a myth, but a real and frequent problem. Students copy from other students, from scripts posted online and from any source they can get their hands on. :( However, only teaching the standards makes it much easier to spot (at least when it comes to copying from the internet) since most scripts posted online have to account for browser incompatibilities. Labs We only held 3 hands-on lectures (2 hours each), due to time availability issues of everyone involved in the course. I taught the first 2 and another TA was responsible for the 3rd one. Details below: 1st lab [ final result ] The students had to write an HTML file for the single page personal website of some fictional web developer and then use CSS to style it in a certain way. The process was guided, in order to keep all of them on the same track. The site was carefully designed to demonstrate many key CSS concepts &amp; features at once. 2nd lab [ final result ] [ JS code ] [ incomplete JS code ] The students were given an HTML and a CSS file and they had to fill in a .js file that had some parts missing (replaced by TODO comments as placeholders) to complete a very simple ajax rating widget. Lessons learned Never provide downloadable slides with the things the students must write by themselves prior to the lecture. They’ll just copy-paste everything from the pdf, even if they have to fix spacing afterwards. If you absolutely have to, make sure the text is not selectable. It takes students far more time to write code than you planned for When the students don’t understand something, most of them won’t ask. :( It’s best if you personally explain things to anyone having difficulties, but there’s usually not enough time for that Personal aftermath I found out that I love teaching. Successfully helping a student with a problem they had or something they did not understand was sometimes enough to make my day. Preparing material for the course --although exhausting-- was one of the most interesting and creative things I have ever done. Even the actual teaching is thrilling. It’s very challenging to try to keep the students’ interest, since most of them will resort to chatting with their buddies instead of paying attention way more easily than professionals would during a conference talk. However, if you manage to do so, it can be quite rewarding. I hate grading. It’s boring, time-consuming, carries a lot of responsibility and you have to ensure every point you deduct is justified, because you might have to defend your judgement in case a student complains. Sometimes it can also freak you out completely (“OMGWTF, how could they understand it so wrong?? Why didn’t they ask?”) These strips sum it up perfectly (and with a good dose of humor):</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Organizing a university course on modern Web development</title>
  <link>https://lea.verou.me/2010/07/organizing-a-university-course-on-modern-web-development/</link>
  <pubDate>Sun, 17 May 2026 00:45:08 +0200</pubDate>
  <description>About a year ago, prof. Vasilis Vassalos of Athens University of Economics and Business approached me and asked for my help in a new course they were preparing for their Computer Science department , which would introduce 4th year undergrads to various web development aspects. Since I was always complaining about how outdated higher education is when it comes to web development, I saw it as my chance to help things change for the better, so I agreed without a second thought. This is one of the main reasons I didnt have time to write many blog posts for the past months: This activity took up all my spare time. However, it proved to be an interesting and enlightening experience, in more than one ways. In this blog post Ill describe the dilemmas we faced, the decisions we made and the insights I gained throughout these 6 months, with the hope that theyll prove to be useful for anyone involved in something similar. Table of contents Content Homework Labs Personal aftermath Content The goals of a university course differ from the ones of a professional seminar or conference session in many ways, the key one being that most of its students will (professionally) utilize the things they learned in the future and not right after they walk away from class. So, the stuff being taught must be useful even after a couple years have passed. Also, issues of the present might not be issues of the future and what isnt possible today (due to browser support issues) will probably be tomorrow. These observations led us to decide against teaching proprietary stuff. Instead, we only included things which come with a specification that has reached a fairly stable state (with the exception of very widespread non-standard stuff, such as innerHTML ). We also decided not to address workarounds and browser incompatibilities at all , since these would probably be out of date in a few years. Also because, if we teach everything else right, they should be able to learn these by themselves, if needed (we did teach feature detection techniques though, those are timeless ;-)). We also included many cutting edge topics (CSS3, HTML5, ES5, SVG) since we believe that they will be necessary tools of the trade tomorrow . To be pragmatic however, we did not teach stuff that no browser has implemented yet , besides perhaps a brief mention. To make things easier for the students, we used Firefox 3.6 for everything. We tested their assignments there, we used it to present something in the labs etc. Why Firefox? Its at a quite good level of standards compliance and implements many modern technologies &amp; features Fewer bugs (Webkit implements stuff faster, but in more buggy ways) It has the best development tools (Firebug) With Brendan Eich being Mozillas CTO, we all know how progressive Firefox is when it comes to JavaScript. Of course, this doesnt mean its the only right choice. Google Chrome for example would be another good pick. Another useful observation was that 4th year Computer Science students already know programming quite well, especially Java. So, we did not need to go through the basics of programming syntax like introductory books or seminars frequently do. Consequently, we skipped explaining how control structures or operators work in JavaScript or PHP and just focused on their differences from Java and other languages. Another dilemma we faced was whether we should teach stuff on popular frameworks and whether we should allow them in the homeworks. We decided against allowing them in the homeworks because I believe that someone must not use a framework just to skip learning about the intricacies of a language. They should be used after the basics have been consolidated, in order to save time. Also because if everyone skips learning and just uses an abstraction to do the heavy lifting from the very beginning, who will write the abstractions after all? Another reason was that a large portion of every JavaScript framework is about handling cross-browser differences. However, these had no place in our course, so a JS framework wasnt as necessary as it is in day to day web development. Regarding teaching them, we thought it would be a good idea to introduce students to the popular JS &amp; PHP frameworks in the last lectures , but there was no time left. Maybe next year. To sum up, the course content ended up being (Im listing client-side matters more extensively, since they are also the focus of this blog): General stuff about web application architecture and how the HTTP protocol works We presented a small web application example (an AJAX shopping cart) in order for the students to get an idea about how everything clicks together Markup languages SGML DTDs HTML and XHTML Basic structure of an (X)HTML document Content model, block vs inline elements Basic HTML elements headings &amp; paragraphs lists (ordered, unordered, definition lists) tables grouping elements (div &amp; span) Doctypes, the HTML5 doctype The incentives behind XHTML &amp; the future ((X)HTML 5) (X)HTML Validation HTML forms How forms work, GET vs POST Form controls, shared attributes The various input types (+ the new ones HTML5 brings) Other form controls (buttons, lists, textareas) Basic form accessibility (labels &amp; fieldsets) Working with Multimedia (old methods, HTML5 video &amp; audio elements, comparison) XML and XPath, XQuery, XSLT CSS CSS standards CSS rules Validation Adding CSS to a page (linking/embedding methods) Media targeting (The media attribute, @media rules, media queries) CSS selectors Introduction to the DOM Basic selectors (Universal selector, Type selector, Class selector, Id selector) Classes vs Ids Attribute selectors (all 6) Pseudo-classes (including most of the CSS3 ones) Pseudo-elements Simple selectors &amp; simple selector sequences Combinators (all 4) Selector grouping XML namespaces &amp; CSS Cascading &amp; Inheritance The problem: Conflicts Specificity Origin !important Inheritance The special value inherit Properties &amp; values Keywords Numerical values &amp; units Colors (including CSS3 colors) How shorthands work Unsupported values &amp; providing fallbacks Box model width &amp; height Block level &amp; inline level elements (reminder from the HTML lectures) The display property border padding margin Positioning The position property Positioning types (absolute, relative, fixed) z-index float Problems with floats, the clear property Generated content ::before and ::after Static generated content Dynamic generated content (attributes &amp; counters) JavaScript Adding JS to a document Separation of concerns A first, annotated, example (a simple script that generates tables of content from headings) Basic syntax rules (including semicolons &amp; semicolon insertion) Variables Operators (including typeof, the comma operator, strict operators, differences of &amp;&amp;/|| in JS) Primitives (String, Number, Boolean, null, undefined) Conversion across primitives Objects The in &amp; delete operators forin loops Native objects for primitives (eg the literal 5 vs new Number(5)) The global object Functions (including function expressions vs function declarations) this &amp; changing execution context Arrays (including .forEach() traversal) Regular expressions in JavaScript OOP in JavaScript OOP concepts in JS Constructors Inheritance Encapsulation (private, priviledged &amp; public properties) Method overloading JavaScript shortcomings when it comes to OOP forin loops, inherited properties &amp; [[Enumerable]], .hasOwnProperty() Type detection based on [[Class]] detection (using Object.prototype.toString()) DOM Traversal Node types Selecting elements (getElementById, getElementsByClassName, getElementsByName, querySelector, using XPath to select elements) DOM Manipulation innerHTML, advantages &amp; criticism Events Binding &amp; Removing event handlers Traditional event binding Capturing &amp; bubbling Event objects Event delegation Firing events Custom events What if theres no mouse? Client side storage Cookies via HTTP headers, cookies in JavaScript Problems with cookies Web storage (localStorage, sessionStorage) Client-side databases BOM The window object, window names Opening new windows Cross-window communication Closing windows, Focusing on windows Cross-origin window communication location &amp; its components The history , screen &amp; navigator objects User Agent strings Why you shouldnt use browser detection Built-in modal windows (alert, confirm, prompt) JavaScript &amp; CSS CSS modification (className &amp; classList, inline styles) CSSStyleDeclaration objects The document.styleSheets collection Switching stylesheets StyleSheet objects CSSStyleRule objects Computed style, getting the computed style Asynchronous execution Timeouts &amp; Intervals Background workers Graphics creation (canvas) A brief mention of WebGL (we also showed the video of Googles web based DOOM game) Best practices When JS is disabled Feature detection Regular expressions Ajax (including data interchange formats, like JSON, other async data transmission techniques, including dynamic script loading &amp; JSONP, usability concerns) SVG Server side web development PHP (also covering OOP in PHP extensively) Database driven websites State &amp; session management REST SOAP Web application security Note: For brevity reasons, the lists above do not include introductory stuff such as: Whats X? A brief history of X Why use X? etc Lessons learned Its very hard to momentarily change your mindset and try to imagine that you live in a modern, fully standards-based web development world, where old browsers, proprietary stuff, hacks and compatibility workarounds have no place. A world where IE doesnt exist. However, its the world that all our material assumed, for the reasons stated above. And its beautiful, so much that it becomes addictive and makes you hate all these bugs &amp; incompatibilities that we have to face today even more. Homework The students were given 3 assignments throughout the semester, each covering: 1st assignment: HTML, CSS, XPath, XSLT 2nd assignment: JavaScript, Ajax, SVG 3rd assignment: Server side web dev + CSS, JavaScript, Ajax These homeworks accounted for 30% of their final grade (10% each), which probably should have been more. We searched for exercises on these topics from other universities but couldnt find anything, so we made our own. Ive translated them, in case someone finds them useful, given that theres a great shortage of such material in the intertubes. You can get them through the links below, along with their complementary files. 1st assignment [ pdf ] [ files ] I think 1.A and 1.B are excellent exercises to make the students fully understand how CSS selectors work and avoid them resulting to only use the 4-5 basic ones just because they dont understand the rest (like many web developers do). Its a pity that many of them resulted to online scripts for the conversion (but luckily it was easy to spot: These answers were way more verbose than the corresponding handmade ones, and in some cases even incorrect!) I also think 1.C is an excellent exercise for cascading &amp; inheritance practice. Some of the cases were even quite tricky (for instance, the way specificity works for :not() or how grouping works if one of the selectors is invalid) and treated almost all factors that someone should know to predict which rule overrules. Its important however that the student justifies the answer, because otherwise they can just test it in a browser and write down the result, without understanding why. Im not sure yet if freeform questions were a good idea, but (hopefully) they got them to practice their critical thinking and do some research (we hadnt presented :checked and :lang() in class). We didnt expect many to get the 3rd one right, but we were pleasantly surprised. What I like in 3.A is that I believe it enforces the Separation of Concerns guideline, since they cannot alter the HTML file (something even professionals commonly do to get something done, the quick &amp; dirty way) so they have to move all presentation to the CSS file. It also contained a quite tricky part: Maintaining state without JavaScript, by utilizing the :checked pseudo-class and some combinators (a technique made popular quite recently by Ryan Seddon ). Obviously, this is not a good way to change views in a photo gallery (too much wasted bandwidth), but it was perfect as a CSS exercise. To my surprise, more than half of the students got it right, which indicates that we probably did a good job explaining CSS Selectors :) 2nd assignment [ pdf ] [ files ] I like exercise 1 because it teaches them how they can take somebody elses work, extend it and make it more generic and useful. This is something thats frequently done in web development. By the way, the deviation in the solutions was quite interesting. Others had implemented a recursive algorithm, others approached it in an Object Oriented manner and others took the classic iterative route. Exercise 2 lets them practice event delegation, unobtrusive progressive enhancement via JavaScript, decisions to improve performance (and still, its unbelievable how many students made choices that were obviously terrible performance-wise. I still remember one script that created another DOM element on every mouseover !) Exercise 3 combines many of the technologies they learned in the previous lectures. It also lets them practice their critical thinking by comparing the methods afterwards. Most students picked the CSS method, which would also be my choice, for such a simple bar chart (however, anything rational got full points, I dont think theres a correct answer here, it depends on many factors). I like exercise 4 because it introduces them to the concept of writing JavaScript that is intended to be used by other developers, and not just in a particular project (along with 2 perhaps). However, none of the students fully understood what it was about. All of them fired the HTTP request when ajaxForm() was called and most of them also implemented callback() and errorCallback(), which wasnt supposed to be their job. Exercise 5, besides serving well as regular JavaScript practice, it also lets them learn more about cutting edge technologies such as localStorage , Web databases or offline web apps . 3rd assignment [ pdf ] [ files ] In this assignment, the students practiced in PHP, combined everything else theyve learned and understood better how everything clicks together to bring a fully-fledged web application to life. We didnt get many submissions, since most students were busy with other assignments these days but most of the ones we got were awesome, I had an extremely hard time picking the best one. Lessons learned Most mistakes are not very original: They tend to appear over and over again in unrelated assignments. Most of them are caused either by ambiguities in the description or because the student didnt bother to read all of it. Also, the most frequent excuse for not doing something right is it wasnt in the description!. So, they have to be as detailed as possible, including even stuff thats obvious to someone more experienced. Plagiarism is not a myth, but a real and frequent problem. Students copy from other students, from scripts posted online and from any source they can get their hands on. :( However, only teaching the standards makes it much easier to spot (at least when it comes to copying from the internet) since most scripts posted online have to account for browser incompatibilities. Labs We only held 3 hands-on lectures (2 hours each), due to time availability issues of everyone involved in the course. I taught the first 2 and another TA was responsible for the 3rd one. Details below: 1st lab [ final result ] The students had to write an HTML file for the single page personal website of some fictional web developer and then use CSS to style it in a certain way. The process was guided, in order to keep all of them on the same track. The site was carefully designed to demonstrate many key CSS concepts &amp; features at once. 2nd lab [ final result ] [ JS code ] [ incomplete JS code ] The students were given an HTML and a CSS file and they had to fill in a .js file that had some parts missing (replaced by TODO comments as placeholders) to complete a very simple ajax rating widget. Lessons learned Never provide downloadable slides with the things the students must write by themselves prior to the lecture. Theyll just copy-paste everything from the pdf, even if they have to fix spacing afterwards. If you absolutely have to, make sure the text is not selectable. It takes students far more time to write code than you planned for When the students dont understand something, most of them wont ask. :( Its best if you personally explain things to anyone having difficulties, but theres usually not enough time for that Personal aftermath I found out that I love teaching. Successfully helping a student with a problem they had or something they did not understand was sometimes enough to make my day. Preparing material for the course --although exhausting-- was one of the most interesting and creative things I have ever done. Even the actual teaching is thrilling. Its very challenging to try to keep the students interest, since most of them will resort to chatting with their buddies instead of paying attention way more easily than professionals would during a conference talk. However, if you manage to do so, it can be quite rewarding. I hate grading. Its boring, time-consuming, carries a lot of responsibility and you have to ensure every point you deduct is justified, because you might have to defend your judgement in case a student complains. Sometimes it can also freak you out completely (OMGWTF, how could they understand it so wrong?? Why didnt they ask?) These strips sum it up perfectly (and with a good dose of humor):</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>&quot;Wow, Mona Lisa with pure CSS!&quot;</title>
  <link>https://lea.verou.me/2010/05/wow-mona-lisa-with-pure-css/</link>
  <pubDate>Sun, 17 May 2026 00:45:07 +0200</pubDate>
  <description>There has been a recent flood of CSS experiments that utilize CSS3 features to convert some meaningless markup to spectacular pictures. It all started when David Desandro used CSS3 to draw the Opera logo . This seemed to inspire lots of other folks who created similar demos: Pure CSS icons Create Social Media Icons in pure CSS CSS flower Curtis CSS typeface CSS3 Gradients coffee cup I can certainly share their enthusiasm and I am also amazed by their results. Besides that, I think that pushing CSS3 to the edge like that, helps us understand the spec better, which leads us to find and file browser bugs or write comments regarding the spec itself. Filing bugs is crucial at this stage, with all browser vendors gradually adding experimental --and frequently buggy-- CSS3 support to their products. Also, don’t get me wrong: I can easily see the benefits of reducing the number of images in a web application interface (far quicker/easier modifications, less HTTP requests and most of the time, less bandwidth). However, I’m afraid we’re losing sight of the big picture. These aren’t demos that are or will ever be legitimate CSS use cases. Even after universal CSS3 browser support is achieved, they would (and should) still be considered “hacks”. Almost all the arguments pro their usage also apply to more suitable ways of including images in web applications: Fewer HTTP requests : Same with any kind of embedded image (data URIs, inline SVG and so on) Scalable : Same with SVG and symbols embedded in custom fonts Easier to modify: Same with SVG Less bandwidth (in some cases): Same with SVG (and it can be cached too, when not inline) And apart from these, these illustrations require non-semantic crap to be included in the markup which, besides issues of theoretical purity, makes it harder for other people to use them. As for the graceful degradation argument, yes, this does only apply to CSS “images”. But in this case, is it really an advantage? I seriously doubt it. People won’t notice rounded corners if they’re missing from an interface, but they’re definitely going to notice a blocky Opera logo. And they’re not used in thinking that their browser has something to do with how an image renders, so they’ll just blame the website. CSS is supposed to enhance the presentation of a document or interface, not to be (ab)used for the creation of illustrations from scratch. It’s supposed to separate presentation from structure, not generate stuff. There are other technologies that are far more suitable for this (*cough*SVG*cough*). I think we should use our energy and creativity to make CSS3 demos that people will actually use in the future when all this is fully supported, not stuff doomed to be eternally considered hackery. “Where should we draw the line?” one might ask. For example, is a Pure CSS analog clock a CSS ab use case? Or even my own CSS iPhone keyboard ? Now that’s a good question! A rule of thumb seems to be “if it inherently (=not due to browser support issues) involves a bunch of empty (or with meaningless content) HTML elements, then that’s a bad sign” but that might be overly strict. What’s your take on it? Disclaimer: Yes, I’m fully aware that most of the time, such experiments are created just for fun by their (very talented) authors, which are perfectly aware of all the things mentioned above. However, I’ve also grown tired of reading comments by people that seem to to think that “This is the future of the web!”. Let’s hope it’s not.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>&quot;Wow, Mona Lisa with pure CSS!&quot;</title>
  <link>https://lea.verou.me/2010/05/wow-mona-lisa-with-pure-css/</link>
  <pubDate>Sun, 17 May 2026 00:45:07 +0200</pubDate>
  <description>There has been a recent flood of CSS experiments that utilize CSS3 features to convert some meaningless markup to spectacular pictures. It all started when David Desandro used CSS3 to draw the Opera logo . This seemed to inspire lots of other folks who created similar demos: Pure CSS icons Create Social Media Icons in pure CSS CSS flower Curtis CSS typeface CSS3 Gradients coffee cup I can certainly share their enthusiasm and I am also amazed by their results. Besides that, I think that pushing CSS3 to the edge like that, helps us understand the spec better, which leads us to find and file browser bugs or write comments regarding the spec itself. Filing bugs is crucial at this stage, with all browser vendors gradually adding experimental --and frequently buggy-- CSS3 support to their products. Also, dont get me wrong: I can easily see the benefits of reducing the number of images in a web application interface (far quicker/easier modifications, less HTTP requests and most of the time, less bandwidth). However, Im afraid were losing sight of the big picture. These arent demos that are or will ever be legitimate CSS use cases. Even after universal CSS3 browser support is achieved, they would (and should) still be considered hacks. Almost all the arguments pro their usage also apply to more suitable ways of including images in web applications: Fewer HTTP requests : Same with any kind of embedded image (data URIs, inline SVG and so on) Scalable : Same with SVG and symbols embedded in custom fonts Easier to modify: Same with SVG Less bandwidth (in some cases): Same with SVG (and it can be cached too, when not inline) And apart from these, these illustrations require non-semantic crap to be included in the markup which, besides issues of theoretical purity, makes it harder for other people to use them. As for the graceful degradation argument, yes, this does only apply to CSS images. But in this case, is it really an advantage? I seriously doubt it. People wont notice rounded corners if theyre missing from an interface, but theyre definitely going to notice a blocky Opera logo. And theyre not used in thinking that their browser has something to do with how an image renders, so theyll just blame the website. CSS is supposed to enhance the presentation of a document or interface, not to be (ab)used for the creation of illustrations from scratch. Its supposed to separate presentation from structure, not generate stuff. There are other technologies that are far more suitable for this (*cough*SVG*cough*). I think we should use our energy and creativity to make CSS3 demos that people will actually use in the future when all this is fully supported, not stuff doomed to be eternally considered hackery. Where should we draw the line? one might ask. For example, is a Pure CSS analog clock a CSS ab use case? Or even my own CSS iPhone keyboard ? Now thats a good question! A rule of thumb seems to be if it inherently (=not due to browser support issues) involves a bunch of empty (or with meaningless content) HTML elements, then thats a bad sign but that might be overly strict. Whats your take on it? Disclaimer: Yes, Im fully aware that most of the time, such experiments are created just for fun by their (very talented) authors, which are perfectly aware of all the things mentioned above. However, Ive also grown tired of reading comments by people that seem to to think that This is the future of the web!. Lets hope its not.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On CSS counters plus a CSS3 Reversi UI</title>
  <link>https://lea.verou.me/2010/04/on-css-counters-plus-a-css3-reversi-ui/</link>
  <pubDate>Sun, 17 May 2026 00:45:05 +0200</pubDate>
  <description>CSS Counters have a lot more potential than most web developers seem to think. The common use case consists of something like: somecontainer { counter-reset: foocount; } Ε { counter-increment: foocount; } Ε::before { content: counter(foocount) &quot;. &quot;; } commonly used to add numbering to section headings or re-create an &#39;s counters in order to style them (since browser support for ::marker is ridiculous). Have you ever thought of applying the counter to different elements than the ones being counted? This way we’re able to count elements and display their total count somewhere with CSS alone! (and with the variety of selectors in CSS3, I see great potential here…). I’m referring to something like: ul { counter-reset:foo; } li { counter-increment:foo; } p::after { content:counter(foo); } From my tests, this works flawlessly in Firefox, Safari, Opera and Chrome (I’ve only checked the latest stable though), as long as the element that displays the count comes after the elements being counted (in the markup) . Another underutilized aspect of CSS counters (well, far less underused than the above, but still) is how we can combine multiple in the same pseudoelement. For instance, to count rows and cells of a table and display the count inside each cell: table { counter-reset:row; } tr { counter-increment:row; counter-reset:cell; } td { counter-increment:cell; } td::after { content:counter(row, upper-alpha) counter(cell); } Which displays counters like A1, A2, A3, B1, B2, B3, etc in the cells. When the content property is more properly implemented, you wouldn’t even need the last rule. Last but not least, a CSS3 Reversi UI (no images used!) I created a while ago that demonstrates the above (and various other things, like --finally-- a use case for :empty :P ). Looks fine only in Firefox and Opera 10.5, due to lack of support for inset box shadows in Safari and buggy support in Chrome. Works fine in all 4 of them (IE is out of the question anyway). The displayed counts of each player’s pieces (top right corner) are just CSS counters. Same goes for every cell’s name. This is mostly a proof of concept, since it’s impossible to determine if someone won by CSS alone, so we would have to count the pieces in JS too. As a game it’s not finalized, you are basically only able to play against yourself and it doesn’t know when somebody won, so it’s not very useful or enjoyable. If someone wants to take it up and develop it further be my guest. Note to avoid confusion: CSS Counters are not CSS 3. They are perfectly valid CSS 2.1 . The “CSS3” in the title (“CSS3 Reversi”) is due to other techniques used in it’s UI.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On CSS counters plus a CSS3 Reversi UI</title>
  <link>https://lea.verou.me/2010/04/on-css-counters-plus-a-css3-reversi-ui/</link>
  <pubDate>Sun, 17 May 2026 00:45:05 +0200</pubDate>
  <description>CSS Counters have a lot more potential than most web developers seem to think. The common use case consists of something like: somecontainer { counter-reset: foocount; } { counter-increment: foocount; } ::before { content: counter(foocount) &quot;. &quot;; } commonly used to add numbering to section headings or re-create an &#39;s counters in order to style them (since browser support for ::marker is ridiculous). Have you ever thought of applying the counter to different elements than the ones being counted? This way were able to count elements and display their total count somewhere with CSS alone! (and with the variety of selectors in CSS3, I see great potential here). Im referring to something like: ul { counter-reset:foo; } li { counter-increment:foo; } p::after { content:counter(foo); } From my tests, this works flawlessly in Firefox, Safari, Opera and Chrome (Ive only checked the latest stable though), as long as the element that displays the count comes after the elements being counted (in the markup) . Another underutilized aspect of CSS counters (well, far less underused than the above, but still) is how we can combine multiple in the same pseudoelement. For instance, to count rows and cells of a table and display the count inside each cell: table { counter-reset:row; } tr { counter-increment:row; counter-reset:cell; } td { counter-increment:cell; } td::after { content:counter(row, upper-alpha) counter(cell); } Which displays counters like A1, A2, A3, B1, B2, B3, etc in the cells. When the content property is more properly implemented, you wouldnt even need the last rule. Last but not least, a CSS3 Reversi UI (no images used!) I created a while ago that demonstrates the above (and various other things, like --finally-- a use case for :empty :P ). Looks fine only in Firefox and Opera 10.5, due to lack of support for inset box shadows in Safari and buggy support in Chrome. Works fine in all 4 of them (IE is out of the question anyway). The displayed counts of each players pieces (top right corner) are just CSS counters. Same goes for every cells name. This is mostly a proof of concept, since its impossible to determine if someone won by CSS alone, so we would have to count the pieces in JS too. As a game its not finalized, you are basically only able to play against yourself and it doesnt know when somebody won, so its not very useful or enjoyable. If someone wants to take it up and develop it further be my guest. Note to avoid confusion: CSS Counters are not CSS 3. They are perfectly valid CSS 2.1 . The CSS3 in the title (CSS3 Reversi) is due to other techniques used in its UI.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>MySQL: Are you actually utilizing your indexes?</title>
  <link>https://lea.verou.me/2010/03/mysql-are-you-actually-utilizing-your-indexes/</link>
  <pubDate>Sun, 17 May 2026 00:45:04 +0200</pubDate>
  <description>This might seem elementary to those of you that are DBAs or something similar, but it was fascinating to find out (not to mention it greatly helped what I had to do), so I decided to post it, in case it helps someone else too. A few moments ago I found out that whereas a query along the lines of… SELECT title, COUNT(1) as replies FROM post INNER JOIN thread USING(threadid) WHERE **UNIX\_TIMESTAMP(NOW()) - post.dateline took a whopping ~10 seconds on a post table of around 2,000,000 rows and a thread table of around 40,000 rows, the following: SELECT title, COUNT(1) as replies FROM post INNER JOIN thread USING(threadid) WHERE **post.dateline &gt; UNIX\_TIMESTAMP(NOW()) - 86400** GROUP BY threadid ORDER BY replies DESC LIMIT 5 took a mere 0.03 seconds ! Probably, MySQL wasn’t able to utilize the B+ tree index of the dateline column in the first query, whereas in the second, things were a bit more obvious to it. This can also be observed by examining the information about the execution plan that EXPLAIN provides: mysql&gt; explain select t.threadid, t.title, count(1) as replies from vb3\_post as p inner join vb3\_thread as t using(threadid) where unix\_timestamp(now()) - p.dateline explain select t.threadid, t.title, count(1) as replies from vb3\_post as p inner join vb3\_thread as t using(threadid) where p.dateline &gt; UNIX\_TIMESTAMP(NOW()) - 86400 group by p.threadid order by replies desc limit 5; +----+-------------+-------+--------+-------------------+----------+---------+------------+------+----------------------------------------------+ | id | select\_type | table | type | possible\_keys | key | key\_len | ref | rows | Extra | +----+-------------+-------+--------+-------------------+----------+---------+------------+------+----------------------------------------------+ | 1 | SIMPLE | p | range | threadid,dateline | dateline | 4 | NULL | 1171 | Using where; Using temporary; Using filesort | | 1 | SIMPLE | t | eq\_ref | PRIMARY | PRIMARY | 4 | p.threadid | 1 | | +----+-------------+-------+--------+-------------------+----------+---------+------------+------+----------------------------------------------+ 2 rows in set (0.00 sec) ``` So, don&#39;t rest assured that MySQL will use your indexes every time it should. It seems that sometimes you have to explicitly point it out.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>MySQL: Are you actually utilizing your indexes?</title>
  <link>https://lea.verou.me/2010/03/mysql-are-you-actually-utilizing-your-indexes/</link>
  <pubDate>Sun, 17 May 2026 00:45:04 +0200</pubDate>
  <description>This might seem elementary to those of you that are DBAs or something similar, but it was fascinating to find out (not to mention it greatly helped what I had to do), so I decided to post it, in case it helps someone else too. A few moments ago I found out that whereas a query along the lines of SELECT title, COUNT(1) as replies FROM post INNER JOIN thread USING(threadid) WHERE **UNIX\_TIMESTAMP(NOW()) - post.dateline took a whopping ~10 seconds on a post table of around 2,000,000 rows and a thread table of around 40,000 rows, the following: SELECT title, COUNT(1) as replies FROM post INNER JOIN thread USING(threadid) WHERE **post.dateline &gt; UNIX\_TIMESTAMP(NOW()) - 86400** GROUP BY threadid ORDER BY replies DESC LIMIT 5 took a mere 0.03 seconds ! Probably, MySQL wasnt able to utilize the B+ tree index of the dateline column in the first query, whereas in the second, things were a bit more obvious to it. This can also be observed by examining the information about the execution plan that EXPLAIN provides: mysql&gt; explain select t.threadid, t.title, count(1) as replies from vb3\_post as p inner join vb3\_thread as t using(threadid) where unix\_timestamp(now()) - p.dateline explain select t.threadid, t.title, count(1) as replies from vb3\_post as p inner join vb3\_thread as t using(threadid) where p.dateline &gt; UNIX\_TIMESTAMP(NOW()) - 86400 group by p.threadid order by replies desc limit 5; +----+-------------+-------+--------+-------------------+----------+---------+------------+------+----------------------------------------------+ | id | select\_type | table | type | possible\_keys | key | key\_len | ref | rows | Extra | +----+-------------+-------+--------+-------------------+----------+---------+------------+------+----------------------------------------------+ | 1 | SIMPLE | p | range | threadid,dateline | dateline | 4 | NULL | 1171 | Using where; Using temporary; Using filesort | | 1 | SIMPLE | t | eq\_ref | PRIMARY | PRIMARY | 4 | p.threadid | 1 | | +----+-------------+-------+--------+-------------------+----------+---------+------------+------+----------------------------------------------+ 2 rows in set (0.00 sec) ``` So, don&#39;t rest assured that MySQL will use your indexes every time it should. It seems that sometimes you have to explicitly point it out.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 structural pseudo-class selector tester</title>
  <link>https://lea.verou.me/2010/03/css3-structural-pseudo-class-selector-tester/</link>
  <pubDate>Sun, 17 May 2026 00:45:03 +0200</pubDate>
  <description>I was doing some research today about how people explain the CSS3 structural* pseudo classes and I stumbled upon this demo by CSS tricks: http://css-tricks.com/examples/nth-child-tester/ I thought the idea is awesome , but lacks a few features: It doesn’t use the native browser algorithm for selecting the elements. Granted, it’s not that tough to code your own properly, but I trust a browser implementation more (IE doesn’t support these altogether, so it’s out of the question anyway). Doesn’t allow you to test for nth-last-child, nth-of-type, nth-last-of-type (and especially the last two are a lot harder to understand for most people) Doesn’t allow you to add/remove list items to see the effects of the selector with different numbers of elements (especially needed if nth-last-child, nth-of-type, nth-last-of-type were involved) So, I decided to code my own. It allows you to test for all 4 nth-something selectors, supports adding/removing elements (the selected elements update instantly) and uses the native browser implementation to select them (so it won’t work on IE and old browsers). Enjoy: CSS3 structural pseudo-class selector tester :) *Yes, :root and :empty also belong to those, but are rarely used. All other structural pseudoclasses are actually shortcuts to some particular case of the aforementioned 4 :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 structural pseudo-class selector tester</title>
  <link>https://lea.verou.me/2010/03/css3-structural-pseudo-class-selector-tester/</link>
  <pubDate>Sun, 17 May 2026 00:45:03 +0200</pubDate>
  <description>I was doing some research today about how people explain the CSS3 structural* pseudo classes and I stumbled upon this demo by CSS tricks: http://css-tricks.com/examples/nth-child-tester/ I thought the idea is awesome , but lacks a few features: It doesnt use the native browser algorithm for selecting the elements. Granted, its not that tough to code your own properly, but I trust a browser implementation more (IE doesnt support these altogether, so its out of the question anyway). Doesnt allow you to test for nth-last-child, nth-of-type, nth-last-of-type (and especially the last two are a lot harder to understand for most people) Doesnt allow you to add/remove list items to see the effects of the selector with different numbers of elements (especially needed if nth-last-child, nth-of-type, nth-last-of-type were involved) So, I decided to code my own. It allows you to test for all 4 nth-something selectors, supports adding/removing elements (the selected elements update instantly) and uses the native browser implementation to select them (so it wont work on IE and old browsers). Enjoy: CSS3 structural pseudo-class selector tester :) *Yes, :root and :empty also belong to those, but are rarely used. All other structural pseudoclasses are actually shortcuts to some particular case of the aforementioned 4 :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Redesign</title>
  <link>https://lea.verou.me/2010/02/redesign/</link>
  <pubDate>Sun, 17 May 2026 00:26:20 +0200</pubDate>
  <description>Was about time, wasn’t it? I wanted a simpler, more minimalistic (and wider!) theme for a while now. The other one was too restrictive. I had designed it when I had absolutely no content, and few changes were made to it afterwards. So, today that I was too sad and furious to do anything productive, I spent a few hours redesigning the blog (creative venting…). Please note that it’s just a few hours’ work (with no mockup), so it’s bound to be a bit rough around the edges. I will refine it more as time goes by. (and just like the previous one, it’s best viewed in more CSS3-supporting browsers, like Firefox, Chrome or Safari. If we can’t use the latest bells n’ whistles in our personal blogs, where can we? ;)) Here’s a screenshot from the previous theme: R.I.P. my first wordpress theme. PS: Yeah, I know I haven’t posted in a while. I have started lots of posts, but didn’t finish any. I hope I’ll have something complete to post soon.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Redesign</title>
  <link>https://lea.verou.me/2010/02/redesign/</link>
  <pubDate>Sun, 17 May 2026 00:26:20 +0200</pubDate>
  <description>Was about time, wasnt it? I wanted a simpler, more minimalistic (and wider!) theme for a while now. The other one was too restrictive. I had designed it when I had absolutely no content, and few changes were made to it afterwards. So, today that I was too sad and furious to do anything productive, I spent a few hours redesigning the blog (creative venting). Please note that its just a few hours work (with no mockup), so its bound to be a bit rough around the edges. I will refine it more as time goes by. (and just like the previous one, its best viewed in more CSS3-supporting browsers, like Firefox, Chrome or Safari. If we cant use the latest bells n whistles in our personal blogs, where can we? ;)) Heres a screenshot from the previous theme: R.I.P. my first wordpress theme. PS: Yeah, I know I havent posted in a while. I have started lots of posts, but didnt finish any. I hope Ill have something complete to post soon.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>A CSS3 learning(?) tool</title>
  <link>https://lea.verou.me/2009/10/a-css3-learning-tool/</link>
  <pubDate>Sun, 17 May 2026 00:26:11 +0200</pubDate>
  <description>In case anyone is interested, this is my take on the “challenge” that Brad Neuberg posted today on Ajaxian . It needs more properties, but it’s very easy to extend. I guess I should also add CSS3 values (RGBA/HSL(A) colors, CSS gradients etc) but oh well, I’m currently in a hurry. I will, if anyone actually finds it useful (?). It didn’t prove much of a challenge actually and I honestly doubt it’s educational value (actually it’s value in general), but it was an interesting thing to do while drinking my first coffee in the morning – I really enjoyed writing it :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>A CSS3 learning(?) tool</title>
  <link>https://lea.verou.me/2009/10/a-css3-learning-tool/</link>
  <pubDate>Sun, 17 May 2026 00:26:11 +0200</pubDate>
  <description>In case anyone is interested, this is my take on the challenge that Brad Neuberg posted today on Ajaxian . It needs more properties, but its very easy to extend. I guess I should also add CSS3 values (RGBA/HSL(A) colors, CSS gradients etc) but oh well, Im currently in a hurry. I will, if anyone actually finds it useful (?). It didnt prove much of a challenge actually and I honestly doubt its educational value (actually its value in general), but it was an interesting thing to do while drinking my first coffee in the morning I really enjoyed writing it :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Bevels in CSS3</title>
  <link>https://lea.verou.me/2009/07/bevels-in-css3/</link>
  <pubDate>Sun, 17 May 2026 00:26:08 +0200</pubDate>
  <description>Yeah, yeah I know, bevels are soooo 1996. And I agree. However, it’s always good to know the capabilities of your tools. Talented designers will know when it’s suitable to use a certain effect and incapable ones will abuse whatever is given to them, so after a lot of thought, I decided to blog about my discovery. Even though not directly mentioned in the spec, CSS3 is capable of easily creating a bevel effect on any element. Moreover, if the element has rounded corners, the bevel follows that as well. Before explaining the technique, let’s think about how a bevel actually gets drawn. It’s essentially two inner shadows, that when combined, create the illusion of a 3d appearance: a light one from the top left corner and a dark one from the bottom right corner. CSS3 includes the ability to create inner shadows, if you specify the keyword “inset” in the box-shadow declaration (currently only supported by Firefox 3.5). Moreover, the CSS3 spec allows for multiple box shadows on the same elements. Now, let’s examine an example (only works in Firefox 3.5): button { background:#f16; color:white; padding:6px 12px 8px 12px; border:none; font-size:18px; -moz-border-radius:10px; -moz-box-shadow: -2px -2px 10px rgba(0,0,0,.25) inset, 2px 2px 10px white inset; } which produces this result: If we want, we can also create a “pressed” button state, in a similar fashion: button:active { -moz-box-shadow: 2px 2px 10px rgba(0,0,0,.25) inset, -2px -2px 10px white inset; padding:7px 11px 7px 13px; } button::-moz-focus-inner which produces this pressed state: See it in action here (only for Firefox 3.5): http://lea.verou.me/demos/css3bevel.html Of course, if implemented in a real world website, you should also add the -webkit- and -o- CSS3 properties to provide a closer effect for the other browsers and be ready for the time when the ones that aren’t implemented yet in them will finally make it (for instance, when Webkit implements inset box shadows, it will work in it as well). Enjoy responsibly . :-)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Bevels in CSS3</title>
  <link>https://lea.verou.me/2009/07/bevels-in-css3/</link>
  <pubDate>Sun, 17 May 2026 00:26:08 +0200</pubDate>
  <description>Yeah, yeah I know, bevels are soooo 1996. And I agree. However, its always good to know the capabilities of your tools. Talented designers will know when its suitable to use a certain effect and incapable ones will abuse whatever is given to them, so after a lot of thought, I decided to blog about my discovery. Even though not directly mentioned in the spec, CSS3 is capable of easily creating a bevel effect on any element. Moreover, if the element has rounded corners, the bevel follows that as well. Before explaining the technique, lets think about how a bevel actually gets drawn. Its essentially two inner shadows, that when combined, create the illusion of a 3d appearance: a light one from the top left corner and a dark one from the bottom right corner. CSS3 includes the ability to create inner shadows, if you specify the keyword inset in the box-shadow declaration (currently only supported by Firefox 3.5). Moreover, the CSS3 spec allows for multiple box shadows on the same elements. Now, lets examine an example (only works in Firefox 3.5): button { background:#f16; color:white; padding:6px 12px 8px 12px; border:none; font-size:18px; -moz-border-radius:10px; -moz-box-shadow: -2px -2px 10px rgba(0,0,0,.25) inset, 2px 2px 10px white inset; } which produces this result: If we want, we can also create a pressed button state, in a similar fashion: button:active { -moz-box-shadow: 2px 2px 10px rgba(0,0,0,.25) inset, -2px -2px 10px white inset; padding:7px 11px 7px 13px; } button::-moz-focus-inner which produces this pressed state: See it in action here (only for Firefox 3.5): http://lea.verou.me/demos/css3bevel.html Of course, if implemented in a real world website, you should also add the -webkit- and -o- CSS3 properties to provide a closer effect for the other browsers and be ready for the time when the ones that arent implemented yet in them will finally make it (for instance, when Webkit implements inset box shadows, it will work in it as well). Enjoy responsibly . :-)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Harness Engineering</title>
  <link>https://martinfowler.com/articles/exploring-gen-ai/harness-engineering.html</link>
  <pubDate>Sun, 17 May 2026 00:25:14 +0200</pubDate>
  <description>Birgitta Böckeler explains why OpenAI&#39;s recent write-up on Harness Engineering is a valuable framing of a key activity in AI-enabled software development. The harness includes context engineering, architectural constraints, and garbage collection of the code base. It&#39;s a serious activity: OpenAI took five months to build their harness. more…</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Harness Engineering</title>
  <link>https://martinfowler.com/articles/exploring-gen-ai/harness-engineering.html</link>
  <pubDate>Sun, 17 May 2026 00:25:14 +0200</pubDate>
  <description>Birgitta Böckeler explains why OpenAI&#39;s recent write-up on Harness Engineering is a valuable framing of a key activity in AI-enabled software development. The harness includes context engineering, architectural constraints, and garbage collection of the code base. It&#39;s a serious activity: OpenAI took five months to build their harness. more</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Bliki: Host Leadership</title>
  <link>https://martinfowler.com/bliki/HostLeadership.html</link>
  <pubDate>Sun, 17 May 2026 00:25:10 +0200</pubDate>
  <description>If you&#39;ve hung around agile circles for long, you&#39;ve probably heard about the concept of servant leadership , that managers should think of themselves as supporting the team, removing blocks, protecting them from the vagaries of corporate life. That&#39;s never sounded quite right to me, and a recent conversation with Kent Beck nailed why - it&#39;s gaslighting. The manager claims to be a servant, but everyone knows who really has the power. My colleague Giles Edwards-Alexander told me about an alternative way of thinking about leadership, one that he came across working with mental-health professionals. This casts the leader as a host: preparing a suitable space, inviting the team in, providing ideas and problems, and then stepping back to let them work. The host looks after the team, rather as the ideal servant leader does, but still has the power to intervene should things go awry. Further Reading Dr Mark McKergow and Helen Bailey wrote a book in 2014. The website hostleadership.com has ongoing information including a blog. McKergow and Bailey have a short article in HR Review that outlines the six roles of engagement of a host leader.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Bliki: Host Leadership</title>
  <link>https://martinfowler.com/bliki/HostLeadership.html</link>
  <pubDate>Sun, 17 May 2026 00:25:10 +0200</pubDate>
  <description>If you&#39;ve hung around agile circles for long, you&#39;ve probably heard about the concept of servant leadership , that managers should think of themselves as supporting the team, removing blocks, protecting them from the vagaries of corporate life. That&#39;s never sounded quite right to me, and a recent conversation with Kent Beck nailed why - it&#39;s gaslighting. The manager claims to be a servant, but everyone knows who really has the power. My colleague Giles Edwards-Alexander told me about an alternative way of thinking about leadership, one that he came across working with mental-health professionals. This casts the leader as a host: preparing a suitable space, inviting the team in, providing ideas and problems, and then stepping back to let them work. The host looks after the team, rather as the ideal servant leader does, but still has the power to intervene should things go awry. Further Reading Dr Mark McKergow and Helen Bailey wrote a book in 2014. The website hostleadership.com has ongoing information including a blog. McKergow and Bailey have a short article in HR Review that outlines the six roles of engagement of a host leader.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Help me: take the color survey</title>
  <link>https://lea.verou.me/2009/04/help-me-take-the-color-survey/</link>
  <pubDate>Sun, 17 May 2026 00:07:28 +0200</pubDate>
  <description>If you are a creative professional, or just passionate about colors, please take my survey: http://bit.ly/colorsurvey It will greatly help me to make a future project of our company more usable (some of its features at least) and it only takes a few minutes (it contains 10-19 questions, depending on your responses). Any suggestions, corrections, questions etc are of course welcome. Thanks a lot in advance to everyone that takes the survey! :D Of course, when it ends and I find the time to analyze the results, I’ll post them here for anyone interested. (Hint: That means that if you are interested in the results, you can promote the survey yourself as well, since more responses = more accurate results)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Advocacy of JavaScript</title>
  <link>https://lea.verou.me/2009/03/advocacy-of-javascript/</link>
  <pubDate>Sun, 17 May 2026 00:07:28 +0200</pubDate>
  <description>I frequently meet these “hardcore” developers that deep (or not so deep) inside them, tend to underestimate JavaScript developers and boast about their own superiority. I’m sure that if you spent an important percentage of your career working with JavaScript and are even barely social, you definitely know what I’m talking about. It’s those desktop application programmers or these back-end developers that tend to consider JavaScript a toy, and try to convince you to engage in “more serious stuff” (if they appreciate you even a little; if they don’t they just mock you endlessly and/or look down on you). Funnily enough, when most of these people are required to write JavaScript for some reason, one of the following happens: They write 2000-style code, which is usually the reason that most of them underestimate JavaScript so much: They think that everybody codes in JavaScript like themselves. They desperately look for “a good library” because “it’s not worth wasting my time to learn that stuff”. They actually learn the darn language and the relevant browser quirks and change their attitude towards JavaScript developers. Douglas Crockford did it much better than me , but I would like to take my turn in arguing against their most frequent claims, if I may. “Javascripters are not really developers.” Oh r’ly? Is it because JavaScript doesn’t follow what you’ve learned to expect from most languages? Well, newsflash: Assembly doesn’t either and every programming language is actually an abstraction to it. It’s in fact much harder to write the same thing in a language that lacks what we’ve learned to expect. Think about low level coding: Even the simplest tasks seem hard. At a smaller extent, it’s the same with JavaScript: Things that are easy to do in other languages, are a pain in JavaScript, even if we leave out the implementation differences across browsers. For instance, in Java there is a built-in class for most common tasks. JavaScript isn’t that rich, and it penalizes you for every external library you use, by forcing your users to download extra Kilobytes of code. JavaScript is probably the only modern language in which short code isn’t only elegance, but also a necessity. Also, in other languages, you only have to deal with one implementation. Even when using Java to code for multiple operating systems, the differences are minor for most applications. With JavaScript, you are dealing with at least 5 implementations with many differences and bugs to circumvent. Writing a piece of code that works in one browser is not good enough, you have to make it work across all major browsers, in all their versions that still have significant market share. And yeah, this is most of the times just as dreadful as it sounds, if not more. Of course, I’m not implying that everyone who wrote a script in JavaScript is a developer, just like everyone that wrote a Hello World application in C++ is not a programmer. JavaScript is notorious for being used mostly by amateurs for the following reasons: Most people that ever wrote a webpage needed something that could only be done with JavaScript. Most of these people weren’t developers and didn’t have any interest in programming. Because of (1) there are many JavaScript tutorials and books around for accomplishing simple tasks, most of them being leftovers from the 2000 era and promote bad code practices. During that era, people didn’t care about nice code, usability, accessibility and cross-browser functionality. They just wanted to get the job done spending the least possible time and they only cared if it worked in Internet Explorer. Most people just copy and paste stuff from the tutorials mentioned in (2), leading to duplicate functionality, bad code, bad usability, complete absence of accessibility and buggy results in browsers other than the target one. This caused JavaScript to be related to these vices although these things were actually caused by abusing the language. “Javascript is a toy, not a real programming language” It may have been a toy in the 2000 era where your mind is still stuck. Currently, browser vendors are constantly adding new features to it, in order to make it able to compete with a fully-fledged programming language and competent front-end developers have been pushing JavaScript to an extent that was unimaginable when it was first introduced. If you are not convinced, pay a visit to Chrome Experiments (as the name suggests, you are advised to use Google Chrome when viewing them). JavaScript is not a light version of Java, nor is it a light version of any programming language. It has a soul of it’s own, so stop comparing it to other languages and pointing out the areas where it lacks. Open your eyes instead to see the areas where it’s superior to all other languages you probably know ( lambda for instance). “How can I respect a language that only lives inside a browser?” Newsflash: You are wrong, again . You can code in JavaScript for the server , create Windows executable files (.exe) , create plugins and extensions for a plethora of applications, and actually even Flash’s ActionScript is based on ECMAScript, a standard that was derived from and currently controls JavaScript implementations. Disclaimers Ah, these are always necessary in rants :) I didn’t have any particular individual in mind when writing this post, so if you think it’s about you, get over it. My memory is too bad to do so anyway. ;) I am not implying that JavaScript is the best programming language around. I actually don’t think there is such a language. My point was that JavaScript is not inferior to the others. That doesn’t mean I consider it superior either. I don’t claim to be a programming guru (anyone who does so is usually ignorant anyway), nor do I claim to be always right. Feel free to argue, if you have thought a valid counterargument. :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Help me: take the color survey</title>
  <link>https://lea.verou.me/2009/04/help-me-take-the-color-survey/</link>
  <pubDate>Sun, 17 May 2026 00:07:28 +0200</pubDate>
  <description>If you are a creative professional, or just passionate about colors, please take my survey: http://bit.ly/colorsurvey It will greatly help me to make a future project of our company more usable (some of its features at least) and it only takes a few minutes (it contains 10-19 questions, depending on your responses). Any suggestions, corrections, questions etc are of course welcome. Thanks a lot in advance to everyone that takes the survey! :D Of course, when it ends and I find the time to analyze the results, Ill post them here for anyone interested. (Hint: That means that if you are interested in the results, you can promote the survey yourself as well, since more responses = more accurate results)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Advocacy of JavaScript</title>
  <link>https://lea.verou.me/2009/03/advocacy-of-javascript/</link>
  <pubDate>Sun, 17 May 2026 00:07:28 +0200</pubDate>
  <description>I frequently meet these hardcore developers that deep (or not so deep) inside them, tend to underestimate JavaScript developers and boast about their own superiority. Im sure that if you spent an important percentage of your career working with JavaScript and are even barely social, you definitely know what Im talking about. Its those desktop application programmers or these back-end developers that tend to consider JavaScript a toy, and try to convince you to engage in more serious stuff (if they appreciate you even a little; if they dont they just mock you endlessly and/or look down on you). Funnily enough, when most of these people are required to write JavaScript for some reason, one of the following happens: They write 2000-style code, which is usually the reason that most of them underestimate JavaScript so much: They think that everybody codes in JavaScript like themselves. They desperately look for a good library because its not worth wasting my time to learn that stuff. They actually learn the darn language and the relevant browser quirks and change their attitude towards JavaScript developers. Douglas Crockford did it much better than me , but I would like to take my turn in arguing against their most frequent claims, if I may. Javascripters are not really developers. Oh rly? Is it because JavaScript doesnt follow what youve learned to expect from most languages? Well, newsflash: Assembly doesnt either and every programming language is actually an abstraction to it. Its in fact much harder to write the same thing in a language that lacks what weve learned to expect. Think about low level coding: Even the simplest tasks seem hard. At a smaller extent, its the same with JavaScript: Things that are easy to do in other languages, are a pain in JavaScript, even if we leave out the implementation differences across browsers. For instance, in Java there is a built-in class for most common tasks. JavaScript isnt that rich, and it penalizes you for every external library you use, by forcing your users to download extra Kilobytes of code. JavaScript is probably the only modern language in which short code isnt only elegance, but also a necessity. Also, in other languages, you only have to deal with one implementation. Even when using Java to code for multiple operating systems, the differences are minor for most applications. With JavaScript, you are dealing with at least 5 implementations with many differences and bugs to circumvent. Writing a piece of code that works in one browser is not good enough, you have to make it work across all major browsers, in all their versions that still have significant market share. And yeah, this is most of the times just as dreadful as it sounds, if not more. Of course, Im not implying that everyone who wrote a script in JavaScript is a developer, just like everyone that wrote a Hello World application in C++ is not a programmer. JavaScript is notorious for being used mostly by amateurs for the following reasons: Most people that ever wrote a webpage needed something that could only be done with JavaScript. Most of these people werent developers and didnt have any interest in programming. Because of (1) there are many JavaScript tutorials and books around for accomplishing simple tasks, most of them being leftovers from the 2000 era and promote bad code practices. During that era, people didnt care about nice code, usability, accessibility and cross-browser functionality. They just wanted to get the job done spending the least possible time and they only cared if it worked in Internet Explorer. Most people just copy and paste stuff from the tutorials mentioned in (2), leading to duplicate functionality, bad code, bad usability, complete absence of accessibility and buggy results in browsers other than the target one. This caused JavaScript to be related to these vices although these things were actually caused by abusing the language. Javascript is a toy, not a real programming language It may have been a toy in the 2000 era where your mind is still stuck. Currently, browser vendors are constantly adding new features to it, in order to make it able to compete with a fully-fledged programming language and competent front-end developers have been pushing JavaScript to an extent that was unimaginable when it was first introduced. If you are not convinced, pay a visit to Chrome Experiments (as the name suggests, you are advised to use Google Chrome when viewing them). JavaScript is not a light version of Java, nor is it a light version of any programming language. It has a soul of its own, so stop comparing it to other languages and pointing out the areas where it lacks. Open your eyes instead to see the areas where its superior to all other languages you probably know ( lambda for instance). How can I respect a language that only lives inside a browser? Newsflash: You are wrong, again . You can code in JavaScript for the server , create Windows executable files (.exe) , create plugins and extensions for a plethora of applications, and actually even Flashs ActionScript is based on ECMAScript, a standard that was derived from and currently controls JavaScript implementations. Disclaimers Ah, these are always necessary in rants :) I didnt have any particular individual in mind when writing this post, so if you think its about you, get over it. My memory is too bad to do so anyway. ;) I am not implying that JavaScript is the best programming language around. I actually dont think there is such a language. My point was that JavaScript is not inferior to the others. That doesnt mean I consider it superior either. I dont claim to be a programming guru (anyone who does so is usually ignorant anyway), nor do I claim to be always right. Feel free to argue, if you have thought a valid counterargument. :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Extend Math.log to allow for bases != e</title>
  <link>https://lea.verou.me/2009/03/extend-mathlog-to-allow-for-bases-e/</link>
  <pubDate>Sun, 17 May 2026 00:07:27 +0200</pubDate>
  <description>As Math.log currently stands, it’s a bit useless. It only calculates natural logarithms (base e). We can easily modify it however, to calculate logarithms of any base: Math.log = (function() { var log = Math.log; return function(n, a) { return log(n)/(a? log(a) : 1); } })(); We can now specify the base as a second parameter, or still use the default one (Math.E) if we don’t specify one, so older scripts won’t break or if we want a shortcut to the natural logarithm. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Extend Math.log to allow for bases != e</title>
  <link>https://lea.verou.me/2009/03/extend-mathlog-to-allow-for-bases-e/</link>
  <pubDate>Sun, 17 May 2026 00:07:27 +0200</pubDate>
  <description>As Math.log currently stands, its a bit useless. It only calculates natural logarithms (base e). We can easily modify it however, to calculate logarithms of any base: Math.log = (function() { var log = Math.log; return function(n, a) { return log(n)/(a? log(a) : 1); } })(); We can now specify the base as a second parameter, or still use the default one (Math.E) if we dont specify one, so older scripts wont break or if we want a shortcut to the natural logarithm. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>100% Cyan in CMYK is NOT rgb(0,255,255)!!</title>
  <link>https://lea.verou.me/2009/03/100-cyan-in-cmyk-is-not-rgb0255255/</link>
  <pubDate>Sun, 17 May 2026 00:07:26 +0200</pubDate>
  <description>As I mentioned in an earlier post of mine, I have to create a color picker, so I’ve already started to write the code for the Color class it’s going to use. I need it to natively support RGB, HSL, Lab and CMYK. And the latter part is causing unexpected trouble. It seems that there is the notion out there that conversion from CMYK to RGB is easy. Newsflash: It’s not . As every graphic designer knows, the CMYK color gamut is smaller than the the RGB color gamut (even the sRGB color gamut). You can’t take a CMYK color and convert it to an out-of-CMYK-gamut RGB color! That’s nonsense! And it’s precisely what most conversion algorithms and color pickers out there do! Even Adobe Kuler !!! Since yesterday, I’ve studied dozens of algorithms and color pickers that claim to do CMYK -&gt; RGB conversion, and every single one of them is wrong . You can test a color picker that claims to support CMYK, or a CMYK RGB conversion algorithm in the following simple way: Test how it converts the color CMYK(100%, 0, 0, 0) to RGB. If the result is rgb(0,255,255) then the algorithm is crap. rgb(0, 255, 255) is neither the same color, nor is it even in the CMYK color gamut! So basically, these algorithms convert a CMYK color to an RGB color that is outside of the CMYK color gamut! A color that cannot be represented with CMYK is supposed to be the result of a CMYK-&gt;RGB conversion? This is madness! So far the only CMYK -&gt; RGB converter that I’ve seen and does it right, is the one used by Adobe CS products (Photoshop, Illustrator, etc). And that makes me wonder why Kuler does it the wrong way, since they have already figured the algorithm! It’s crazy! What’s even more strange is that I can’t even find which sRGB colors are usually out of the CMYK color gamut, so that I can adjust the algorithm I use properly (even if it just clipped the color to the nearest in-gamut one, it would be an improvement). I’ve been searching since yesterady even for that with no luck. I hope I don’t end up using the wrong algorithm myself too…</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>100% Cyan in CMYK is NOT rgb(0,255,255)!!</title>
  <link>https://lea.verou.me/2009/03/100-cyan-in-cmyk-is-not-rgb0255255/</link>
  <pubDate>Sun, 17 May 2026 00:07:26 +0200</pubDate>
  <description>As I mentioned in an earlier post of mine, I have to create a color picker, so Ive already started to write the code for the Color class its going to use. I need it to natively support RGB, HSL, Lab and CMYK. And the latter part is causing unexpected trouble. It seems that there is the notion out there that conversion from CMYK to RGB is easy. Newsflash: Its not . As every graphic designer knows, the CMYK color gamut is smaller than the the RGB color gamut (even the sRGB color gamut). You cant take a CMYK color and convert it to an out-of-CMYK-gamut RGB color! Thats nonsense! And its precisely what most conversion algorithms and color pickers out there do! Even Adobe Kuler !!! Since yesterday, Ive studied dozens of algorithms and color pickers that claim to do CMYK -&gt; RGB conversion, and every single one of them is wrong . You can test a color picker that claims to support CMYK, or a CMYK RGB conversion algorithm in the following simple way: Test how it converts the color CMYK(100%, 0, 0, 0) to RGB. If the result is rgb(0,255,255) then the algorithm is crap. rgb(0, 255, 255) is neither the same color, nor is it even in the CMYK color gamut! So basically, these algorithms convert a CMYK color to an RGB color that is outside of the CMYK color gamut! A color that cannot be represented with CMYK is supposed to be the result of a CMYK-&gt;RGB conversion? This is madness! So far the only CMYK -&gt; RGB converter that Ive seen and does it right, is the one used by Adobe CS products (Photoshop, Illustrator, etc). And that makes me wonder why Kuler does it the wrong way, since they have already figured the algorithm! Its crazy! Whats even more strange is that I cant even find which sRGB colors are usually out of the CMYK color gamut, so that I can adjust the algorithm I use properly (even if it just clipped the color to the nearest in-gamut one, it would be an improvement). Ive been searching since yesterady even for that with no luck. I hope I dont end up using the wrong algorithm myself too</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Cross browser, imageless linear gradients</title>
  <link>https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/</link>
  <pubDate>Sun, 17 May 2026 00:07:25 +0200</pubDate>
  <description>I have to write a color picker in the near future and I wanted it to have those little gradients on top of the sliders that show you the effect that a slider change will have on the selected color. Consequently, I needed to create imageless gradients, in order to easily change them. My very first thought was creating many div or span elements in order to show the gradient. I rejected it almost instantly, for ovbious reasons (*cough* performance *cough*). My second thought was SVG for the proper browsers, and gradient filters for IE. As it turned out, inline SVG was too much of a hassle and I didn’t want to use Data URIs. My final thought was canvas for the proper browsers and gradient filters for IE. Since I consider such a script very entertaining, I didn’t google it at all, I started coding right away. Time to have fun! :D After finishing it though, I googled it just out of curiosity and didn’t like the other solutions much (either the solution itself, or the code), so I decided to post it in case it helps someone. I also made a little test page, so that you may test out how it works. :) The script is a class for the creation of linear 2-color gradients in any browser. It’s used like this: var g = new Gradient(200, 100, &#39;#000000&#39;, &#39;#ff1166&#39;, true); document.body.appendChild(g.canvas); You can create and manipulate the Gradient object at any point (during or after DOM parsing) but you have to insert the element somewhere in the DOM after the DOM has finished parsing (which is common sense). All the parameters in the constructor are optional and can be manipulated later. Their order is width, height, startColor, endColor, vertical . Some notes: Its object oriented and doesn’t throw any strict warnings Tested in IE6, IE7, IE8, Firefox 3, Safari 4b and Opera 9.6. Probably works with older versions of Firefox, Opera and Safari as well (as long as they support ), I’m just not able to test in them currently. All it’s methods return the object, so they can be chained. You can modify it to support RGBA as well, but you’d have to use a different format for IE (extended hex) and a different one for the proper browsers. I didn’t need that and it would make the script unnecessarily complex, so I didn’t implement it. Limitations ( all these limitations are enforced by IE’s gradient filter): Only does linear gradients The gradient can be either vertical or horizontal. No other angles. The only color format supported is #RRGGBB. Properties canvas (HTMLElement) The HTML Element that is being used to render the gradient. Either a or a . You have to use it at least once, in order to insert the element in the DOM. I preferred not to do this automatically, since it would be too restrictive. startColor (String) The current start color of the gradient. endColor (String) The current end color of the gradient. vertical (Boolean) True if the gradient is vertical, false if it’s horizontal. width (Number) The width of the gradient in pixels height (Number) The height of the gradient in pixels Methods paint(startColor, endColor, vertical) Used to change the colors and/or the orientation of the gradient. All parameters are optional. resize(width, height) Used to change the size of the gradient. Both parameters are optional. flip() Reverses the gradient (swaps endColor with startColor) rotate() Rotates the gradient by 90 degrees clockwise (should I add CCW too?) Download gradient.js (2.7 KB) gradient-min.js (1.4 KB) Test page Hope you find it useful :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Mockup viewer bookmarklet</title>
  <link>https://lea.verou.me/2009/03/mockup-viewer-bookmarklet/</link>
  <pubDate>Sun, 17 May 2026 00:07:25 +0200</pubDate>
  <description>I usually view mockups in a browser, so that the impression I get is as close as possible to reality (I learned this the hard way: A mockup that seemed great in the neutral and minimalistic environment of a picture viewer, ended up looking way too fancy when viewed in a browser, something that I realized after having worked for 6 months on the site). If you do the same, I’m sure you’ll feel my pain: Every time I do that, I have to carefully scroll down just as much as to hide the margin that the browser adds, and left just as much as to center the image. Not to mention the click required to enlarge the image to full-size. Not any more! I was so fed up today, that I wrote a little bookmarklet that does this. It enlarges the image to full size, removes the margins and scrolls the page left so that the image is centered. It works on any recent browser I’ve tested, and I think it will probably work in most browsers that web designers use (hint: not old IEs) :P Enjoy. Mockup viewer JS code: (function(){ document.body.style.margin = 0; var inner = window.innerWidth || document.body.clientWidth, img = document.getElementsByTagName(&#39;img&#39;)\[0\]; img.removeAttribute(&#39;width&#39;); img.removeAttribute(&#39;height&#39;); document.body.scrollLeft = (img.offsetWidth - inner)/2; })(); If only it could also write the XHTML &amp; CSS for the site… :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Cross browser, imageless linear gradients</title>
  <link>https://lea.verou.me/2009/03/cross-browser-imageless-linear-gradients/</link>
  <pubDate>Sun, 17 May 2026 00:07:25 +0200</pubDate>
  <description>I have to write a color picker in the near future and I wanted it to have those little gradients on top of the sliders that show you the effect that a slider change will have on the selected color. Consequently, I needed to create imageless gradients, in order to easily change them. My very first thought was creating many div or span elements in order to show the gradient. I rejected it almost instantly, for ovbious reasons (*cough* performance *cough*). My second thought was SVG for the proper browsers, and gradient filters for IE. As it turned out, inline SVG was too much of a hassle and I didnt want to use Data URIs. My final thought was canvas for the proper browsers and gradient filters for IE. Since I consider such a script very entertaining, I didnt google it at all, I started coding right away. Time to have fun! :D After finishing it though, I googled it just out of curiosity and didnt like the other solutions much (either the solution itself, or the code), so I decided to post it in case it helps someone. I also made a little test page, so that you may test out how it works. :) The script is a class for the creation of linear 2-color gradients in any browser. Its used like this: var g = new Gradient(200, 100, &#39;#000000&#39;, &#39;#ff1166&#39;, true); document.body.appendChild(g.canvas); You can create and manipulate the Gradient object at any point (during or after DOM parsing) but you have to insert the element somewhere in the DOM after the DOM has finished parsing (which is common sense). All the parameters in the constructor are optional and can be manipulated later. Their order is width, height, startColor, endColor, vertical . Some notes: Its object oriented and doesnt throw any strict warnings Tested in IE6, IE7, IE8, Firefox 3, Safari 4b and Opera 9.6. Probably works with older versions of Firefox, Opera and Safari as well (as long as they support ), Im just not able to test in them currently. All its methods return the object, so they can be chained. You can modify it to support RGBA as well, but youd have to use a different format for IE (extended hex) and a different one for the proper browsers. I didnt need that and it would make the script unnecessarily complex, so I didnt implement it. Limitations ( all these limitations are enforced by IEs gradient filter): Only does linear gradients The gradient can be either vertical or horizontal. No other angles. The only color format supported is #RRGGBB. Properties canvas (HTMLElement) The HTML Element that is being used to render the gradient. Either a or a . You have to use it at least once, in order to insert the element in the DOM. I preferred not to do this automatically, since it would be too restrictive. startColor (String) The current start color of the gradient. endColor (String) The current end color of the gradient. vertical (Boolean) True if the gradient is vertical, false if its horizontal. width (Number) The width of the gradient in pixels height (Number) The height of the gradient in pixels Methods paint(startColor, endColor, vertical) Used to change the colors and/or the orientation of the gradient. All parameters are optional. resize(width, height) Used to change the size of the gradient. Both parameters are optional. flip() Reverses the gradient (swaps endColor with startColor) rotate() Rotates the gradient by 90 degrees clockwise (should I add CCW too?) Download gradient.js (2.7 KB) gradient-min.js (1.4 KB) Test page Hope you find it useful :)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Mockup viewer bookmarklet</title>
  <link>https://lea.verou.me/2009/03/mockup-viewer-bookmarklet/</link>
  <pubDate>Sun, 17 May 2026 00:07:25 +0200</pubDate>
  <description>I usually view mockups in a browser, so that the impression I get is as close as possible to reality (I learned this the hard way: A mockup that seemed great in the neutral and minimalistic environment of a picture viewer, ended up looking way too fancy when viewed in a browser, something that I realized after having worked for 6 months on the site). If you do the same, Im sure youll feel my pain: Every time I do that, I have to carefully scroll down just as much as to hide the margin that the browser adds, and left just as much as to center the image. Not to mention the click required to enlarge the image to full-size. Not any more! I was so fed up today, that I wrote a little bookmarklet that does this. It enlarges the image to full size, removes the margins and scrolls the page left so that the image is centered. It works on any recent browser Ive tested, and I think it will probably work in most browsers that web designers use (hint: not old IEs) :P Enjoy. Mockup viewer JS code: (function(){ document.body.style.margin = 0; var inner = window.innerWidth || document.body.clientWidth, img = document.getElementsByTagName(&#39;img&#39;)\[0\]; img.removeAttribute(&#39;width&#39;); img.removeAttribute(&#39;height&#39;); document.body.scrollLeft = (img.offsetWidth - inner)/2; })(); If only it could also write the XHTML &amp; CSS for the site :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 colors, today (MediaCampAthens session)</title>
  <link>https://lea.verou.me/2009/03/css3-colors-today-mediacampathens-session/</link>
  <pubDate>Sun, 17 May 2026 00:07:24 +0200</pubDate>
  <description>Yesterday, I had a session at MediaCampAthens (a BarCamp-style event), regarding CSS3 colors. If you’ve followed my earlier posts tagged with “colors” , my presentation was mostly a sum-up of these. It was my first presentation ever, actually, the first time I talked to an audience for more than 1 minute :P . This caused some goofs: When introducing myself, I said completely different things than I intended to and ended up sounding like an arrogant moron :P I tried not to look at the audience too much, in order to avoid sounding nervous, and this caused me to completely ignore 2 questions (as I found out afterwards)! How embarrasing! At a certain point, I said “URL” instead of “domain” :P Also, I had prepared some screenshots (you’ll see them in the ppt) and the projector completely screwed them up, as it showed any dark color as black. Apart from those, I think it went very well, I received lots of positive feedback about it and the audience was paying attention, so I guess they found it interesting (something that I didn’t expect :P ). Here is the presentation: Please note that Slideshare messed up slide #8 and the background seems semi-transparent grey instead of semi-transparent white. By the way, I also thought afterwards that I had made a mistake: -ms-filter is not required if we combine the gradient filter with Data URIs, since IE8 supports Data URIs (for images at least). Oops, I hate making mistakes that I can’t correct. Here are some photos from my session . If I did it correctly, every facebook user can see them. If I messed things up, tell me :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 colors, today (MediaCampAthens session)</title>
  <link>https://lea.verou.me/2009/03/css3-colors-today-mediacampathens-session/</link>
  <pubDate>Sun, 17 May 2026 00:07:24 +0200</pubDate>
  <description>Yesterday, I had a session at MediaCampAthens (a BarCamp-style event), regarding CSS3 colors. If youve followed my earlier posts tagged with colors , my presentation was mostly a sum-up of these. It was my first presentation ever, actually, the first time I talked to an audience for more than 1 minute :P . This caused some goofs: When introducing myself, I said completely different things than I intended to and ended up sounding like an arrogant moron :P I tried not to look at the audience too much, in order to avoid sounding nervous, and this caused me to completely ignore 2 questions (as I found out afterwards)! How embarrasing! At a certain point, I said URL instead of domain :P Also, I had prepared some screenshots (youll see them in the ppt) and the projector completely screwed them up, as it showed any dark color as black. Apart from those, I think it went very well, I received lots of positive feedback about it and the audience was paying attention, so I guess they found it interesting (something that I didnt expect :P ). Here is the presentation: Please note that Slideshare messed up slide #8 and the background seems semi-transparent grey instead of semi-transparent white. By the way, I also thought afterwards that I had made a mistake: -ms-filter is not required if we combine the gradient filter with Data URIs, since IE8 supports Data URIs (for images at least). Oops, I hate making mistakes that I cant correct. Here are some photos from my session . If I did it correctly, every facebook user can see them. If I messed things up, tell me :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CMYK colors in CSS: Useful or useless?</title>
  <link>https://lea.verou.me/2009/03/cmyk-colors-in-css-useful-or-useless/</link>
  <pubDate>Sun, 17 May 2026 00:07:23 +0200</pubDate>
  <description>As someone who dealed a bit with print design in the past, I consider CMYK colors the easiest color system for humen to understand and manipulate. It’s very similar to what we used as children, when mixing watercolors for our drawings. It makes perfect sense, more than HSL and definately more than RGB. I understand that most of us are so accustomed to using RGB that can’t realise that, but try to think for a moment: Which color system would make more sense to you if you had no idea and no experience at all with any of them? Personally, even though I have lots more experience with RGB, given the fact that most of my work will be displayed on screen and not printed on paper, when I think of a color I want, I can instantly find out the percentages of Cyan, Magenta, Yellow and blacK needed to create it. I can’t do that with HSL or RGB, I’d have to play a little bit with the color picker’s sliders. I sometimes start by specifying a color in CMYK and then tweaking it via RGB or HSL to achieve the exact color I need (since the CMYK gamut is smaller than the RGB gamut) and I find that much faster than starting with RGB or HSL right away. Also, when you don’t have a color picker, it’s much easier to create beautiful colors with CMYK than it is with RGB. For example the CMYK magenta (0% Cyan, 100% Magenta, 0% Yellow, 0% blacK) is a much better color than the RGB Magenta (255 Red, 0 Green, 100% Blue). Given the above, I’ve always thought how much I wanted to be able to specify CMYK colors in my CSS. I agree that sometimes this would result in crippling myself, since as I said above the CMYK gamut is smaller, but it has other significant advantages that I think it would make it a useful option for some people. There are algorithms available for CMYK to RGB conversion, and the browser could use those to display the specified color on the screen. Then, if the user decided to print the page, The CMYK colors could be used as-is for the printer. Another advantage, as none of the current CSS color formats allow us to control that. People who don’t find the CMYK color system easier for them to understand, they could still use it for their print stylesheets. Also, graphic designers who decided to switch to web design would find it much easier to specify color values in a format they are already comfortable with. To sum it up, the advantages that I think this option would provide us are: A color system that’s easier for most people to understand and manipulate. The colors you get when combining “easy” CMYK values (0%, 50%, 100%) are more beatuful than the ones you get with “easy” RGB values (0, 128, 255). Bored people and people without a taste in color selection would create more beatuful websites this way and our eyes wouldn’t hurt. We would be able to specify how our colors will get printed, something that is not currently possible at all. Extremely useful for print stylesheets. It would be easier for graphic designers to switch to web design. And the format is very easy to imagine: background-color: cmyk(0, 100, 50, 0); or background-color: cmyk(0%, 100%, 50%, 0%); or background-color: cmyk(0, 1, 0.5, 0); So, what do you think? Useful or useless? Edit: As it turns out, I’m not crazy! The W3 already considers this for CSS3 with the 3rd format (from 0 to 1)! However, no browser supports it yet, not even Webkit nightlies… :( Translations Portuguese</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On native, single-input, multiple file uploads</title>
  <link>https://lea.verou.me/2009/03/regarding-native-single-input-multiple-file-uploads/</link>
  <pubDate>Sun, 17 May 2026 00:07:23 +0200</pubDate>
  <description>If you are following the current news on web development, you probably heard that the new Safari 4 has a great feature: It natively allows the user to select multiple files via a single input control, if you specify a value for the attribute multiple : or, in XHTML: You might not know that Opera supported multiple file uploads for a while now, based on the earlier Web Forms 2.0 standard in a slightly different (and more flexible) format: Can we use those currently? Sure we can, but we should provide fallbacks for the other browsers. Using these features will put pressure on the other browser vendors to implement them as well and generally, native is always better. How can we find out whether the browser supports them? Opera Opera supports accessing those min and max properties as properties of the element. So, it’s quite trivial to check whether Opera-style multiple inputs are supported: var supportsMin = (function(){ var fi = document.createElement(&#39;input&#39;); fi.type = &#39;file&#39;; return fi.min === &#39;&#39; &amp;&amp; fi.max === &#39;&#39;; })(); Safari 4 In Safari 4 the check would be equally simple, if it supported accessing the multiple attribute as a property. Then we could easily check whether it’s boolean and conclude that Safari-style multiple inputs are supported: var supportsMultiple = (function(){ var fi = document.createElement(&#39;input&#39;); fi.type = &#39;file&#39;; // The second check is probably redundant but what if in the future an implementor // decides to make the file inputs to handle multiple selections by default? // Yeah, it&#39;s not likely, but it&#39;s not entirely impossible. return fi.multiple === false || fi.multiple === true; })(); However, that’s currently not the case. The good news are that I reported this as a bug today, and the Webkit team fixed it , so it will be possible in the next Webkit nightly! Combining the two You can easily combine these two together with the workaround you prefer: // Create a file input that allows multiple file selection var fi = document.createElement(&#39;input&#39;); fi.type = &#39;file&#39;; if(fi.multiple === false || fi.multiple === true) { fi.multiple = true; } else if(fi.min === &#39;&#39; &amp;&amp; fi.max === &#39;&#39;) { fi.min = 1; fi.max = 9999; } else { // Our preferred workaround here } What about Mozilla? Ok, we all know that IE will probably take years to implement similar functionality. But usually, the Mozilla team implements new and exciting stuff quite fast. As it turns out, there is a relevant ticket sitting in their Bugzilla for a while now. If you want them to implement it, vote for it so that it’s priority increases. If they do implement it in the way suggested, the code posted above will work for that too, without any changes - The advantages of feature detection baby! ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CMYK colors in CSS: Useful or useless?</title>
  <link>https://lea.verou.me/2009/03/cmyk-colors-in-css-useful-or-useless/</link>
  <pubDate>Sun, 17 May 2026 00:07:23 +0200</pubDate>
  <description>As someone who dealed a bit with print design in the past, I consider CMYK colors the easiest color system for humen to understand and manipulate. Its very similar to what we used as children, when mixing watercolors for our drawings. It makes perfect sense, more than HSL and definately more than RGB. I understand that most of us are so accustomed to using RGB that cant realise that, but try to think for a moment: Which color system would make more sense to you if you had no idea and no experience at all with any of them? Personally, even though I have lots more experience with RGB, given the fact that most of my work will be displayed on screen and not printed on paper, when I think of a color I want, I can instantly find out the percentages of Cyan, Magenta, Yellow and blacK needed to create it. I cant do that with HSL or RGB, Id have to play a little bit with the color pickers sliders. I sometimes start by specifying a color in CMYK and then tweaking it via RGB or HSL to achieve the exact color I need (since the CMYK gamut is smaller than the RGB gamut) and I find that much faster than starting with RGB or HSL right away. Also, when you dont have a color picker, its much easier to create beautiful colors with CMYK than it is with RGB. For example the CMYK magenta (0% Cyan, 100% Magenta, 0% Yellow, 0% blacK) is a much better color than the RGB Magenta (255 Red, 0 Green, 100% Blue). Given the above, Ive always thought how much I wanted to be able to specify CMYK colors in my CSS. I agree that sometimes this would result in crippling myself, since as I said above the CMYK gamut is smaller, but it has other significant advantages that I think it would make it a useful option for some people. There are algorithms available for CMYK to RGB conversion, and the browser could use those to display the specified color on the screen. Then, if the user decided to print the page, The CMYK colors could be used as-is for the printer. Another advantage, as none of the current CSS color formats allow us to control that. People who dont find the CMYK color system easier for them to understand, they could still use it for their print stylesheets. Also, graphic designers who decided to switch to web design would find it much easier to specify color values in a format they are already comfortable with. To sum it up, the advantages that I think this option would provide us are: A color system thats easier for most people to understand and manipulate. The colors you get when combining easy CMYK values (0%, 50%, 100%) are more beatuful than the ones you get with easy RGB values (0, 128, 255). Bored people and people without a taste in color selection would create more beatuful websites this way and our eyes wouldnt hurt. We would be able to specify how our colors will get printed, something that is not currently possible at all. Extremely useful for print stylesheets. It would be easier for graphic designers to switch to web design. And the format is very easy to imagine: background-color: cmyk(0, 100, 50, 0); or background-color: cmyk(0%, 100%, 50%, 0%); or background-color: cmyk(0, 1, 0.5, 0); So, what do you think? Useful or useless? Edit: As it turns out, Im not crazy! The W3 already considers this for CSS3 with the 3rd format (from 0 to 1)! However, no browser supports it yet, not even Webkit nightlies :( Translations Portuguese</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>On native, single-input, multiple file uploads</title>
  <link>https://lea.verou.me/2009/03/regarding-native-single-input-multiple-file-uploads/</link>
  <pubDate>Sun, 17 May 2026 00:07:23 +0200</pubDate>
  <description>If you are following the current news on web development, you probably heard that the new Safari 4 has a great feature: It natively allows the user to select multiple files via a single input control, if you specify a value for the attribute multiple : or, in XHTML: You might not know that Opera supported multiple file uploads for a while now, based on the earlier Web Forms 2.0 standard in a slightly different (and more flexible) format: Can we use those currently? Sure we can, but we should provide fallbacks for the other browsers. Using these features will put pressure on the other browser vendors to implement them as well and generally, native is always better. How can we find out whether the browser supports them? Opera Opera supports accessing those min and max properties as properties of the element. So, its quite trivial to check whether Opera-style multiple inputs are supported: var supportsMin = (function(){ var fi = document.createElement(&#39;input&#39;); fi.type = &#39;file&#39;; return fi.min === &#39;&#39; &amp;&amp; fi.max === &#39;&#39;; })(); Safari 4 In Safari 4 the check would be equally simple, if it supported accessing the multiple attribute as a property. Then we could easily check whether its boolean and conclude that Safari-style multiple inputs are supported: var supportsMultiple = (function(){ var fi = document.createElement(&#39;input&#39;); fi.type = &#39;file&#39;; // The second check is probably redundant but what if in the future an implementor // decides to make the file inputs to handle multiple selections by default? // Yeah, it&#39;s not likely, but it&#39;s not entirely impossible. return fi.multiple === false || fi.multiple === true; })(); However, thats currently not the case. The good news are that I reported this as a bug today, and the Webkit team fixed it , so it will be possible in the next Webkit nightly! Combining the two You can easily combine these two together with the workaround you prefer: // Create a file input that allows multiple file selection var fi = document.createElement(&#39;input&#39;); fi.type = &#39;file&#39;; if(fi.multiple === false || fi.multiple === true) { fi.multiple = true; } else if(fi.min === &#39;&#39; &amp;&amp; fi.max === &#39;&#39;) { fi.min = 1; fi.max = 9999; } else { // Our preferred workaround here } What about Mozilla? Ok, we all know that IE will probably take years to implement similar functionality. But usually, the Mozilla team implements new and exciting stuff quite fast. As it turns out, there is a relevant ticket sitting in their Bugzilla for a while now. If you want them to implement it, vote for it so that its priority increases. If they do implement it in the way suggested, the code posted above will work for that too, without any changes - The advantages of feature detection baby! ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Check whether the browser supports RGBA (and other CSS3 values)</title>
  <link>https://lea.verou.me/2009/03/check-whether-the-browser-supports-rgba-and-other-css3-values/</link>
  <pubDate>Sun, 17 May 2026 00:07:22 +0200</pubDate>
  <description>When using CSS, we can just include both declarations, one using rgba, and one without it, as mentioned in my post on cross-browser RGBA backgrounds . When writing JavaScript however, it’s a waste of resources to do that (and requires more verbose code), since we can easily check whether the browser is RGBA-capable, almost as easily as we can check whether it suppports a given property . We can even follow the same technique to detect the support of other CSS3 values (for instance, multiple backgrounds support, HSLA support, etc). The technique I’m going to present is based on the fact that when we assign a non-supported CSS value on any supported CSS property, the browser either throws an error AND ignores it (IE-style), or simply ignores it (Firefox-style). Concequently, to check whether RGBA is supported, the algorithm would be: Get the color (or backgroundColor , or borderColor or any property that is widely supported and accepts color values) value of the style object of any element that exists in the page for sure (for instance, the first script tag) and store it in a variable. Assign an RGBA color to the color property of that element and catch any errors produced. Assign to a variable whether the color of that element did change (boolean true or false ). Restore the previous color to the color property, so that our script doesn’t interfere with the designer’s decisions. Return the stored result. and it would result in the following code: function supportsRGBA() { var scriptElement = document.getElementsByTagName(&#39;script&#39;)[0]; var prevColor = scriptElement.style.color; try { scriptElement.style.color = &#39;rgba(1,5,13,0.44)&#39;; } catch(e) {} var result = scriptElement.style.color != prevColor; scriptElement.style.color = prevColor; return result; } Performance improvements The code above works, but it wastes resources for no reason. Every time the function is called, it tests RGBA support again, even though the result will never change. So, we need a way to cache the result, and return the cached result after the first time the function is called. This can be achieved in many ways. My personal preference is to store the result as a property of the function called, named &#39;result&#39; : function supportsRGBA() { if(!(&#39;result&#39; in arguments.callee)) { var scriptElement = document.getElementsByTagName(&#39;script&#39;)[0]; var prevColor = scriptElement.style.color; try { scriptElement.style.color = &#39;rgba(0, 0, 0, 0.5)&#39;; } catch(e) {} arguments.callee.result = scriptElement.style.color != prevColor; scriptElement.style.color = prevColor; } return arguments.callee.result; } Making it bulletproof There is a rare case where the script element might already have rgba(0,0,0,0.5) set as it’s color value (don’t ask me why would someone want to do that :P ), in which case our function will return false even if the browser actually supports RGBA. To prevent this, you might want to check whether the color property is already set to rgba(0,0,0,0.5) and return true if it is (because if the browser doesn’t support rgba, it will be blank): function supportsRGBA() { if(!(&#39;result&#39; in arguments.callee)) { var scriptElement = document.getElementsByTagName(&#39;script&#39;)[0]; var prevColor = scriptElement.style.color; var testColor = &#39;rgba(0, 0, 0, 0.5)&#39;; if(prevColor == testColor) { arguments.callee.result = true; } else { try { scriptElement.style.color = testColor; } catch(e) {} arguments.callee.result = scriptElement.style.color != prevColor; scriptElement.style.color = prevColor; } } return arguments.callee.result; } Done!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Check whether the browser supports RGBA (and other CSS3 values)</title>
  <link>https://lea.verou.me/2009/03/check-whether-the-browser-supports-rgba-and-other-css3-values/</link>
  <pubDate>Sun, 17 May 2026 00:07:22 +0200</pubDate>
  <description>When using CSS, we can just include both declarations, one using rgba, and one without it, as mentioned in my post on cross-browser RGBA backgrounds . When writing JavaScript however, its a waste of resources to do that (and requires more verbose code), since we can easily check whether the browser is RGBA-capable, almost as easily as we can check whether it suppports a given property . We can even follow the same technique to detect the support of other CSS3 values (for instance, multiple backgrounds support, HSLA support, etc). The technique Im going to present is based on the fact that when we assign a non-supported CSS value on any supported CSS property, the browser either throws an error AND ignores it (IE-style), or simply ignores it (Firefox-style). Concequently, to check whether RGBA is supported, the algorithm would be: Get the color (or backgroundColor , or borderColor or any property that is widely supported and accepts color values) value of the style object of any element that exists in the page for sure (for instance, the first script tag) and store it in a variable. Assign an RGBA color to the color property of that element and catch any errors produced. Assign to a variable whether the color of that element did change (boolean true or false ). Restore the previous color to the color property, so that our script doesnt interfere with the designers decisions. Return the stored result. and it would result in the following code: function supportsRGBA() { var scriptElement = document.getElementsByTagName(&#39;script&#39;)[0]; var prevColor = scriptElement.style.color; try { scriptElement.style.color = &#39;rgba(1,5,13,0.44)&#39;; } catch(e) {} var result = scriptElement.style.color != prevColor; scriptElement.style.color = prevColor; return result; } Performance improvements The code above works, but it wastes resources for no reason. Every time the function is called, it tests RGBA support again, even though the result will never change. So, we need a way to cache the result, and return the cached result after the first time the function is called. This can be achieved in many ways. My personal preference is to store the result as a property of the function called, named &#39;result&#39; : function supportsRGBA() { if(!(&#39;result&#39; in arguments.callee)) { var scriptElement = document.getElementsByTagName(&#39;script&#39;)[0]; var prevColor = scriptElement.style.color; try { scriptElement.style.color = &#39;rgba(0, 0, 0, 0.5)&#39;; } catch(e) {} arguments.callee.result = scriptElement.style.color != prevColor; scriptElement.style.color = prevColor; } return arguments.callee.result; } Making it bulletproof There is a rare case where the script element might already have rgba(0,0,0,0.5) set as its color value (dont ask me why would someone want to do that :P ), in which case our function will return false even if the browser actually supports RGBA. To prevent this, you might want to check whether the color property is already set to rgba(0,0,0,0.5) and return true if it is (because if the browser doesnt support rgba, it will be blank): function supportsRGBA() { if(!(&#39;result&#39; in arguments.callee)) { var scriptElement = document.getElementsByTagName(&#39;script&#39;)[0]; var prevColor = scriptElement.style.color; var testColor = &#39;rgba(0, 0, 0, 0.5)&#39;; if(prevColor == testColor) { arguments.callee.result = true; } else { try { scriptElement.style.color = testColor; } catch(e) {} arguments.callee.result = scriptElement.style.color != prevColor; scriptElement.style.color = prevColor; } } return arguments.callee.result; } Done!</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>&quot;Appearances can be deceiving Mr. Anderson&quot; - a.k.a. short code is not always fast code</title>
  <link>https://lea.verou.me/2009/02/appearances-can-be-deceiving-mr-anderson-aka-short-code-is-not-always-fast-code/</link>
  <pubDate>Sun, 17 May 2026 00:07:21 +0200</pubDate>
  <description>I used to take pride in my short, bulletproof and elegant String and Number type checks: // Check whether obj is a Number obj + 0 === obj // Check whether obj is a String obj + &#39;&#39; === obj I always thought that apart from being short and elegant, they should be faster. However, some quick tests gave me a cold slap in the face and proved my assertion to be entirely false. When comparing the following 4 methods for string and number type checking: “My” method (mentioned above) Object.prototype.toString method: Object.prototype.toString.call(obj) === &#39;[object String]&#39; or Object.prototype.toString.call(obj) === &#39;[object Number]&#39; Typeof method: typeof obj === &#39;string&#39; or typeof obj === &#39;number&#39; Contructor method: obj.constructor === String or obj.constructor === Number It turned out that the Object.prototype.toString method was 50% faster than my method, and both typeof and constructor methods were a whopping 150% faster than my method! No wonder jQuery uses the typeof method for their String/Number tests . Now that I think about it, it does actually make sense - my method converts obj to a String or Number, then concatenates/adds it with another String/Number, then compares value and type. Too much stuff done there to be fast. But I guess I was too innocent and subconsciously thought that it wouldn’t be fair if elegant and short code wasn’t fast too. Of course the overall time needed for any of these tests was neglible, but it’s a good example of how much appearances can be deceiving - even in programming! ;) The moral: Never assume. Always test. So, which method is ideal for String/Number checks? (added afterwards) The typeof method and my method fail for non-primitive String/Number objects, as you can easily observe if you type in the console: typeof new String(&#39;foo&#39;) // &#39;object&#39; typeof new Number(5) // &#39;object&#39; new String(&#39;foo&#39;) + &#39;&#39; === new String(&#39;foo&#39;) // false This can easily be solved if you also check the type via instanceof (the decrease in speed is negligible): foo = new String(&#39;foo&#39;); typeof foo === &#39;string&#39; || foo instanceof String foo + &#39;&#39; === foo || foo instanceof String Don’t use instanceof alone, since it fails for String and Number primitives. The instanceof method also fails for Strings and Numbers created in another window, since their constructor there is different. Same happens with the Constructor method mentioned above. It seems that if you need a bulletproof check the only method you can use is the Object.prototype.toString method and luckily, it’s one of the fastest (not the fastest one though), so I guess we can safely elect it as the ideal method for String and Number checks (and not only for arrays, as it was first made popular for). PS: For anyone wondering what the quote in the title reminds him/her, its from the Matrix Revolutions movie.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>&quot;Appearances can be deceiving Mr. Anderson&quot; - a.k.a. short code is not always fast code</title>
  <link>https://lea.verou.me/2009/02/appearances-can-be-deceiving-mr-anderson-aka-short-code-is-not-always-fast-code/</link>
  <pubDate>Sun, 17 May 2026 00:07:21 +0200</pubDate>
  <description>I used to take pride in my short, bulletproof and elegant String and Number type checks: // Check whether obj is a Number obj + 0 === obj // Check whether obj is a String obj + &#39;&#39; === obj I always thought that apart from being short and elegant, they should be faster. However, some quick tests gave me a cold slap in the face and proved my assertion to be entirely false. When comparing the following 4 methods for string and number type checking: My method (mentioned above) Object.prototype.toString method: Object.prototype.toString.call(obj) === &#39;[object String]&#39; or Object.prototype.toString.call(obj) === &#39;[object Number]&#39; Typeof method: typeof obj === &#39;string&#39; or typeof obj === &#39;number&#39; Contructor method: obj.constructor === String or obj.constructor === Number It turned out that the Object.prototype.toString method was 50% faster than my method, and both typeof and constructor methods were a whopping 150% faster than my method! No wonder jQuery uses the typeof method for their String/Number tests . Now that I think about it, it does actually make sense - my method converts obj to a String or Number, then concatenates/adds it with another String/Number, then compares value and type. Too much stuff done there to be fast. But I guess I was too innocent and subconsciously thought that it wouldnt be fair if elegant and short code wasnt fast too. Of course the overall time needed for any of these tests was neglible, but its a good example of how much appearances can be deceiving - even in programming! ;) The moral: Never assume. Always test. So, which method is ideal for String/Number checks? (added afterwards) The typeof method and my method fail for non-primitive String/Number objects, as you can easily observe if you type in the console: typeof new String(&#39;foo&#39;) // &#39;object&#39; typeof new Number(5) // &#39;object&#39; new String(&#39;foo&#39;) + &#39;&#39; === new String(&#39;foo&#39;) // false This can easily be solved if you also check the type via instanceof (the decrease in speed is negligible): foo = new String(&#39;foo&#39;); typeof foo === &#39;string&#39; || foo instanceof String foo + &#39;&#39; === foo || foo instanceof String Dont use instanceof alone, since it fails for String and Number primitives. The instanceof method also fails for Strings and Numbers created in another window, since their constructor there is different. Same happens with the Constructor method mentioned above. It seems that if you need a bulletproof check the only method you can use is the Object.prototype.toString method and luckily, its one of the fastest (not the fastest one though), so I guess we can safely elect it as the ideal method for String and Number checks (and not only for arrays, as it was first made popular for). PS: For anyone wondering what the quote in the title reminds him/her, its from the Matrix Revolutions movie.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Quick &amp; dirty way to run snippets of JavaScript anywhere</title>
  <link>https://lea.verou.me/2009/02/quick-dirty-way-to-run-snippets-of-javascript-anywhere/</link>
  <pubDate>Sun, 17 May 2026 00:07:20 +0200</pubDate>
  <description>Ever wanted to run a snippet of JavaScript on a browser that doesn’t support a console in order to debug something? (for instance, IE6, Opera etc) You probably know about Firebug Lite , but this either requires you to already have the bookmarklet, or include the script in the page. Although Firebug Lite is a great tool for more in depth debugging, it can be tedious for simple tasks (eg. “What’s the value of that property?” ). Fortunately, there is a simpler way. Do you remember the 2000 era and the javascript: URIs? Did you know that they also work from the address bar of any javascript-capable browser? For instance, to find out the value of the global variable foo , you just type in the address bar javascript:alert(foo) . You can write any code you wish after the javascript: part, as long as you write it properly to fit in one line. Of course these URIs are a no-no for websites, but they can be handy for simple debugging in browsers that don’t support a console. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>20 things you should know when not using a JS library</title>
  <link>https://lea.verou.me/2009/02/20things-you-should-know-when-not-using-a-js-library/</link>
  <pubDate>Sun, 17 May 2026 00:07:20 +0200</pubDate>
  <description>You might just dislike JavaScript libraries and the trend around them, or the project you’re currently working on might be too small for a JavaScript library. In both cases, I understand, and after all, who am I to judge you? I don’t use a library myself either (at least not one that you could’ve heard about ;) ), even though I admire the ingenuity and code quality of some. However, when you take such a brave decision, it’s up to you to take care of those problems that JavaScript libraries carefully hide from your way. A JavaScript library’s purpose isn’t only to provide shortcuts to tedious tasks and allow you to easily add cool animations and Ajax functionality as many people (even library users) seem to think. Of course these are things that they are bound to offer if they want to succeed, but not the only ones. JavaScript libraries also have to workaround browser differences and bugs and this is the toughest part, since they have to constantly keep up with browser releases and their respective bugs and judge which ones are common enough to deserve workaround and which ones are so rare that would bloat the library without being worth it. Sometimes I think that nowadays, how good of a JavaScript developer you are doesn’t really depend on how well you know the language, but rather on how many browser bugs you’ve heard/read/know/found out. :P The purpose of this post is to let you know about the browser bugs and incompatibilities that you are most likely to face when deciding againist the use of a JavaScript library. Knowledge is power, and only if you know about them beforehand you can workaround them without spending countless debugging hours wondering “WHAT THE…”. And even if you do use a JavaScript library, you will learn to appreciate the hard work that has been put in it even more. Some of the things mentioned below might seem elementary to many of you. However, I wanted this article to be fairly complete and contain as many common problems as possible, without making assumptions about the knowledge of my readers (as someone said, “assumption is the mother of all fuck-ups” :P ). After all, it does no harm if you read something that you already know, but it does if you remain ignorant about something you ought to know. I hope that even the most experienced among you, will find at least one thing they didn’t know very well or had misunderstood (unless I’m honoured to have library authors reading this blog, which in that case, you probably know all the facts mentioned below :P ) . If you think that something is missing from the list, feel free to suggest it in the comments, but have in mind that I conciously omitted many things because I didn’t consider them common enough. DOM getElementById(&#39;foo&#39;) also returns elements with name=&quot;foo&quot; in IE and older versions of Opera. getElementsByTagName(&#39;*&#39;) returns no elements in IE5.5 and also returns comment nodes in all versions of IE (In case you’re wondering: DOCTYPE declaration will count, Conditional comments will not). getElementsByClassName() in Opera (even Opera 10 Alpha) doesn’t match elements with 2 or more classes when the one you’re looking for is not the first but it’s also a substring of the first. Read the discussion between me and John Resig on the latter’s blog post mentioned below if this seems a bit unclear. There is no element.children collection in Firefox 3- . You have to create it yourself by filtering the childNodes collection if it doesn’t exist. If your code involves preformatted elements (for instance if you are making a syntax highlighter), beware when setting the innerHTML of those: IE won’t preserve line breaks ( \r\n s) and whitespace . You have to use outerHTML , which will actually replace the element so you should find a way to obtain a reference to the newly created one in case you still need to perform stuff on it. To get the dimensions of the viewport, standard compliant browsers use window.innerWidth (and innerHeight ) whereas IE uses document.documentElement.clientWidth (and clientHeight ). To get the scroll offsets of the current page, standard compliant browsers use window.pageXOffset (and pageYOffset ) whereas IE uses document.documentElement.scrollTop (and scrollLeft ). To make matters worse, in both cases above, you need to use document.body instead of document.documentElement when in Quirks mode. John Resig (of the jQuery fame), recently posted a great presentation , which summarized some browser bugs related to DOM functions. A few of the bugs/inconsistencies mentioned above are derived from that presentation. Events When using IE’s attachEvent() the this reference inside the callback refers to the useless window object eventObject.target is eventObject.srcElement in IE eventObject.stopPropagation() is eventObject.cancelBubble = true; in IE eventObject.preventDefault() is eventObject.returnValue = false; in IE There are many more event object incompatibilities for specific events (the ones above are for all events). Take a trip to QuirksMode for more information. IE leaks horribly (especially IE6) in various cases . If you register the same event handler X times, IE fires it X times. Determining when the DOM is ready is a complete mess . Firefox and Opera 9+ support the DOMContentLoaded event, Safari doesn’t but you can check it’s document.readyState property and in IE document.readyState is unreliable and you should either inject a deferred script , either poll the DOM untill there are no errors or use an external behavior file . Of course you could always just put a script tag at the bottom of the page, just before the body closing tag, which will fire all attached handlers which is actually the best approach in terms of which way fires earliest (but not too early) according to my tests, but that hardly qualifies as unobtrusive… (edit, thanks Sobral!) The Event object is not passed as a parameter to the callback but resides in window.event in older versions of IE Type detection The typeof operator is almost useless: typeof null == &#39;object&#39; typeof new String(&#39;foo&#39;) == &#39;object&#39; typeof [] == &#39;object&#39; Use Object.prototype.toString instead . CSS Although most CSS properties are converted to their JavaScript equivalents in a standard way (characters after dashes are Uppercase, others are lowercase, the dashes get removed), float is an exception: It’s converted to cssFloat in most browsers and styleFloat in IE. Check which one exists and use that. Getting the current (computed) style of an element is another complete mess . IE uses element.currentStyle[propertyJS] whereas standard compliant browsers use document.defaultView.getComputedStyle(element, null).getPropertyValue(propertyCSS) . And as if this wasn’t enough, there are various problems associated with specific properties or browsers, like: IE returns the cascaded values and not the computed ones (for instance, it might return em s for a property that was specified in em s, and not pixels). Dean Edwards has thought a very clever hack to workaround this and didn’t even blog about it (it’s simply a comment in a blog post of Erik Arvidsson’s!). Any hidden (via display:none; ) element, yields a width / height / top / right / bottom / left value of zero. auto or normal might be returned for properties that are left at their defaults. For instance, IE does this with width / height for elements that don’t have dimensions explicitly set via CSS. In most browsers, shorthands (like border ) will yield a blank string. You’d have to use the most specific property (for instance, border-left-width ). Colors will be returned in different formats across browsers. For instance, IE uses #RRGGBB whereas Mozilla uses rgb(red, green, blue) . So, what now? Never, EVER use a browser detect to solve the problems mentioned above. They can all be solved with feature/object detection, simple one-time tests or defensive coding. I have done it myself (and so did most libraries nowadays I think) so I know it’s possible. I will not post all of these solutions to avoid bloating this post even more. You can ask me about particular ones in the comments, or read the uncompressed source code of any library that advertises itself as “not using browser detects”. JavaScript Libraries are a much more interesting read than literature anyway. :P Are the facts mentioned above actually 20? I’m not really sure to be honest, it depends on how you count them. I thought that if I put a nice round number in the title, it would be more catchy :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Quick &amp; dirty way to run snippets of JavaScript anywhere</title>
  <link>https://lea.verou.me/2009/02/quick-dirty-way-to-run-snippets-of-javascript-anywhere/</link>
  <pubDate>Sun, 17 May 2026 00:07:20 +0200</pubDate>
  <description>Ever wanted to run a snippet of JavaScript on a browser that doesnt support a console in order to debug something? (for instance, IE6, Opera etc) You probably know about Firebug Lite , but this either requires you to already have the bookmarklet, or include the script in the page. Although Firebug Lite is a great tool for more in depth debugging, it can be tedious for simple tasks (eg. Whats the value of that property? ). Fortunately, there is a simpler way. Do you remember the 2000 era and the javascript: URIs? Did you know that they also work from the address bar of any javascript-capable browser? For instance, to find out the value of the global variable foo , you just type in the address bar javascript:alert(foo) . You can write any code you wish after the javascript: part, as long as you write it properly to fit in one line. Of course these URIs are a no-no for websites, but they can be handy for simple debugging in browsers that dont support a console. ;)</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>20 things you should know when not using a JS library</title>
  <link>https://lea.verou.me/2009/02/20things-you-should-know-when-not-using-a-js-library/</link>
  <pubDate>Sun, 17 May 2026 00:07:20 +0200</pubDate>
  <description>You might just dislike JavaScript libraries and the trend around them, or the project youre currently working on might be too small for a JavaScript library. In both cases, I understand, and after all, who am I to judge you? I dont use a library myself either (at least not one that you couldve heard about ;) ), even though I admire the ingenuity and code quality of some. However, when you take such a brave decision, its up to you to take care of those problems that JavaScript libraries carefully hide from your way. A JavaScript librarys purpose isnt only to provide shortcuts to tedious tasks and allow you to easily add cool animations and Ajax functionality as many people (even library users) seem to think. Of course these are things that they are bound to offer if they want to succeed, but not the only ones. JavaScript libraries also have to workaround browser differences and bugs and this is the toughest part, since they have to constantly keep up with browser releases and their respective bugs and judge which ones are common enough to deserve workaround and which ones are so rare that would bloat the library without being worth it. Sometimes I think that nowadays, how good of a JavaScript developer you are doesnt really depend on how well you know the language, but rather on how many browser bugs youve heard/read/know/found out. :P The purpose of this post is to let you know about the browser bugs and incompatibilities that you are most likely to face when deciding againist the use of a JavaScript library. Knowledge is power, and only if you know about them beforehand you can workaround them without spending countless debugging hours wondering WHAT THE. And even if you do use a JavaScript library, you will learn to appreciate the hard work that has been put in it even more. Some of the things mentioned below might seem elementary to many of you. However, I wanted this article to be fairly complete and contain as many common problems as possible, without making assumptions about the knowledge of my readers (as someone said, assumption is the mother of all fuck-ups :P ). After all, it does no harm if you read something that you already know, but it does if you remain ignorant about something you ought to know. I hope that even the most experienced among you, will find at least one thing they didnt know very well or had misunderstood (unless Im honoured to have library authors reading this blog, which in that case, you probably know all the facts mentioned below :P ) . If you think that something is missing from the list, feel free to suggest it in the comments, but have in mind that I conciously omitted many things because I didnt consider them common enough. DOM getElementById(&#39;foo&#39;) also returns elements with name=&quot;foo&quot; in IE and older versions of Opera. getElementsByTagName(&#39;*&#39;) returns no elements in IE5.5 and also returns comment nodes in all versions of IE (In case youre wondering: DOCTYPE declaration will count, Conditional comments will not). getElementsByClassName() in Opera (even Opera 10 Alpha) doesnt match elements with 2 or more classes when the one youre looking for is not the first but its also a substring of the first. Read the discussion between me and John Resig on the latters blog post mentioned below if this seems a bit unclear. There is no element.children collection in Firefox 3- . You have to create it yourself by filtering the childNodes collection if it doesnt exist. If your code involves preformatted elements (for instance if you are making a syntax highlighter), beware when setting the innerHTML of those: IE wont preserve line breaks ( \r\n s) and whitespace . You have to use outerHTML , which will actually replace the element so you should find a way to obtain a reference to the newly created one in case you still need to perform stuff on it. To get the dimensions of the viewport, standard compliant browsers use window.innerWidth (and innerHeight ) whereas IE uses document.documentElement.clientWidth (and clientHeight ). To get the scroll offsets of the current page, standard compliant browsers use window.pageXOffset (and pageYOffset ) whereas IE uses document.documentElement.scrollTop (and scrollLeft ). To make matters worse, in both cases above, you need to use document.body instead of document.documentElement when in Quirks mode. John Resig (of the jQuery fame), recently posted a great presentation , which summarized some browser bugs related to DOM functions. A few of the bugs/inconsistencies mentioned above are derived from that presentation. Events When using IEs attachEvent() the this reference inside the callback refers to the useless window object eventObject.target is eventObject.srcElement in IE eventObject.stopPropagation() is eventObject.cancelBubble = true; in IE eventObject.preventDefault() is eventObject.returnValue = false; in IE There are many more event object incompatibilities for specific events (the ones above are for all events). Take a trip to QuirksMode for more information. IE leaks horribly (especially IE6) in various cases . If you register the same event handler X times, IE fires it X times. Determining when the DOM is ready is a complete mess . Firefox and Opera 9+ support the DOMContentLoaded event, Safari doesnt but you can check its document.readyState property and in IE document.readyState is unreliable and you should either inject a deferred script , either poll the DOM untill there are no errors or use an external behavior file . Of course you could always just put a script tag at the bottom of the page, just before the body closing tag, which will fire all attached handlers which is actually the best approach in terms of which way fires earliest (but not too early) according to my tests, but that hardly qualifies as unobtrusive (edit, thanks Sobral!) The Event object is not passed as a parameter to the callback but resides in window.event in older versions of IE Type detection The typeof operator is almost useless: typeof null == &#39;object&#39; typeof new String(&#39;foo&#39;) == &#39;object&#39; typeof [] == &#39;object&#39; Use Object.prototype.toString instead . CSS Although most CSS properties are converted to their JavaScript equivalents in a standard way (characters after dashes are Uppercase, others are lowercase, the dashes get removed), float is an exception: Its converted to cssFloat in most browsers and styleFloat in IE. Check which one exists and use that. Getting the current (computed) style of an element is another complete mess . IE uses element.currentStyle[propertyJS] whereas standard compliant browsers use document.defaultView.getComputedStyle(element, null).getPropertyValue(propertyCSS) . And as if this wasnt enough, there are various problems associated with specific properties or browsers, like: IE returns the cascaded values and not the computed ones (for instance, it might return em s for a property that was specified in em s, and not pixels). Dean Edwards has thought a very clever hack to workaround this and didnt even blog about it (its simply a comment in a blog post of Erik Arvidssons!). Any hidden (via display:none; ) element, yields a width / height / top / right / bottom / left value of zero. auto or normal might be returned for properties that are left at their defaults. For instance, IE does this with width / height for elements that dont have dimensions explicitly set via CSS. In most browsers, shorthands (like border ) will yield a blank string. Youd have to use the most specific property (for instance, border-left-width ). Colors will be returned in different formats across browsers. For instance, IE uses #RRGGBB whereas Mozilla uses rgb(red, green, blue) . So, what now? Never, EVER use a browser detect to solve the problems mentioned above. They can all be solved with feature/object detection, simple one-time tests or defensive coding. I have done it myself (and so did most libraries nowadays I think) so I know its possible. I will not post all of these solutions to avoid bloating this post even more. You can ask me about particular ones in the comments, or read the uncompressed source code of any library that advertises itself as not using browser detects. JavaScript Libraries are a much more interesting read than literature anyway. :P Are the facts mentioned above actually 20? Im not really sure to be honest, it depends on how you count them. I thought that if I put a nice round number in the title, it would be more catchy :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Silent, automatic updates are the way to go</title>
  <link>https://lea.verou.me/2009/02/silent-automatic-updates-are-the-way-to-go/</link>
  <pubDate>Sun, 17 May 2026 00:07:19 +0200</pubDate>
  <description>Recently, PPK stated that he hates Google Chrome’s automatic updates . I disagree. In fact, I think that all browser vendors should enforce automatic updates as violently as Google Chrome does. There should be no option to disable them. For anybody. But what about the user’s freedom of choice? This might sound a bit facist at start, but imagine a world where all browsers would get automatically updated, without the possiblity of an opt-out. If you went online, you would be bound to have the very latest version, regardless of how computer (i)literate you were (Many — if not most — home users that don’t upgrade are like that because they think it’s too difficult for their computer expertise level). Sure, if you were a developer you wouldn’t be able to test a website in older browser versions. But why would you need to do so? If everybody had the latest browser version, you would only develop for the latest version and perhaps for the next one (via nightlies and betas, that could still be separate in that ideal world). Imagine a world where your job wouldn’t have to involve tedious IE6 (and in a few weeks, no IE7 either), Firefox 2, Opera 9.5 and Safari 3.1- testing. A world where you would spend your work hours on more creative stuff, where you wouldn’t want to bang your head on the wall because you know you did nothing wrong but the ancient browser that you are currently testing in is just incompetent and YOU have to fix it’s sh*t. A world where the size of your Javascript code (and the JS libraries’ code) would be half its size and constantly decreasing as new browser versions come out. A world where you would only have 1 CSS file in most websites you develop. A world where you wouldn’t feel so bad because IE8 doesn’t support opacity, border-radius or SVG, because you would know that in 1-2 years everyone would have IE9 and it will probably support them. A world where designing a website would be as much fun as designing your personal blog. Doesn’t such a world sound like a dream? Would it harm anyone? Users would browse a much lighter and beautiful web, with a more feature-rich and secure browser. Developers would work half as much to produce better results and they would enjoy their work more. What about corporate intranets and abandoned sites that won’t keep up? Oh come on, that isn’t a good enough reason to not make that dream come true! Companies and individuals could be allowed to have an older version of the browser installed as well . They still wouldn’t be able to opt out from the automatic upgrade, but they could apply somehow to have an older version of the browser in the same system as well. Similarly to what happens now with browser betas. People would use the older version to access corporate intranet applications and obsolete sites and the latest version to surf the web. I may be overly optimistic, but I think that if a user had both versions of a browser installed, (s)he would prefer the latest wherever (s)he can. Perhaps another step towards enforcing that would be if the OS prevented an older browser version from being set as the default browser, but I guess that would be too hard to do, especially if the browser in question is not the OS default one. Other people who agree with me Aaron Boodman Joe Walker What’s your opinion?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Silent, automatic updates are the way to go</title>
  <link>https://lea.verou.me/2009/02/silent-automatic-updates-are-the-way-to-go/</link>
  <pubDate>Sun, 17 May 2026 00:07:19 +0200</pubDate>
  <description>Recently, PPK stated that he hates Google Chromes automatic updates . I disagree. In fact, I think that all browser vendors should enforce automatic updates as violently as Google Chrome does. There should be no option to disable them. For anybody. But what about the users freedom of choice? This might sound a bit facist at start, but imagine a world where all browsers would get automatically updated, without the possiblity of an opt-out. If you went online, you would be bound to have the very latest version, regardless of how computer (i)literate you were (Many if not most home users that dont upgrade are like that because they think its too difficult for their computer expertise level). Sure, if you were a developer you wouldnt be able to test a website in older browser versions. But why would you need to do so? If everybody had the latest browser version, you would only develop for the latest version and perhaps for the next one (via nightlies and betas, that could still be separate in that ideal world). Imagine a world where your job wouldnt have to involve tedious IE6 (and in a few weeks, no IE7 either), Firefox 2, Opera 9.5 and Safari 3.1- testing. A world where you would spend your work hours on more creative stuff, where you wouldnt want to bang your head on the wall because you know you did nothing wrong but the ancient browser that you are currently testing in is just incompetent and YOU have to fix its sh*t. A world where the size of your Javascript code (and the JS libraries code) would be half its size and constantly decreasing as new browser versions come out. A world where you would only have 1 CSS file in most websites you develop. A world where you wouldnt feel so bad because IE8 doesnt support opacity, border-radius or SVG, because you would know that in 1-2 years everyone would have IE9 and it will probably support them. A world where designing a website would be as much fun as designing your personal blog. Doesnt such a world sound like a dream? Would it harm anyone? Users would browse a much lighter and beautiful web, with a more feature-rich and secure browser. Developers would work half as much to produce better results and they would enjoy their work more. What about corporate intranets and abandoned sites that wont keep up? Oh come on, that isnt a good enough reason to not make that dream come true! Companies and individuals could be allowed to have an older version of the browser installed as well . They still wouldnt be able to opt out from the automatic upgrade, but they could apply somehow to have an older version of the browser in the same system as well. Similarly to what happens now with browser betas. People would use the older version to access corporate intranet applications and obsolete sites and the latest version to surf the web. I may be overly optimistic, but I think that if a user had both versions of a browser installed, (s)he would prefer the latest wherever (s)he can. Perhaps another step towards enforcing that would be if the OS prevented an older browser version from being set as the default browser, but I guess that would be too hard to do, especially if the browser in question is not the OS default one. Other people who agree with me Aaron Boodman Joe Walker Whats your opinion?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Bulletproof, cross-browser RGBA backgrounds, today</title>
  <link>https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/</link>
  <pubDate>Sun, 17 May 2026 00:07:18 +0200</pubDate>
  <description>UPDATE: New version First of all, happy Valentine’s day for yersterday. :) This is the second part of my “ Using CSS3 today ” series. This article discusses current RGBA browser support and ways to use RGBA backgrounds in non-supporting browsers. Bonus gift: A PHP script of mine that creates fallback 1-pixel images on the fly that allow you to easily utilize RGBA backgrounds in any browser that can support png transparency. In addition, the images created are forced to be cached by the client and they are saved on the server’s hard drive for higher performance. Browsers that currently support RGBA These are: Firefox 3+ Safari 2+ Opera 10 (still in beta) Google Chrome In these browsers you can write CSS declarations like: background: rgba(255,200,35,0.5) url(somebackground.png) repeat-x 0 50%; border: 1px solid rgba(0,0,0,0.3); color: rgba(255,255,255,0.8); And they will work flawlessly. Internet Explorer Surprisingly, it seems that Internet Explorer supported RGBA backgrounds long before the others . Of course, with it’s very own properietary syntax , as usual: filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#550000FF, endColorstr=#550000FF); And since nothing is ever simple with IE, IE8 requires a special syntax which has to be put before the first one to work properly in IE8 beta1: -ms-filter: “progid:DXImageTransform.Microsoft.gradient(startColorstr=#550000FF, endColorstr=#550000FF)”; The code above actually draws a gradient from rgba(0,0,255,0.33) to rgba(0,0,255,0.33) using a Microsoft-proprietary “extended” hex format that places the Alpha parameter first (instead of last) and in the range of 00-FF (instead of 0-1). The rest is a usual hex color, in that case #0000FF. Caution: The “gradients” that are created via the gradient filter are placed on top of any backgrounds currently in effect. So, if you want to have a background image as well, the result may not be what you expected. If you provide a solid color as a background, it will also not work as expected (no alpha transparency), since the gradients created are not exactly backgrounds, they are just layers on top of backgrounds. Problems with the filter method Filters are bad for client-side performance. Filters cause the text rendering to be aliased and especially when it’s bold and there is no background-color set it becomes completely unreadable. (the worst disadvantage if you ask me) Filters only work with IE. What about Firefox 2- and Opera 9.6-? Filters are lengthy (especially now that you have to include 2 different syntaxes) so they significantly increase the size of your CSS when used frequently. You have to convert the red, green and blue values to hex to use that method. To use a filter, the element has to have Layout . This is usually done via zoom:1. More non-standard clutter in your CSS. Doesn’t play along well with other workarounds, since it doesn’t modify the background of the element. So, personally, I only use that approach sparingly, in particular, only when “no/minimum external files” is a big requirement. A bulletproof solution My favored approach is to use rgba() for all RGBA-capable browsers and fallback pngs for the ones that don’t support RGBA. However, creating the pngs in Photoshop, or a similar program and then uploading them is too much of a fuss for me to bare (I get bored easily :P ). So, I created a small PHP script that: Creates a 1-pixel png image with the parameters passed for red, green, blue and alpha. No need to convert to hex. Supports named colors, to speed up typing even more for colors that you use commonly in a site (it includes white and black by default, but you may easily add as many as you like). Stores the generated images on the server, so that they don’t have to be created every time (generating images on the fly has quite an important performance impact). Forces the images to be cached on the browser so that they don’t have to be generated every time (even though their size is very small, about 73 bytes). Here it is: rgba.php You use it like this: background: url(rgba.php?r=255&amp;g=100&amp;b=0&amp;a=50) repeat; background: rgba(255,100,0,0.5); or, for named colors: background: url(rgba.php?name=white&amp;a=50) repeat; background: rgba(255,255,255,0.5); Browsers that are RGBA-aware will follow the second background declaration and will not even try to fetch the png. Browsers that are RGBA-incapable will ignore the second declaration, since they don’t understand it, and stick with the first one. Don’t change the order of the declarations: The png one goes first, the rgba() one goes second. If you put the png one second, it will always be applied, even if the browser does support rgba. Before you use it, open it with an editor to specify the directory you want it to use to store the created pngs (the default is &#39;colors/&#39; ) and add any color names you want to be able to easily address (the defaults are white and black). If the directory you specify does not exist or isn’t writeable you’ll get an error. Caution: You have to enter the alpha value in a scale of 0 to 100, and not from 0 to 1 as in the CSS. This is because you have to urlencode dots to transfer them via a URI and it would complicate things for anyone who used this. Edit: It seems that IE8 sometimes doesn’t cache the image produced. I should investigate this further. IMPORTANT: If your PHP version is below 5.1.2 perform this change in the PHP file or it won’t work. Why not data:// URIs? Of course, you could combine the IE gradient filter, rgba() and data:// URIs for a cross-browser solution that does not depend on external files . However, this approach has some disadvantages: All the disadvantages of filters mentioned above . You can’t be spontaneous in your CSS and changes are difficult. Every time you want to use RGBA, you have to resort to some converter to create the png and it’s data:// URI. Unless you are some kind of a cyborg with an embedded base64 encoder/decoder in your head :P Larger filesize (you have to use 4-5 declarations (the rgba() one, the data:// one, 2 filters, one for IE7- and one for IE8 and a zoom:1; to give the element “layout” so that filters can be applied) instead of 2, and the data:// URI has the same size as the png). Also, the data:// URI can not be cached so every time you use it, you increase the filesize even more. Ok, you save an http request per use, but is it worth it? and some advantages: You will not see the site without a background for even a single millisecond. Since the png is embedded in the CSS, it’s loaded as soon as the CSS itself is loaded. If your site background is too dark and you rely on the RGBA background to make the content legible, you might want to consider this solution. No external files, no extra http requests. The filter method works in IE6- without the script for transparent PNGs. Choose the method that fits your needs better. :) RGBA is not only for backgrounds! It’s also for every CSS property that accepts color values. However, backgrounds in most cases are the easiest to workaround. As for borders, if you want solid ones, you can simulate them sometimes by wrapping a padded container with an RGBA background around your actual one and giving it as much padding as your desired border-width. For text color, sometimes you can fake that with opacity. However, these “solutions” are definitely incomplete, so you’d probably have to wait for full RGBA support and provide solid color fallbacks for those (unless someone comes up with an ingenious solution in , it’s common these days :P ).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 border-radius, today</title>
  <link>https://lea.verou.me/2009/02/css3-border-radius-today/</link>
  <pubDate>Sun, 17 May 2026 00:07:18 +0200</pubDate>
  <description>This is the first one from a series of articles I’m going to write about using CSS3 properties or values today . I’ll cover everything I have found out while using them, including various browser quirks and bugs I know of or have personally filed regarding them. In this part I’ll discuss ways to create rounded corners without images and if possible without JavaScript in the most cross-browser fashion. I will not cover irregular curves in this article, since I’ve yet to find any person who actually needed them, even once, including myself and browser support for them is far worse. Caution: The contents of a container with border-radius set are NOT clipped according to the border radius in any implementation/workaround mentioned below, and no, setting overflow to hidden won’t help (and even if it did, you’d risk text missing). You should specify a proper border-radius and/or padding to them if you want them to follow their container’s curves properly. This could allow for some nice effects but most of the time it’s just a pain in the a$$. Mozilla Firefox Firefox supports rounded corners since version 2. However incomplete support in version 2 made designers sceptical to use them. The problem was that the rounded corners created were aliased back then, and also did not crop the background image, so if you had one, no rounded corners for you. This was fixed in FF3, so now more and more designers are starting to use them. The syntax is -moz-border-radius: [Number][unit]; This is effectively a shorthand for: -moz-border-radius-bottomleft: [Number][unit]; -moz-border-radius-bottomright: [Number][unit]; -moz-border-radius-topleft: [Number][unit]; -moz-border-radius-topright: [Number][unit]; You don’t need to specify all these properties though, even if you wan’t different measures per corner, as -moz-border-radius functions as a regular CSS shorthand, allowing us to specify all 4 corners at once. It can be used in the following ways: -moz-border-radius: [Top-left and Bottom-right] [Top-right and bottom-left]; -moz-border-radius: [Top-left] [Top-right and bottom-left] [Bottom-right]; -moz-border-radius: [Top-left] [Top-right] [Bottom-right] [Bottom-left]; A good mnemonic rule for the order of the values is that they are arranged clockwise, starting from Top left. Apple Safari Safari also implements CSS3 border-radius, but in a quite different way. If you want to set all four corners to the same border-radius, the process is almost identical. The only thing needed is: -webkit-border-radius: [Number][unit] However, things start to get tricky when you want to specify different radiuses per corner. Webkit does not support a shorthand syntax, since it chose to implement the spec closely, sacrifycing clarity but allowing for more flexibility. To cut a long story short, Webkit supports irregular curves instead of just circle quarters on each corner , so if you try to add 2 values, the result will be horrendous . So, you have to specify all four properties (or less if you want some of them to be square). To make matters even worse, the way the names of the properties are structured is different. There is one more dash, and the position of the corner styled by each property is not at the end but before -radius : -webkit-border-top-left-radius -webkit-border-top-right-radius -webkit-border-bottom-left-radius -webkit-border-bottom-right-radius Caution: If the dimensions of your element are not enough to accomodate the rounded corners, they will be square in Webkit-based browsers. Specify a min-width / min-height or enough padding to avoid this. Google Chrome Since Google Chrome is based on Webkit, its border-radius support is like Safari’s. However, it’s haunted by an ugly bug: It renders the rounded corners aliased . :( Opera The bad news is that Opera does not implement the CSS3 border-radius yet (it will in the future, confirmed ). The good news is that it allows for SVG backgrounds since version 9.5. The even better news is that it supports data:// URIs, so you can embed the SVG in your CSS, without resorting to external files as someone recently pointed out to me . Alexis Deveria was clever enough to even create a generator for them , so that you could easily specify the background, border width and border-color and get the data URI instantly. This is a quite useful tool, but lacks some features (for instance you might want the background to be semi-transparent, like the one used in this blog). It’s ok for most cases though. While Opera’s current lack of border-radius support is disappointing, you can utilize it pretty well with this method and if you know SVG well enough yourself you can create stunning effects. Internet Explorer (aka “The Web designer’s nemesis”) There’s no need to tell you that IE doesn’t support border-radius or SVG backgrounds, even in it’s latest version, right? You probably guessed already. There is some hope here though, a clever guy named Drew Diller carefully researched the MS-proprietary VML language and came up with a script that utilizes it to create rounded corners in IE . The bad news is that MS when releasing IE8 fixed some things and messed up others, so the script barely works on it. It also has some other shortcomings , but for most cases it can be a great tool (for IE7 and below, unless MS surprises us and fixes the VML regressions in IE8 before the stable). Also, if rounded corners are not crucial to your design and you don’t get too much traffic from IE users, you might consider ignoring IE altogether and having square corners in it. This way you’re also serving the greater good, since when IE users see your site in a supporting browser, they’ll conclude that “Oh, this browser shows the web nicer!” and the site will still be just as usable (in most cases rounded corners are not that crucial for usability, although they enchance it a bit ). Afterword I hope this article helped you learn something new. If you found any mistakes or inaccuracies, don’t hesitate to leave a comment, I don’t know everything and I’m not god. :) One thing I have in mind is creating a PHP script that takes care of all these incompatibilities for you and caches the result. I don’t know if I’ll ever find the time to write it though, especially before someone else does :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Bulletproof, cross-browser RGBA backgrounds, today</title>
  <link>https://lea.verou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/</link>
  <pubDate>Sun, 17 May 2026 00:07:18 +0200</pubDate>
  <description>UPDATE: New version First of all, happy Valentines day for yersterday. :) This is the second part of my Using CSS3 today series. This article discusses current RGBA browser support and ways to use RGBA backgrounds in non-supporting browsers. Bonus gift: A PHP script of mine that creates fallback 1-pixel images on the fly that allow you to easily utilize RGBA backgrounds in any browser that can support png transparency. In addition, the images created are forced to be cached by the client and they are saved on the servers hard drive for higher performance. Browsers that currently support RGBA These are: Firefox 3+ Safari 2+ Opera 10 (still in beta) Google Chrome In these browsers you can write CSS declarations like: background: rgba(255,200,35,0.5) url(somebackground.png) repeat-x 0 50%; border: 1px solid rgba(0,0,0,0.3); color: rgba(255,255,255,0.8); And they will work flawlessly. Internet Explorer Surprisingly, it seems that Internet Explorer supported RGBA backgrounds long before the others . Of course, with its very own properietary syntax , as usual: filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#550000FF, endColorstr=#550000FF); And since nothing is ever simple with IE, IE8 requires a special syntax which has to be put before the first one to work properly in IE8 beta1: -ms-filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#550000FF, endColorstr=#550000FF); The code above actually draws a gradient from rgba(0,0,255,0.33) to rgba(0,0,255,0.33) using a Microsoft-proprietary extended hex format that places the Alpha parameter first (instead of last) and in the range of 00-FF (instead of 0-1). The rest is a usual hex color, in that case #0000FF. Caution: The gradients that are created via the gradient filter are placed on top of any backgrounds currently in effect. So, if you want to have a background image as well, the result may not be what you expected. If you provide a solid color as a background, it will also not work as expected (no alpha transparency), since the gradients created are not exactly backgrounds, they are just layers on top of backgrounds. Problems with the filter method Filters are bad for client-side performance. Filters cause the text rendering to be aliased and especially when its bold and there is no background-color set it becomes completely unreadable. (the worst disadvantage if you ask me) Filters only work with IE. What about Firefox 2- and Opera 9.6-? Filters are lengthy (especially now that you have to include 2 different syntaxes) so they significantly increase the size of your CSS when used frequently. You have to convert the red, green and blue values to hex to use that method. To use a filter, the element has to have Layout . This is usually done via zoom:1. More non-standard clutter in your CSS. Doesnt play along well with other workarounds, since it doesnt modify the background of the element. So, personally, I only use that approach sparingly, in particular, only when no/minimum external files is a big requirement. A bulletproof solution My favored approach is to use rgba() for all RGBA-capable browsers and fallback pngs for the ones that dont support RGBA. However, creating the pngs in Photoshop, or a similar program and then uploading them is too much of a fuss for me to bare (I get bored easily :P ). So, I created a small PHP script that: Creates a 1-pixel png image with the parameters passed for red, green, blue and alpha. No need to convert to hex. Supports named colors, to speed up typing even more for colors that you use commonly in a site (it includes white and black by default, but you may easily add as many as you like). Stores the generated images on the server, so that they dont have to be created every time (generating images on the fly has quite an important performance impact). Forces the images to be cached on the browser so that they dont have to be generated every time (even though their size is very small, about 73 bytes). Here it is: rgba.php You use it like this: background: url(rgba.php?r=255&amp;g=100&amp;b=0&amp;a=50) repeat; background: rgba(255,100,0,0.5); or, for named colors: background: url(rgba.php?name=white&amp;a=50) repeat; background: rgba(255,255,255,0.5); Browsers that are RGBA-aware will follow the second background declaration and will not even try to fetch the png. Browsers that are RGBA-incapable will ignore the second declaration, since they dont understand it, and stick with the first one. Dont change the order of the declarations: The png one goes first, the rgba() one goes second. If you put the png one second, it will always be applied, even if the browser does support rgba. Before you use it, open it with an editor to specify the directory you want it to use to store the created pngs (the default is &#39;colors/&#39; ) and add any color names you want to be able to easily address (the defaults are white and black). If the directory you specify does not exist or isnt writeable youll get an error. Caution: You have to enter the alpha value in a scale of 0 to 100, and not from 0 to 1 as in the CSS. This is because you have to urlencode dots to transfer them via a URI and it would complicate things for anyone who used this. Edit: It seems that IE8 sometimes doesnt cache the image produced. I should investigate this further. IMPORTANT: If your PHP version is below 5.1.2 perform this change in the PHP file or it wont work. Why not data:// URIs? Of course, you could combine the IE gradient filter, rgba() and data:// URIs for a cross-browser solution that does not depend on external files . However, this approach has some disadvantages: All the disadvantages of filters mentioned above . You cant be spontaneous in your CSS and changes are difficult. Every time you want to use RGBA, you have to resort to some converter to create the png and its data:// URI. Unless you are some kind of a cyborg with an embedded base64 encoder/decoder in your head :P Larger filesize (you have to use 4-5 declarations (the rgba() one, the data:// one, 2 filters, one for IE7- and one for IE8 and a zoom:1; to give the element layout so that filters can be applied) instead of 2, and the data:// URI has the same size as the png). Also, the data:// URI can not be cached so every time you use it, you increase the filesize even more. Ok, you save an http request per use, but is it worth it? and some advantages: You will not see the site without a background for even a single millisecond. Since the png is embedded in the CSS, its loaded as soon as the CSS itself is loaded. If your site background is too dark and you rely on the RGBA background to make the content legible, you might want to consider this solution. No external files, no extra http requests. The filter method works in IE6- without the script for transparent PNGs. Choose the method that fits your needs better. :) RGBA is not only for backgrounds! Its also for every CSS property that accepts color values. However, backgrounds in most cases are the easiest to workaround. As for borders, if you want solid ones, you can simulate them sometimes by wrapping a padded container with an RGBA background around your actual one and giving it as much padding as your desired border-width. For text color, sometimes you can fake that with opacity. However, these solutions are definitely incomplete, so youd probably have to wait for full RGBA support and provide solid color fallbacks for those (unless someone comes up with an ingenious solution in , its common these days :P ).</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>CSS3 border-radius, today</title>
  <link>https://lea.verou.me/2009/02/css3-border-radius-today/</link>
  <pubDate>Sun, 17 May 2026 00:07:18 +0200</pubDate>
  <description>This is the first one from a series of articles Im going to write about using CSS3 properties or values today . Ill cover everything I have found out while using them, including various browser quirks and bugs I know of or have personally filed regarding them. In this part Ill discuss ways to create rounded corners without images and if possible without JavaScript in the most cross-browser fashion. I will not cover irregular curves in this article, since Ive yet to find any person who actually needed them, even once, including myself and browser support for them is far worse. Caution: The contents of a container with border-radius set are NOT clipped according to the border radius in any implementation/workaround mentioned below, and no, setting overflow to hidden wont help (and even if it did, youd risk text missing). You should specify a proper border-radius and/or padding to them if you want them to follow their containers curves properly. This could allow for some nice effects but most of the time its just a pain in the a$$. Mozilla Firefox Firefox supports rounded corners since version 2. However incomplete support in version 2 made designers sceptical to use them. The problem was that the rounded corners created were aliased back then, and also did not crop the background image, so if you had one, no rounded corners for you. This was fixed in FF3, so now more and more designers are starting to use them. The syntax is -moz-border-radius: [Number][unit]; This is effectively a shorthand for: -moz-border-radius-bottomleft: [Number][unit]; -moz-border-radius-bottomright: [Number][unit]; -moz-border-radius-topleft: [Number][unit]; -moz-border-radius-topright: [Number][unit]; You dont need to specify all these properties though, even if you want different measures per corner, as -moz-border-radius functions as a regular CSS shorthand, allowing us to specify all 4 corners at once. It can be used in the following ways: -moz-border-radius: [Top-left and Bottom-right] [Top-right and bottom-left]; -moz-border-radius: [Top-left] [Top-right and bottom-left] [Bottom-right]; -moz-border-radius: [Top-left] [Top-right] [Bottom-right] [Bottom-left]; A good mnemonic rule for the order of the values is that they are arranged clockwise, starting from Top left. Apple Safari Safari also implements CSS3 border-radius, but in a quite different way. If you want to set all four corners to the same border-radius, the process is almost identical. The only thing needed is: -webkit-border-radius: [Number][unit] However, things start to get tricky when you want to specify different radiuses per corner. Webkit does not support a shorthand syntax, since it chose to implement the spec closely, sacrifycing clarity but allowing for more flexibility. To cut a long story short, Webkit supports irregular curves instead of just circle quarters on each corner , so if you try to add 2 values, the result will be horrendous . So, you have to specify all four properties (or less if you want some of them to be square). To make matters even worse, the way the names of the properties are structured is different. There is one more dash, and the position of the corner styled by each property is not at the end but before -radius : -webkit-border-top-left-radius -webkit-border-top-right-radius -webkit-border-bottom-left-radius -webkit-border-bottom-right-radius Caution: If the dimensions of your element are not enough to accomodate the rounded corners, they will be square in Webkit-based browsers. Specify a min-width / min-height or enough padding to avoid this. Google Chrome Since Google Chrome is based on Webkit, its border-radius support is like Safaris. However, its haunted by an ugly bug: It renders the rounded corners aliased . :( Opera The bad news is that Opera does not implement the CSS3 border-radius yet (it will in the future, confirmed ). The good news is that it allows for SVG backgrounds since version 9.5. The even better news is that it supports data:// URIs, so you can embed the SVG in your CSS, without resorting to external files as someone recently pointed out to me . Alexis Deveria was clever enough to even create a generator for them , so that you could easily specify the background, border width and border-color and get the data URI instantly. This is a quite useful tool, but lacks some features (for instance you might want the background to be semi-transparent, like the one used in this blog). Its ok for most cases though. While Operas current lack of border-radius support is disappointing, you can utilize it pretty well with this method and if you know SVG well enough yourself you can create stunning effects. Internet Explorer (aka The Web designers nemesis) Theres no need to tell you that IE doesnt support border-radius or SVG backgrounds, even in its latest version, right? You probably guessed already. There is some hope here though, a clever guy named Drew Diller carefully researched the MS-proprietary VML language and came up with a script that utilizes it to create rounded corners in IE . The bad news is that MS when releasing IE8 fixed some things and messed up others, so the script barely works on it. It also has some other shortcomings , but for most cases it can be a great tool (for IE7 and below, unless MS surprises us and fixes the VML regressions in IE8 before the stable). Also, if rounded corners are not crucial to your design and you dont get too much traffic from IE users, you might consider ignoring IE altogether and having square corners in it. This way youre also serving the greater good, since when IE users see your site in a supporting browser, theyll conclude that Oh, this browser shows the web nicer! and the site will still be just as usable (in most cases rounded corners are not that crucial for usability, although they enchance it a bit ). Afterword I hope this article helped you learn something new. If you found any mistakes or inaccuracies, dont hesitate to leave a comment, I dont know everything and Im not god. :) One thing I have in mind is creating a PHP script that takes care of all these incompatibilities for you and caches the result. I dont know if Ill ever find the time to write it though, especially before someone else does :P</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Find the vendor prefix of the current browser</title>
  <link>https://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/</link>
  <pubDate>Sun, 17 May 2026 00:07:17 +0200</pubDate>
  <description>As you probably know already, when browsers implement an experimental or proprietary CSS property, they prefix it with their “vendor prefix”, so that 1) it doesn’t collide with other properties and 2) you can choose whether to use it or not in that particular browser, since it’s support might be wrong or incomplete. When writing CSS you probably just include all properties and rest in peace, since browsers ignore properties they don’t know. However, when changing a style via javascript it’s quite a waste to do that. Instead of iterating over all possible vendor prefixes every time to test if a prefixed version of a specific property is supported, we can create a function that returns the current browser’s prefix and caches the result, so that no redundant iterations are performed afterwards. How can we create such a function though? Things to consider The way CSS properties are converted their JS counterparts: Every character after a dash is capitalized, and all others are lowercase. The only exception is the new -ms- prefixed properties: Microsoft did it again and made their JS counterparts start with a lowercase m ! Vendor prefixes always start with a dash and end with a dash Normal CSS properties never start with a dash Algorithm Iterate over all supported properties and find one that starts with a known prefix. Return the prefix. If no property that starts with a known prefix was found, return the empty string. JavaScript code function getVendorPrefix() { var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/; var someScript = document.getElementsByTagName(&#39;script&#39;)[0]; for(var prop in someScript.style) { if(regex.test(prop)) { // test is faster than match, so it&#39;s better to perform // that on the lot and match only when necessary return prop.match(regex)[0]; } } // Nothing found so far? return &#39;&#39;; } Caution: Don’t try to use someScript.style.hasOwnProperty(prop). It’s missing on purpose, since if these properties aren’t set on the particular element, hasOwnProperty will return false and the property will not be checked. Browser bugs In a perfect world we would be done by now. However, if you try running it in Webkit based browsers, you will notice that the empty string is returned. This is because for some reason, Webkit does not enumerate over empty CSS properties. To solve this, we’d have to check for the support of a property that exists in all webkit-based browsers. This property should be one of the oldest -webkit-something properties that were implemented in the browser, so that our function returns correct results for as old browser versions as possible. -webkit-opacity seems like a good candidate but I’d appreciate any better or more well-documented picks. We’d also have to test -khtml-opacity as it seems that Safari had the -khtml- prefix before the -webkit- prefix . So the updated code would be: function getVendorPrefix() { var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/; var someScript = document.getElementsByTagName(&#39;script&#39;)[0]; for(var prop in someScript.style) { if(regex.test(prop)) { // test is faster than match, so it&#39;s better to perform // that on the lot and match only when necessary return prop.match(regex)[0]; } } // Nothing found so far? Webkit does not enumerate over the CSS properties of the style object. // However (prop in style) returns the correct value, so we&#39;ll have to test for // the precence of a specific property if(&#39;WebkitOpacity&#39; in someScript.style) return &#39;Webkit&#39;; if(&#39;KhtmlOpacity&#39; in someScript.style) return &#39;Khtml&#39;; return &#39;&#39;; } By the way, if Webkit ever fixes that bug, the result will be returned straight from the loop, since we have added the Webkit prefix in the regexp as well. Performance improvements There is no need for all this code to run every time the function is called. The vendor prefix does not change, especially during the session :P Consequently, we can cache the result after the first time, and return the cached value afterwards: function getVendorPrefix() { if(&#39;result&#39; in arguments.callee) return arguments.callee.result; var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/; var someScript = document.getElementsByTagName(&#39;script&#39;)[0]; for(var prop in someScript.style) { if(regex.test(prop)) { // test is faster than match, so it&#39;s better to perform // that on the lot and match only when necessary return arguments.callee.result = prop.match(regex)[0]; } } // Nothing found so far? Webkit does not enumerate over the CSS properties of the style object. // However (prop in style) returns the correct value, so we&#39;ll have to test for // the precence of a specific property if(&#39;WebkitOpacity&#39; in someScript.style) return arguments.callee.result = &#39;Webkit&#39;; if(&#39;KhtmlOpacity&#39; in someScript.style) return arguments.callee.result = &#39;Khtml&#39;; return arguments.callee.result = &#39;&#39;; } Afterthoughts Please don’t use this as a browser detection function! Apart from the fact that browser detects are a bad way to code 99.9% of the time, it’s also unreliable for IE, since Microsoft added a vendor prefix in IE8 only. Before that it followed the classic attitude “We have a large market share so standards and conventions don’t apply to us”. There are some browsers that support multiple prefixes. If that is crucial for you, you may want to return an array with all prefixes instead of a string. It shouldn’t be difficult to alter the code above to do that. I’ll only inform you that from my tests, Opera also has Apple , Xn and Wap prefixes and Safari and Chrome also have Khtml . I wish there was a list somewhere with ALL vendor prefixes… If you know such a page, please leave a comment.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Find the vendor prefix of the current browser</title>
  <link>https://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/</link>
  <pubDate>Sun, 17 May 2026 00:07:17 +0200</pubDate>
  <description>As you probably know already, when browsers implement an experimental or proprietary CSS property, they prefix it with their vendor prefix, so that 1) it doesnt collide with other properties and 2) you can choose whether to use it or not in that particular browser, since its support might be wrong or incomplete. When writing CSS you probably just include all properties and rest in peace, since browsers ignore properties they dont know. However, when changing a style via javascript its quite a waste to do that. Instead of iterating over all possible vendor prefixes every time to test if a prefixed version of a specific property is supported, we can create a function that returns the current browsers prefix and caches the result, so that no redundant iterations are performed afterwards. How can we create such a function though? Things to consider The way CSS properties are converted their JS counterparts: Every character after a dash is capitalized, and all others are lowercase. The only exception is the new -ms- prefixed properties: Microsoft did it again and made their JS counterparts start with a lowercase m ! Vendor prefixes always start with a dash and end with a dash Normal CSS properties never start with a dash Algorithm Iterate over all supported properties and find one that starts with a known prefix. Return the prefix. If no property that starts with a known prefix was found, return the empty string. JavaScript code function getVendorPrefix() { var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/; var someScript = document.getElementsByTagName(&#39;script&#39;)[0]; for(var prop in someScript.style) { if(regex.test(prop)) { // test is faster than match, so it&#39;s better to perform // that on the lot and match only when necessary return prop.match(regex)[0]; } } // Nothing found so far? return &#39;&#39;; } Caution: Dont try to use someScript.style.hasOwnProperty(prop). Its missing on purpose, since if these properties arent set on the particular element, hasOwnProperty will return false and the property will not be checked. Browser bugs In a perfect world we would be done by now. However, if you try running it in Webkit based browsers, you will notice that the empty string is returned. This is because for some reason, Webkit does not enumerate over empty CSS properties. To solve this, wed have to check for the support of a property that exists in all webkit-based browsers. This property should be one of the oldest -webkit-something properties that were implemented in the browser, so that our function returns correct results for as old browser versions as possible. -webkit-opacity seems like a good candidate but Id appreciate any better or more well-documented picks. Wed also have to test -khtml-opacity as it seems that Safari had the -khtml- prefix before the -webkit- prefix . So the updated code would be: function getVendorPrefix() { var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/; var someScript = document.getElementsByTagName(&#39;script&#39;)[0]; for(var prop in someScript.style) { if(regex.test(prop)) { // test is faster than match, so it&#39;s better to perform // that on the lot and match only when necessary return prop.match(regex)[0]; } } // Nothing found so far? Webkit does not enumerate over the CSS properties of the style object. // However (prop in style) returns the correct value, so we&#39;ll have to test for // the precence of a specific property if(&#39;WebkitOpacity&#39; in someScript.style) return &#39;Webkit&#39;; if(&#39;KhtmlOpacity&#39; in someScript.style) return &#39;Khtml&#39;; return &#39;&#39;; } By the way, if Webkit ever fixes that bug, the result will be returned straight from the loop, since we have added the Webkit prefix in the regexp as well. Performance improvements There is no need for all this code to run every time the function is called. The vendor prefix does not change, especially during the session :P Consequently, we can cache the result after the first time, and return the cached value afterwards: function getVendorPrefix() { if(&#39;result&#39; in arguments.callee) return arguments.callee.result; var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/; var someScript = document.getElementsByTagName(&#39;script&#39;)[0]; for(var prop in someScript.style) { if(regex.test(prop)) { // test is faster than match, so it&#39;s better to perform // that on the lot and match only when necessary return arguments.callee.result = prop.match(regex)[0]; } } // Nothing found so far? Webkit does not enumerate over the CSS properties of the style object. // However (prop in style) returns the correct value, so we&#39;ll have to test for // the precence of a specific property if(&#39;WebkitOpacity&#39; in someScript.style) return arguments.callee.result = &#39;Webkit&#39;; if(&#39;KhtmlOpacity&#39; in someScript.style) return arguments.callee.result = &#39;Khtml&#39;; return arguments.callee.result = &#39;&#39;; } Afterthoughts Please dont use this as a browser detection function! Apart from the fact that browser detects are a bad way to code 99.9% of the time, its also unreliable for IE, since Microsoft added a vendor prefix in IE8 only. Before that it followed the classic attitude We have a large market share so standards and conventions dont apply to us. There are some browsers that support multiple prefixes. If that is crucial for you, you may want to return an array with all prefixes instead of a string. It shouldnt be difficult to alter the code above to do that. Ill only inform you that from my tests, Opera also has Apple , Xn and Wap prefixes and Safari and Chrome also have Khtml . I wish there was a list somewhere with ALL vendor prefixes If you know such a page, please leave a comment.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Extend Math.round, Math.ceil and Math.floor to allow for precision</title>
  <link>https://lea.verou.me/2009/02/extend-mathround-mathceil-and-mathfloor-to-allow-precision/</link>
  <pubDate>Sun, 17 May 2026 00:07:16 +0200</pubDate>
  <description>Math.round , Math.ceil and Math.floor are very useful functions. However, when using them, I find myself many times needing to specify a precision level. You don’t always want to round to an integer, you often just want to strip away some of the decimals. We probably all know that if we have a function to round to integers, we can round to X decimals by doing Math.round(num*Math.pow(10,X)) / Math.pow(10,X) . This kind of duck typing can get tedious, so usually, you roll your own function to do that. However, why not just add that extra functionality to the functions that already exist and you’re accustomed to? Let’s start with Math.round . It’s the most needed one anyway. Firstly we’ll have to store the native function somewhere, since we’re going to replace it. So we do something along the lines of: Math._round = Math.round; Now let’s sigh replace the native Math.round with our own: Math.round = function(number, precision) { precision = Math.abs(parseInt(precision)) || 0; var coefficient = Math.pow(10, precision); return Math._round(number*coefficient)/coefficient; } And guess what? It still works the old way too, so your old scripts won’t break. So now, let’s go to Math.ceil and Math.floor . If you notice, the only thing that changes is the function name. Everything else is the same. So, even though we could copy-paste the code above and change the names, we would end up with triple the size of the code that we need and we would have also violated the DRY principle. So we could put the names of the functions in an array, and loop over it instead: (function(){ var MathFns = [&#39;round&#39;, &#39;floor&#39;, &#39;ceil&#39; ]; for(var i = MathFns.length; i&gt;-1; i--) { Math[&#39;_&#39; + MathFns[i]] = Math[MathFns[i]]; Math[MathFns[i]] = function(number, precision) { precision = Math.abs(parseInt(precision)) || 0; var coefficient = Math.pow(10, precision); return Math[&#39;_&#39; + MathFns[i]](number*coefficient)/coefficient; } } })(); Why the closure? To allow us to be free in defining our variables without polluting the global namespace. In case Array.prototype.forEach() was cross-browser or if you have mutated the Array prototype to add it for non-supporting ones, you could easily do that: [&#39;round&#39;, &#39;floor&#39;, &#39;ceil&#39; ].forEach(function(funcName){ Math[&#39;_&#39; + funcName] = Math[funcName]; Math[funcName] = function(number, precision) { precision = Math.abs(parseInt(precision)) || 0; var coefficient = Math.pow(10, precision); return Math[&#39;_&#39; + funcName](number*coefficient)/coefficient; } }); No closures and much easier to read code. However, nothing comes without a cost. In this case, the cost is performance. In my tests, the new function takes about twice the time of the native one. Adding a conditional to check if the precision is falsy and use the native function directly if so, doesn’t improve the results much, and it would slow the function down for precision values &gt; 0. Of course the speed would be just as much if the function was a normal one and not a replacement for Math[something], that doesn’t have anything to do with it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>JS library detector</title>
  <link>https://lea.verou.me/2009/02/js-library-detector/</link>
  <pubDate>Sun, 17 May 2026 00:07:16 +0200</pubDate>
  <description>Ever wondered which JavaScript library (if any) is hidden beneath the bells &amp; whistles of each site you gazed at? Since I am a curious person, I find myself wondering every time, so after a bit of research, I wrapped up a little bookmarklet that instantly told me the answer every time. The logic behind it is that every JavaScript library creates at least one global variable with an easily recognizable name. For most JavaScript libraries, this is simply their name (Prototype, jQuery, DOMAssistant, MooTools, dojo). For some others, its something close enough to their name (YAHOO for YUI, Scriptaculous for script.aculo.us , Ext for ExtJS). So if you check the precence of this global variable, you are effectively checking for the precence of the related framework. Most of them also contain a property with their version (which is usually named ‘version’ or ‘Version’ or ‘VERSION’ (in YUI)) - in fact the only library that did not contain such a property was DOMAssistant. So, after a sneak peek at their code, I could easily set up some conditionals that check whether a certain library exists in the page and if so, alert its name and version. If multiple libraries exist at the same page, multiple popups will appear. So, here is the bookmarklet: JS library detector Just drag it to your bookmarks toolbar and it’s ready. And here is the human-readable code: if(&#39;Prototype&#39; in window) { var ret = &#39;Prototype &#39; + Prototype.Version; if(&#39;Scriptaculous&#39; in window) ret += &#39; with script.aculo.us &#39; + Scriptaculous.Version; alert(ret); } if(&#39;jQuery&#39; in window) alert(&#39;jQuery &#39; + jQuery.fn.jquery); if(&#39;MooTools&#39; in window) alert(&#39;MooTools &#39; + MooTools.version); if(&#39;YAHOO&#39; in window) alert(&#39;YUI &#39; + YAHOO.VERSION); if(&#39;dojo&#39; in window) alert(&#39;Dojo &#39; + dojo.version); if(&#39;Ext&#39; in window) alert(&#39;ExtJS &#39; + Ext.version); if(&#39;DOMAssistant&#39; in window) alert(&#39;DOMAssistant&#39;); Am I nuts? Certainly. Has it been useful to me? Absolutely.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Extend Math.round, Math.ceil and Math.floor to allow for precision</title>
  <link>https://lea.verou.me/2009/02/extend-mathround-mathceil-and-mathfloor-to-allow-precision/</link>
  <pubDate>Sun, 17 May 2026 00:07:16 +0200</pubDate>
  <description>Math.round , Math.ceil and Math.floor are very useful functions. However, when using them, I find myself many times needing to specify a precision level. You dont always want to round to an integer, you often just want to strip away some of the decimals. We probably all know that if we have a function to round to integers, we can round to X decimals by doing Math.round(num*Math.pow(10,X)) / Math.pow(10,X) . This kind of duck typing can get tedious, so usually, you roll your own function to do that. However, why not just add that extra functionality to the functions that already exist and youre accustomed to? Lets start with Math.round . Its the most needed one anyway. Firstly well have to store the native function somewhere, since were going to replace it. So we do something along the lines of: Math._round = Math.round; Now lets sigh replace the native Math.round with our own: Math.round = function(number, precision) { precision = Math.abs(parseInt(precision)) || 0; var coefficient = Math.pow(10, precision); return Math._round(number*coefficient)/coefficient; } And guess what? It still works the old way too, so your old scripts wont break. So now, lets go to Math.ceil and Math.floor . If you notice, the only thing that changes is the function name. Everything else is the same. So, even though we could copy-paste the code above and change the names, we would end up with triple the size of the code that we need and we would have also violated the DRY principle. So we could put the names of the functions in an array, and loop over it instead: (function(){ var MathFns = [&#39;round&#39;, &#39;floor&#39;, &#39;ceil&#39; ]; for(var i = MathFns.length; i&gt;-1; i--) { Math[&#39;_&#39; + MathFns[i]] = Math[MathFns[i]]; Math[MathFns[i]] = function(number, precision) { precision = Math.abs(parseInt(precision)) || 0; var coefficient = Math.pow(10, precision); return Math[&#39;_&#39; + MathFns[i]](number*coefficient)/coefficient; } } })(); Why the closure? To allow us to be free in defining our variables without polluting the global namespace. In case Array.prototype.forEach() was cross-browser or if you have mutated the Array prototype to add it for non-supporting ones, you could easily do that: [&#39;round&#39;, &#39;floor&#39;, &#39;ceil&#39; ].forEach(function(funcName){ Math[&#39;_&#39; + funcName] = Math[funcName]; Math[funcName] = function(number, precision) { precision = Math.abs(parseInt(precision)) || 0; var coefficient = Math.pow(10, precision); return Math[&#39;_&#39; + funcName](number*coefficient)/coefficient; } }); No closures and much easier to read code. However, nothing comes without a cost. In this case, the cost is performance. In my tests, the new function takes about twice the time of the native one. Adding a conditional to check if the precision is falsy and use the native function directly if so, doesnt improve the results much, and it would slow the function down for precision values &gt; 0. Of course the speed would be just as much if the function was a normal one and not a replacement for Math[something], that doesnt have anything to do with it.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>JS library detector</title>
  <link>https://lea.verou.me/2009/02/js-library-detector/</link>
  <pubDate>Sun, 17 May 2026 00:07:16 +0200</pubDate>
  <description>Ever wondered which JavaScript library (if any) is hidden beneath the bells &amp; whistles of each site you gazed at? Since I am a curious person, I find myself wondering every time, so after a bit of research, I wrapped up a little bookmarklet that instantly told me the answer every time. The logic behind it is that every JavaScript library creates at least one global variable with an easily recognizable name. For most JavaScript libraries, this is simply their name (Prototype, jQuery, DOMAssistant, MooTools, dojo). For some others, its something close enough to their name (YAHOO for YUI, Scriptaculous for script.aculo.us , Ext for ExtJS). So if you check the precence of this global variable, you are effectively checking for the precence of the related framework. Most of them also contain a property with their version (which is usually named version or Version or VERSION (in YUI)) - in fact the only library that did not contain such a property was DOMAssistant. So, after a sneak peek at their code, I could easily set up some conditionals that check whether a certain library exists in the page and if so, alert its name and version. If multiple libraries exist at the same page, multiple popups will appear. So, here is the bookmarklet: JS library detector Just drag it to your bookmarks toolbar and its ready. And here is the human-readable code: if(&#39;Prototype&#39; in window) { var ret = &#39;Prototype &#39; + Prototype.Version; if(&#39;Scriptaculous&#39; in window) ret += &#39; with script.aculo.us &#39; + Scriptaculous.Version; alert(ret); } if(&#39;jQuery&#39; in window) alert(&#39;jQuery &#39; + jQuery.fn.jquery); if(&#39;MooTools&#39; in window) alert(&#39;MooTools &#39; + MooTools.version); if(&#39;YAHOO&#39; in window) alert(&#39;YUI &#39; + YAHOO.VERSION); if(&#39;dojo&#39; in window) alert(&#39;Dojo &#39; + dojo.version); if(&#39;Ext&#39; in window) alert(&#39;ExtJS &#39; + Ext.version); if(&#39;DOMAssistant&#39; in window) alert(&#39;DOMAssistant&#39;); Am I nuts? Certainly. Has it been useful to me? Absolutely.</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Check whether a CSS property is supported</title>
  <link>https://lea.verou.me/2009/02/check-if-a-css-property-is-supported/</link>
  <pubDate>Sun, 17 May 2026 00:07:15 +0200</pubDate>
  <description>Sometimes when using JavaScript, you need to determine whether a certain CSS property is supported by the current browser or not. For instance when setting opacity for an element, you need to find out whether the property that the browser supports is opacity , -moz-opacity ( MozOpacity ), -khtml-opacity ( KhtmlOpacity ) or the IE proprietary filter . Instead of performing a forwards in compatible browser detect, you can easily check which property is supported with a simple conditional. The only thing you’ll need is a DOM element that exists for sure. A DOM element that exists in every page and is also easily accessible via JS (no need for getElementsByTagName ), is the body element, but you could use the or even a tag (since there is a script running in the page, a tag surely exists). In this article we’ll use document.body, but it’s advised that you use the head or script elements, since document.body may not exist at the time your script is run. So, now that we have an element to test at, the test required is: if(&#39;opacity&#39; in document.body.style) { // do stuff } Of course you’d change document.body with a reference to the element you’d like to test at (in case it’s not the body tag) and &#39;opacity&#39; with the name of the actual property you want to test. You can even wrap up a function to use when you want to check about the support of a certain property: function isPropertySupported(property) { return property in document.body.style; } The only thing you should pay attention to, is using the JavaScript version of the CSS property (for example backgroundColor instead of background-color ) Wasn’t it easy?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Check whether a CSS property is supported</title>
  <link>https://lea.verou.me/2009/02/check-if-a-css-property-is-supported/</link>
  <pubDate>Sun, 17 May 2026 00:07:15 +0200</pubDate>
  <description>Sometimes when using JavaScript, you need to determine whether a certain CSS property is supported by the current browser or not. For instance when setting opacity for an element, you need to find out whether the property that the browser supports is opacity , -moz-opacity ( MozOpacity ), -khtml-opacity ( KhtmlOpacity ) or the IE proprietary filter . Instead of performing a forwards in compatible browser detect, you can easily check which property is supported with a simple conditional. The only thing youll need is a DOM element that exists for sure. A DOM element that exists in every page and is also easily accessible via JS (no need for getElementsByTagName ), is the body element, but you could use the or even a tag (since there is a script running in the page, a tag surely exists). In this article well use document.body, but its advised that you use the head or script elements, since document.body may not exist at the time your script is run. So, now that we have an element to test at, the test required is: if(&#39;opacity&#39; in document.body.style) { // do stuff } Of course youd change document.body with a reference to the element youd like to test at (in case its not the body tag) and &#39;opacity&#39; with the name of the actual property you want to test. You can even wrap up a function to use when you want to check about the support of a certain property: function isPropertySupported(property) { return property in document.body.style; } The only thing you should pay attention to, is using the JavaScript version of the CSS property (for example backgroundColor instead of background-color ) Wasnt it easy?</description>
  <dc:source>Blogs/Lea_Verou</dc:source>
</item>
<item>
  <title>Helicoid-Catenoid</title>
  <link>http://xahlee.info/math/blog.html</link>
  <pubDate>Sun, 17 May 2026 00:06:20 +0200</pubDate>
  <description></description>
  <dc:source>Blogs/Xah_Lee</dc:source>
</item>
<item>
  <title>Helicoid-Catenoid</title>
  <link>http://xahlee.info/math/blog.html</link>
  <pubDate>Sun, 17 May 2026 00:06:20 +0200</pubDate>
  <description></description>
  <dc:source>Blogs/Xah_Lee</dc:source>
</item>
<item>
  <title>Links to archives</title>
  <link>https://worrydream.com/Links2025/</link>
  <pubDate>Sun, 17 May 2026 00:05:24 +0200</pubDate>
  <description>Added some links to archives.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Computational Public Space</title>
  <link>https://dynamicland.org/2024/Computational_Public_Space/</link>
  <pubDate>Sun, 17 May 2026 00:05:24 +0200</pubDate>
  <description>A talk about a values-driven approach to integrating computation into cities.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Booklets</title>
  <link>https://worrydream.com/booklets/</link>
  <pubDate>Sun, 17 May 2026 00:05:24 +0200</pubDate>
  <description>Some booklets.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Links to archives</title>
  <link>https://worrydream.com/Links2025/</link>
  <pubDate>Sun, 17 May 2026 00:05:24 +0200</pubDate>
  <description>Added some links to archives.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Computational Public Space</title>
  <link>https://dynamicland.org/2024/Computational_Public_Space/</link>
  <pubDate>Sun, 17 May 2026 00:05:24 +0200</pubDate>
  <description>A talk about a values-driven approach to integrating computation into cities.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Booklets</title>
  <link>https://worrydream.com/booklets/</link>
  <pubDate>Sun, 17 May 2026 00:05:24 +0200</pubDate>
  <description>Some booklets.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Tweet refs</title>
  <link>https://worrydream.com/Tweets/</link>
  <pubDate>Sun, 17 May 2026 00:05:24 +0200</pubDate>
  <description>Added refs for tweets.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Website updated</title>
  <link>https://worrydream.com</link>
  <pubDate>Sun, 17 May 2026 00:05:23 +0200</pubDate>
  <description>Work from the last decade is now on the website.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Dynamicland website</title>
  <link>https://dynamicland.org</link>
  <pubDate>Sun, 17 May 2026 00:05:23 +0200</pubDate>
  <description>Dynamicland&#39;s new website documents ten years of progress toward a humane dynamic medium.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>New website</title>
  <link>https://worrydream.com/</link>
  <pubDate>Sun, 17 May 2026 00:05:23 +0200</pubDate>
  <description>updated my personal website</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Hypercard in the World</title>
  <link>https://www.youtube.com/watch?v=uI7J3II59lc</link>
  <pubDate>Sun, 17 May 2026 00:05:23 +0200</pubDate>
  <description>A live-programming environment for the real world.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Website updated</title>
  <link>https://worrydream.com</link>
  <pubDate>Sun, 17 May 2026 00:05:23 +0200</pubDate>
  <description>Work from the last decade is now on the website.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Dynamicland website</title>
  <link>https://dynamicland.org</link>
  <pubDate>Sun, 17 May 2026 00:05:23 +0200</pubDate>
  <description>Dynamicland&#39;s new website documents ten years of progress toward a humane dynamic medium.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>New website</title>
  <link>https://worrydream.com/</link>
  <pubDate>Sun, 17 May 2026 00:05:23 +0200</pubDate>
  <description>updated my personal website</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Hypercard in the World</title>
  <link>https://www.youtube.com/watch?v=uI7J3II59lc</link>
  <pubDate>Sun, 17 May 2026 00:05:23 +0200</pubDate>
  <description>A live-programming environment for the real world.</description>
  <dc:source>Blogs/Bret_Victor</dc:source>
</item>
<item>
  <title>Fragments: March 19</title>
  <link>https://martinfowler.com/fragments/2026-03-19.html</link>
  <pubDate>Sun, 17 May 2026 00:05:18 +0200</pubDate>
  <description>David Poll points out the flawed premise of the argument that code review is a bottleneck To be fair, finding defects has always been listed as a goal of code review – Wikipedia will tell you as much. And sure, reviewers do catch bugs. But I think that framing dramatically overstates the bug-catching role and understates everything else code review does. If your review process is primarily a bug-finding mechanism, you’re leaving most of the value on the table. Code review answers: “Should this be part of my product?” That’s close to how I think about it. I think of code review as primarily about keeping the code base healthy. And although many people think of code review as pre-integration review done on pull requests, I look at code review as a broader activity both done earlier (Pair Programming) and later (Refinement Code Review) . At Firebase, I spent 5.5 years running an API council… The most valuable feedback from that council was never “you have a bug in this spec.” It was “this API implies a mental model that contradicts what you shipped last quarter” or “this deprecation strategy will cost more trust than the improvement is worth” or simply “a developer encountering this for the first time won’t understand what it does.” Those are judgment calls about whether something should be part of the product – the same fundamental question that code review answers at a different altitude. No amount of production observability surfaces them, because the system can work perfectly and still be the wrong thing to have built. His overall point is that code review is all about applying judgment, steering the code in a good direction. AI raises the level of that judgment, focusing review on more important things. I agree that we shouldn’t be thinking of review as a bug-catching mechanism, and that it’s about steering the code base. In addition I’d also add that it’s about communication between people, enabling multiple perspectives on the development of the product. This is true both for code review, and for pair programming. ❄ ❄ ❄ ❄ ❄ Charity Majors is unhappy with me and rest of the folks that attended the Thoughtworks Future of Software Development Retreat. But the longer I sit with this recap, the more troubled I am by what it doesn’t say. I worry that the most respected minds in software are unintentionally replicating a serious blind spot that has haunted software engineering for decades: relegating production to the realm of bugs and incidents. There are lots of things we didn’t discuss in that day-and-a-half, and it’s understandable that a topic that matters so deeply to her is visible by its absence. I’m certainly not speaking for anyone else who was there, but I’ll take the opportunity to share some of my thoughts on this. I consider observability to be a key tool in working with our AI future. As she points out, observability isn’t really about finding bugs - although I’ve long been a supporter of the notion of QA in Production . Observability is about revealing what the system actually does, when in the hands of its actual users. Test cases help you deal with the known paths, but reality has a habit of taking you into the unknowns, not just the unknowns of the software’s behavior in unforeseen places, but also the unknowns of how the software affects the broader human and organizational systems it’s embedded into. By watching how software is used, we can learn about what users really want to achieve, these observed requirements are often things that never popped up in interviews and focus groups. If these unknown territories are true in systems written line-by-line in deterministic code, it’s even more true when code is written in a world of supervisory engineering where humans are no longer to look over every semi-colon. Certainly harness engineering and humans in the loop help, and I’m as much a fan as ever about the importance of tests as a way to both explain and evaluate the code. But these unknowns will inevitably raise the importance of observability and its role to understand what the system thinks it does. I think it’s likely we’ll see a future where much of a developer’s effort is figuring what a system is doing and why it’s behaving that way, where observability tools are the IDE. In this I ponder the lesson of AI playing Go. AlphaGo defeated the best humans a decade ago, and since then humans study AI to become better players and maybe discover some broader principles. I’m intrigued by how humans can learn from AI systems to be improve in other fields, where success is less deterministically defined. ❄ ❄ ❄ ❄ ❄ Tim Requarth questions the portrayal of AI as an amplifier for human cognition. He considers the different way we navigate with GPS compared to maps. If you unfold a paper map, you study the streets, trace a route, convert the bird’s-eye abstraction into the first-person POV of actually walking—and by the time you arrived, you’d have a nascent mental model of how the city fits together. Or you could fire up Google Maps: A blue dot, an optimal line from A to B, a reassuring robotic voice telling you when to turn. You follow, you arrive, you have no idea, really, where you are. A paper map demands something from you, and that demand leaves you with knowledge. GPS requires nothing, and leaves you with nothing. A paper map and GPS are tools with the same purpose, but opposite cognitive consequences. He introduces some attractive metaphors here. Steve Jobs called computers “bicycles for the mind”, Satya Nadella said with the launch of ChatGPT that “we went from the bicycle to the steam engine”. Like another 19th-century invention, the steam locomotive, the bicycle was a technological revolution. But a train traveler sat back and enjoyed the ride, while a cyclist still had to put in effort. With a bicycle, “you are traveling,” wrote a cycling enthusiast in 1878, “not being traveled.” In both examples, there’s a difference between tools that extend capability and tools that replace it. The question is what we lose when we are passive in the journey? He argues that Silicon Valley executives are too focused on the goal, and ignoring the cognitive atrophy that happens to the humans being traveled. Much of this depends, I think, on whether we care about what we are losing. I struggle with mental arithmetic, so I value calculators, whether on my phone or M-x calc . I don’t think I lose anything when I let the machine handle the toil of calculation. I share missing the sense of place when using a GPS over a map, but am happy that I can now drive though Lynn without getting lost. And when it comes to writing, I have no desire to let an LLM write this page.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Context Anchoring</title>
  <link>https://martinfowler.com/articles/reduce-friction-ai/context-anchoring.html</link>
  <pubDate>Sun, 17 May 2026 00:05:18 +0200</pubDate>
  <description>Conversations with AI are ephemeral, decisions made early lose attention as the conversation continues, and disappear entirely with a new session. Rahul Garg explains how Context Anchoring externalizes the decision context into a living document. more…</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Bliki: Architecture Decision Record</title>
  <link>https://martinfowler.com/bliki/ArchitectureDecisionRecord.html</link>
  <pubDate>Sun, 17 May 2026 00:05:18 +0200</pubDate>
  <description>An Architecture Decision Record (ADR) is a short document that captures and explains a single decision relevant to a product or ecosystem. Documents should be short, just a couple of pages, and contain the decision, the context for making it, and significant ramifications. They should not be modified if the decision is changed, but linked to a superseding decision. As with most written documents, writing ADRs serves two purposes. Firstly they act as a record of decisions, allowing people months or years later to understand why the system is constructed in the way that it is. But perhaps even more valuable, the act of writing them helps to clarify thinking, particularly with groups of people. Writing a document of consequence often surfaces different points of view - forcing those differences to be discussed, and hopefully resolved. A general rule is to follow an “inverted pyramid” style of writing, commonly associated with news stories. The key is to put the most important material at the start, and push details to later in the record. The common advice is to keep decision records in the source repository of the code base to which they apply. A common choice for their location is doc/adr . This way they are easily available to those working on the code base. For similar reasons they should be written in a lightweight markup language, such as markdown, so they can be easily read and diffed just like any code. We can use a build task to publish them to a product team&#39;s website. Storing them in a product repository won&#39;t work for ADRs that cover a broader ecosystem than a single code base. Some folks also feel that keeping ADRs in git makes it too hard for non-developers to work with them. Each record should be its own file, and should be numbered in a monotonic sequence as part of their file name, with a name that captures the decision, so that they are easy to read in a directory listing. (for example: “ 0001-HTMX-for-active-web-pages “). Each ADR has a status. “proposed” while it is under discussion, “accepted” once the team accepts it and it is active, “superseded” once it is significantly modified or replaced - with a link to the superseding ADR. Once an ADR is accepted, it should never be reopened or changed - instead it should be superseded. That way we have a clear log of decisions and how long they governed the work. ADRs contain not just the decision, but also a brief rationale for the decision. This should summarize the problem that led to this decision being needed and the trade-offs that were taken into account. A good way to think of them follows the notion of “forces” when writing a pattern. As part of this it&#39;s valuable to explicitly list all the serious alternatives that were considered, together with their pros and cons. Any decision has consequences. Sometimes these are clearly implied from the rationale, but sometimes it&#39;s worth clearly stating them in a explicit section. Decisions are usually made under some degree of uncertainty, so it&#39;s handy to record the confidence level of the decision. This is a good place to mention any changes in the product context that should trigger the team to reevaluate the decision. ADRs play a central role in the Advice Process , where they are not only used to document decisions, but the act of writing them is used to elicit expertise and alignment. In this case they should also include advice gathered in forming the ADR, although in order to keep things brief, it may be better to summarize the advice in the ADR and keep a full record of advice separately. The most important thing to bear in mind here is brevity. Keep the ADR short and to the point - typically a single page. If there&#39;s supporting material, link to it. While ADRs are a form for recording decisions in software architecture, the broader concept of writing short decision records is worth considering in other contexts. This kind of decision log creates a valuable historic record that can do much to explain why things are the way they turned out. Further Reading Michael Nygard coined the term “Architecture Decision Record” with an ADR-formatted article in 2011. While he did not originate the idea of a decision log he did make case for a lightweight document, with a focus on the decision itself. In this he was particularly inspired by Phillipe Kruchten talking about decision registers / decision logs, and by the writing style of software patterns . His article is better than pretty much everything else written on the topic, my only desire to write this one was to point to some developments since. On this site, there are brief examples of ADR formats in articles by Harmel-Law and Rowse and Shepherd . adr-tools is a simple command line tool to manage ADRs. It includes a set of ADRs for itself that are a good example of the form. Acknowledgements Andrew Harmel-Law, Brandon Cook, David Lucas, Francisco Dias, Giuseppe Matheus Pereira, John King, Kief Morris, Michael Joyce, Neil Price, Shane Gibson, Steven Peh, and Vijay Raghavan Aravamudhan discussed drafts of this post on our internal chat. Michael Nygard gave some background on the origins of his writing.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: March 19</title>
  <link>https://martinfowler.com/fragments/2026-03-19.html</link>
  <pubDate>Sun, 17 May 2026 00:05:18 +0200</pubDate>
  <description>David Poll points out the flawed premise of the argument that code review is a bottleneck To be fair, finding defects has always been listed as a goal of code review Wikipedia will tell you as much. And sure, reviewers do catch bugs. But I think that framing dramatically overstates the bug-catching role and understates everything else code review does. If your review process is primarily a bug-finding mechanism, youre leaving most of the value on the table. Code review answers: Should this be part of my product? Thats close to how I think about it. I think of code review as primarily about keeping the code base healthy. And although many people think of code review as pre-integration review done on pull requests, I look at code review as a broader activity both done earlier (Pair Programming) and later (Refinement Code Review) . At Firebase, I spent 5.5 years running an API council The most valuable feedback from that council was never you have a bug in this spec. It was this API implies a mental model that contradicts what you shipped last quarter or this deprecation strategy will cost more trust than the improvement is worth or simply a developer encountering this for the first time wont understand what it does. Those are judgment calls about whether something should be part of the product the same fundamental question that code review answers at a different altitude. No amount of production observability surfaces them, because the system can work perfectly and still be the wrong thing to have built. His overall point is that code review is all about applying judgment, steering the code in a good direction. AI raises the level of that judgment, focusing review on more important things. I agree that we shouldnt be thinking of review as a bug-catching mechanism, and that its about steering the code base. In addition Id also add that its about communication between people, enabling multiple perspectives on the development of the product. This is true both for code review, and for pair programming. Charity Majors is unhappy with me and rest of the folks that attended the Thoughtworks Future of Software Development Retreat. But the longer I sit with this recap, the more troubled I am by what it doesnt say. I worry that the most respected minds in software are unintentionally replicating a serious blind spot that has haunted software engineering for decades: relegating production to the realm of bugs and incidents. There are lots of things we didnt discuss in that day-and-a-half, and its understandable that a topic that matters so deeply to her is visible by its absence. Im certainly not speaking for anyone else who was there, but Ill take the opportunity to share some of my thoughts on this. I consider observability to be a key tool in working with our AI future. As she points out, observability isnt really about finding bugs - although Ive long been a supporter of the notion of QA in Production . Observability is about revealing what the system actually does, when in the hands of its actual users. Test cases help you deal with the known paths, but reality has a habit of taking you into the unknowns, not just the unknowns of the softwares behavior in unforeseen places, but also the unknowns of how the software affects the broader human and organizational systems its embedded into. By watching how software is used, we can learn about what users really want to achieve, these observed requirements are often things that never popped up in interviews and focus groups. If these unknown territories are true in systems written line-by-line in deterministic code, its even more true when code is written in a world of supervisory engineering where humans are no longer to look over every semi-colon. Certainly harness engineering and humans in the loop help, and Im as much a fan as ever about the importance of tests as a way to both explain and evaluate the code. But these unknowns will inevitably raise the importance of observability and its role to understand what the system thinks it does. I think its likely well see a future where much of a developers effort is figuring what a system is doing and why its behaving that way, where observability tools are the IDE. In this I ponder the lesson of AI playing Go. AlphaGo defeated the best humans a decade ago, and since then humans study AI to become better players and maybe discover some broader principles. Im intrigued by how humans can learn from AI systems to be improve in other fields, where success is less deterministically defined. Tim Requarth questions the portrayal of AI as an amplifier for human cognition. He considers the different way we navigate with GPS compared to maps. If you unfold a paper map, you study the streets, trace a route, convert the birds-eye abstraction into the first-person POV of actually walkingand by the time you arrived, youd have a nascent mental model of how the city fits together. Or you could fire up Google Maps: A blue dot, an optimal line from A to B, a reassuring robotic voice telling you when to turn. You follow, you arrive, you have no idea, really, where you are. A paper map demands something from you, and that demand leaves you with knowledge. GPS requires nothing, and leaves you with nothing. A paper map and GPS are tools with the same purpose, but opposite cognitive consequences. He introduces some attractive metaphors here. Steve Jobs called computers bicycles for the mind, Satya Nadella said with the launch of ChatGPT that we went from the bicycle to the steam engine. Like another 19th-century invention, the steam locomotive, the bicycle was a technological revolution. But a train traveler sat back and enjoyed the ride, while a cyclist still had to put in effort. With a bicycle, you are traveling, wrote a cycling enthusiast in 1878, not being traveled. In both examples, theres a difference between tools that extend capability and tools that replace it. The question is what we lose when we are passive in the journey? He argues that Silicon Valley executives are too focused on the goal, and ignoring the cognitive atrophy that happens to the humans being traveled. Much of this depends, I think, on whether we care about what we are losing. I struggle with mental arithmetic, so I value calculators, whether on my phone or M-x calc . I dont think I lose anything when I let the machine handle the toil of calculation. I share missing the sense of place when using a GPS over a map, but am happy that I can now drive though Lynn without getting lost. And when it comes to writing, I have no desire to let an LLM write this page.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Context Anchoring</title>
  <link>https://martinfowler.com/articles/reduce-friction-ai/context-anchoring.html</link>
  <pubDate>Sun, 17 May 2026 00:05:18 +0200</pubDate>
  <description>Conversations with AI are ephemeral, decisions made early lose attention as the conversation continues, and disappear entirely with a new session. Rahul Garg explains how Context Anchoring externalizes the decision context into a living document. more</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Bliki: Architecture Decision Record</title>
  <link>https://martinfowler.com/bliki/ArchitectureDecisionRecord.html</link>
  <pubDate>Sun, 17 May 2026 00:05:18 +0200</pubDate>
  <description>An Architecture Decision Record (ADR) is a short document that captures and explains a single decision relevant to a product or ecosystem. Documents should be short, just a couple of pages, and contain the decision, the context for making it, and significant ramifications. They should not be modified if the decision is changed, but linked to a superseding decision. As with most written documents, writing ADRs serves two purposes. Firstly they act as a record of decisions, allowing people months or years later to understand why the system is constructed in the way that it is. But perhaps even more valuable, the act of writing them helps to clarify thinking, particularly with groups of people. Writing a document of consequence often surfaces different points of view - forcing those differences to be discussed, and hopefully resolved. A general rule is to follow an inverted pyramid style of writing, commonly associated with news stories. The key is to put the most important material at the start, and push details to later in the record. The common advice is to keep decision records in the source repository of the code base to which they apply. A common choice for their location is doc/adr . This way they are easily available to those working on the code base. For similar reasons they should be written in a lightweight markup language, such as markdown, so they can be easily read and diffed just like any code. We can use a build task to publish them to a product team&#39;s website. Storing them in a product repository won&#39;t work for ADRs that cover a broader ecosystem than a single code base. Some folks also feel that keeping ADRs in git makes it too hard for non-developers to work with them. Each record should be its own file, and should be numbered in a monotonic sequence as part of their file name, with a name that captures the decision, so that they are easy to read in a directory listing. (for example: 0001-HTMX-for-active-web-pages ). Each ADR has a status. proposed while it is under discussion, accepted once the team accepts it and it is active, superseded once it is significantly modified or replaced - with a link to the superseding ADR. Once an ADR is accepted, it should never be reopened or changed - instead it should be superseded. That way we have a clear log of decisions and how long they governed the work. ADRs contain not just the decision, but also a brief rationale for the decision. This should summarize the problem that led to this decision being needed and the trade-offs that were taken into account. A good way to think of them follows the notion of forces when writing a pattern. As part of this it&#39;s valuable to explicitly list all the serious alternatives that were considered, together with their pros and cons. Any decision has consequences. Sometimes these are clearly implied from the rationale, but sometimes it&#39;s worth clearly stating them in a explicit section. Decisions are usually made under some degree of uncertainty, so it&#39;s handy to record the confidence level of the decision. This is a good place to mention any changes in the product context that should trigger the team to reevaluate the decision. ADRs play a central role in the Advice Process , where they are not only used to document decisions, but the act of writing them is used to elicit expertise and alignment. In this case they should also include advice gathered in forming the ADR, although in order to keep things brief, it may be better to summarize the advice in the ADR and keep a full record of advice separately. The most important thing to bear in mind here is brevity. Keep the ADR short and to the point - typically a single page. If there&#39;s supporting material, link to it. While ADRs are a form for recording decisions in software architecture, the broader concept of writing short decision records is worth considering in other contexts. This kind of decision log creates a valuable historic record that can do much to explain why things are the way they turned out. Further Reading Michael Nygard coined the term Architecture Decision Record with an ADR-formatted article in 2011. While he did not originate the idea of a decision log he did make case for a lightweight document, with a focus on the decision itself. In this he was particularly inspired by Phillipe Kruchten talking about decision registers / decision logs, and by the writing style of software patterns . His article is better than pretty much everything else written on the topic, my only desire to write this one was to point to some developments since. On this site, there are brief examples of ADR formats in articles by Harmel-Law and Rowse and Shepherd . adr-tools is a simple command line tool to manage ADRs. It includes a set of ADRs for itself that are a good example of the form. Acknowledgements Andrew Harmel-Law, Brandon Cook, David Lucas, Francisco Dias, Giuseppe Matheus Pereira, John King, Kief Morris, Michael Joyce, Neil Price, Shane Gibson, Steven Peh, and Vijay Raghavan Aravamudhan discussed drafts of this post on our internal chat. Michael Nygard gave some background on the origins of his writing.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Encoding Team Standards</title>
  <link>https://martinfowler.com/articles/reduce-friction-ai/encoding-team-standards.html</link>
  <pubDate>Sun, 17 May 2026 00:05:17 +0200</pubDate>
  <description>AI coding assistants respond to whoever is prompting, and the quality of what they produce depends on how well the prompter articulates team standards. Rahul Garg proposes treating the instructions that govern AI interactions (generation, refactoring, security, review) as infrastructure: versioned, reviewed, and shared artifacts that encode tacit team knowledge into executable instructions, making quality consistent regardless of who is at the keyboard. more…</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Harness engineering for coding agent users</title>
  <link>https://martinfowler.com/articles/harness-engineering.html</link>
  <pubDate>Sun, 17 May 2026 00:05:17 +0200</pubDate>
  <description>Last month Birgitta Böckeler wrote some initial thoughts about the recently developed notion of Harness Engineering. She&#39;s been researching and thinking more about this in the weeks since and has now written a thoughtful mental model for understanding harness engineering that we think will help people to drive coding agents more effectively. more…</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: March 26</title>
  <link>https://martinfowler.com/fragments/2026-03-26.html</link>
  <pubDate>Sun, 17 May 2026 00:05:17 +0200</pubDate>
  <description>Anthropic carried a study, done by getting its model to interview some 80,000 users to understand their opinions about AI, what they hope from it, and what they fear. Two things stood out to me. It’s easy to assume there are AI optimists and AI pessimists, divided into separate camps. But what we actually found were people organized around what they value—financial security, learning, human connection— watching advancing AI capabilities while managing both hope and fear at once. That makes sense, if asked whether I’m a an AI booster or an AI doomer, I answer “yes”. I am both fascinated by its impact on my profession, expectant of the benefits it will bring to our world, and worried by the harms that will come from it. Powerful technologies rarely yield simple consequences. The other thing that struck me was that, despite most people mixing the two, there was an overall variance between optimism and pessimism with AI by geography. In general, the less developed the country, the more optimism about AI. ❄ ❄ ❄ ❄ ❄ Julias Shaw describes how to fix a gap in many people’s use of specs to drive LLMs: Here’s what I keep seeing: the specification-driven development (SDD) conversation has exploded. The internet is overflowing with people saying you should write a spec before prompting. Describe the behavior you want. Define the constraints. Give the agent guardrails. Good advice. I often follow it myself. But almost nobody takes the next step. Encoding those specifications into automated tests that actually enforce the contract. And the strange part is, most developers outside the extreme programming crowd don’t realize they need to. They genuinely believe the spec document is the safety net. It isn’t. The spec document is the blueprint. The safety net is the test suite that catches the moment your code drifts away from it. As well as explaining why it’s important to have such a test suite, he provides an astute five-step checklist to turn spec documents into executable tests. ❄ ❄ ❄ ❄ ❄ Lawfare has a long article on potential problems countering covert action by Iran . It’s a long article, and I confess I only skip-read it. It begins by outlining a bunch of plots hatched in the last few years. Then it says: If these examples seem repetitive, it’s because they are. Iran has proved itself relentless in its efforts to carry out attacks on U.S. soil—and the U.S., for its part, has demonstrated that it is capable of countering those efforts. The above examples show how robustly the U.S. national security apparatus was able to respond, largely through the FBI and the Justice Department…. That is, potentially, until now. The current administration has decimated the national security elements of both agencies through firings and forced resignations. People with decades of experience in building interagency and critical source relationships around the world, handling high-pressure, complicated investigations straddling classified and unclassified spaces, and acting in time to prevent violence and preserve evidence have been pushed out the door. Those who remain not only have to stretch to make up for the personnel deficit but also are being pulled away by White House priorities not tied to the increasing threat of an Iranian response. The article goes into detail about these cuts, and the threats that may exploit the resulting gaps. It’s the nature of national security people to highlight potential threats and call for more resources and power. But it’s also the nature of enemies to find weak spots and look to cause havoc. I wonder what we’ll think should we read this article again in a few years time</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Encoding Team Standards</title>
  <link>https://martinfowler.com/articles/reduce-friction-ai/encoding-team-standards.html</link>
  <pubDate>Sun, 17 May 2026 00:05:17 +0200</pubDate>
  <description>AI coding assistants respond to whoever is prompting, and the quality of what they produce depends on how well the prompter articulates team standards. Rahul Garg proposes treating the instructions that govern AI interactions (generation, refactoring, security, review) as infrastructure: versioned, reviewed, and shared artifacts that encode tacit team knowledge into executable instructions, making quality consistent regardless of who is at the keyboard. more</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Harness engineering for coding agent users</title>
  <link>https://martinfowler.com/articles/harness-engineering.html</link>
  <pubDate>Sun, 17 May 2026 00:05:17 +0200</pubDate>
  <description>Last month Birgitta Böckeler wrote some initial thoughts about the recently developed notion of Harness Engineering. She&#39;s been researching and thinking more about this in the weeks since and has now written a thoughtful mental model for understanding harness engineering that we think will help people to drive coding agents more effectively. more</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: March 26</title>
  <link>https://martinfowler.com/fragments/2026-03-26.html</link>
  <pubDate>Sun, 17 May 2026 00:05:17 +0200</pubDate>
  <description>Anthropic carried a study, done by getting its model to interview some 80,000 users to understand their opinions about AI, what they hope from it, and what they fear. Two things stood out to me. Its easy to assume there are AI optimists and AI pessimists, divided into separate camps. But what we actually found were people organized around what they valuefinancial security, learning, human connection watching advancing AI capabilities while managing both hope and fear at once. That makes sense, if asked whether Im a an AI booster or an AI doomer, I answer yes. I am both fascinated by its impact on my profession, expectant of the benefits it will bring to our world, and worried by the harms that will come from it. Powerful technologies rarely yield simple consequences. The other thing that struck me was that, despite most people mixing the two, there was an overall variance between optimism and pessimism with AI by geography. In general, the less developed the country, the more optimism about AI. Julias Shaw describes how to fix a gap in many peoples use of specs to drive LLMs: Heres what I keep seeing: the specification-driven development (SDD) conversation has exploded. The internet is overflowing with people saying you should write a spec before prompting. Describe the behavior you want. Define the constraints. Give the agent guardrails. Good advice. I often follow it myself. But almost nobody takes the next step. Encoding those specifications into automated tests that actually enforce the contract. And the strange part is, most developers outside the extreme programming crowd dont realize they need to. They genuinely believe the spec document is the safety net. It isnt. The spec document is the blueprint. The safety net is the test suite that catches the moment your code drifts away from it. As well as explaining why its important to have such a test suite, he provides an astute five-step checklist to turn spec documents into executable tests. Lawfare has a long article on potential problems countering covert action by Iran . Its a long article, and I confess I only skip-read it. It begins by outlining a bunch of plots hatched in the last few years. Then it says: If these examples seem repetitive, its because they are. Iran has proved itself relentless in its efforts to carry out attacks on U.S. soiland the U.S., for its part, has demonstrated that it is capable of countering those efforts. The above examples show how robustly the U.S. national security apparatus was able to respond, largely through the FBI and the Justice Department. That is, potentially, until now. The current administration has decimated the national security elements of both agencies through firings and forced resignations. People with decades of experience in building interagency and critical source relationships around the world, handling high-pressure, complicated investigations straddling classified and unclassified spaces, and acting in time to prevent violence and preserve evidence have been pushed out the door. Those who remain not only have to stretch to make up for the personnel deficit but also are being pulled away by White House priorities not tied to the increasing threat of an Iranian response. The article goes into detail about these cuts, and the threats that may exploit the resulting gaps. Its the nature of national security people to highlight potential threats and call for more resources and power. But its also the nature of enemies to find weak spots and look to cause havoc. I wonder what well think should we read this article again in a few years time</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 2</title>
  <link>https://martinfowler.com/fragments/2026-04-02.html</link>
  <pubDate>Sun, 17 May 2026 00:05:16 +0200</pubDate>
  <description>As we see LLMs churn out scads of code, folks have increasingly turned to Cognitive Debt as a metaphor for capturing how a team can lose understanding of what a system does. Margaret-Anne Storey thinks a good way of thinking about these problems is to consider three layers of system health : Technical debt lives in code. It accumulates when implementation decisions compromise future changeability. It limits how systems can change. Cognitive debt lives in people. It accumulates when shared understanding of the system erodes faster than it is replenished. It limits how teams can reason about change. Intent debt lives in artifacts. It accumulates when the goals and constraints that should guide the system are poorly captured or maintained. It limits whether the system continues to reflect what we meant to build and it limits how humans and AI agents can continue to evolve the system effectively. While I’m getting a bit bemused by debt metaphor proliferation, this way of thinking does make a fair bit of sense. The article includes useful sections to diagnose and mitigate each kind of debt. The three interact with each other, and the article outlines some general activities teams should do to keep it all under control ❄ ❄ In the article she references a recent paper by Shaw and Nave at the Wharton School that adds LLMs to Kahneman’s two-system model of thinking . Kahneman’s book, “Thinking Fast and Slow”, is one of my favorite books. Its central idea is that humans have two systems of cognition. System 1 (intuition) makes rapid decisions, often barely-consciously. System 2 (deliberation) is when we apply deliberate thinking to a problem. He observed that to save energy we default to intuition, and that sometimes gets us into trouble when we overlook things that we would have spotted had we applied deliberation to the problem. Shaw and Nave consider AI as System 3 A consequence of System 3 is the introduction of cognitive surrender, characterized by uncritical reliance on externally generated artificial reasoning, bypassing System 2. Crucially, we distinguish cognitive surrender, marked by passive trust and uncritical evaluation of external information, from cognitive offloading, which involves strategic delegation of cognition during deliberation. It’s a long paper, that goes into detail on this “Tri-System theory of cognition” and reports on several experiments they’ve done to test how well this theory can predict behavior (at least within a lab). ❄ ❄ ❄ ❄ ❄ I’ve seen a few illustrations recently that use the symbols “ ” as part of an icon to illustrate code. That strikes me as rather odd, I can’t think of any programming language that uses “ ” to surround program elements. Why that and not, say, “{ }”? Obviously the reason is that they are thinking of HTML (or maybe XML), which is even more obvious when they use “ ” in their icons. But programmers don’t program in HTML. ❄ ❄ ❄ ❄ ❄ Ajey Gore thinks about if coding agents make coding free, what becomes the expensive thing ? His answer is verification. What does “correct” mean for an ETA algorithm in Jakarta traffic versus Ho Chi Minh City? What does a “successful” driver allocation look like when you’re balancing earnings fairness, customer wait time, and fleet utilisation simultaneously? When hundreds of engineers are shipping into ~900 microservices around the clock, “correct” isn’t one definition — it’s thousands of definitions, all shifting, all context-dependent. These aren’t edge cases. They’re the entire job. And they’re precisely the kind of judgment that agents cannot perform for you. Increasingly I’m seeing a view that agents do really well when they have good, preferably automated, verification for their work. This encourages such things as Test Driven Development . That’s still a lot of verification to do, which suggests we should see more effort to find ways to make it easier for humans to comprehend larger ranges of tests. While I agree with most of what Ajey writes here, I do have a quibble with his view of legacy migration. He thinks it’s a delusion that “agentic coding will finally crack legacy modernisation”. I agree with him that agentic coding is overrated in a legacy context, but I have seen compelling evidence that LLMs help a great deal in understanding what legacy code is doing . The big consequence of Ajey’s assessment is that we’ll need to reorganize around verification rather than writing code: If agents handle execution, the human job becomes designing verification systems, defining quality, and handling the ambiguous cases agents can’t resolve. Your org chart should reflect this. Practically, this means your Monday morning standup changes. Instead of “what did we ship?” the question becomes “what did we validate?” Instead of tracking output, you’re tracking whether the output was right. The team that used to have ten engineers building features now has three engineers and seven people defining acceptance criteria, designing test harnesses, and monitoring outcomes. That’s the reorganisation. It’s uncomfortable because it demotes the act of building and promotes the act of judging. Most engineering cultures resist this. The ones that don’t will win. ❄ ❄ ❄ ❄ ❄ One the questions comes up when we think of LLMs-as-programmers is whether there is a future for source code. David Cassel on The New Stack has an article summarizing several views of the future of code . Some folks are experimenting with entirely new languages built with the LLM in mind, others think that existing languages, especially strictly typed languages like TypeScript and Rust will be the best fit for LLMs. It’s an overview article, one that has lots of quotations, but not much analysis in itself - but it’s worth a read as a good overview of the discussion. I’m interested to see how all this will play out. I do think there’s still a role for humans to work with LLMs to build useful abstractions in which to talk about what the code does - essentially the DDD notion of Ubiquitous Language . Last year Unmesh and I talked about growing a language with LLMs. As Unmesh put it Programming isn’t just typing coding syntax that computers can understand and execute; it’s shaping a solution. We slice the problem into focused pieces, bind related data and behaviour together, and—crucially—choose names that expose intent. Good names cut through complexity and turn code into a schematic everyone can follow. The most creative act is this continual weaving of names that reveal the structure of the solution that maps clearly to the problem we are trying to solve.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 9</title>
  <link>https://martinfowler.com/fragments/2026-04-09.html</link>
  <pubDate>Sun, 17 May 2026 00:05:16 +0200</pubDate>
  <description>I mostly link to written material here, but I’ve recently listened to two excellent podcasts that I can recommend. Anyone who regularly reads these fragments knows that I’m a big fan of Simon Willison, his (also very fragmentary) posts have earned a regular spot in my RSS reader. But the problem with fragments, however valuable, is that they don’t provide a cohesive overview of the situation. So his podcast with Lenny Rachitsky is a welcome survey of that state of world as seen through a discerning pair of eyeballs. He paints a good picture of how programming has changed for him since the “November inflection point”, important patterns for this work, and his concern about the security bomb nestled inside the beast. My other great listening was on a regular podcast that I listen to, as Gergely Orosz interviewed Thuan Pham - the former CTO of Uber. As with so many of Gergely’s podcasts, they focused on Thuan Pham’s fascinating career direction, giving listeners an opportunity to learn from a successful professional. There’s also an informative insight into Uber’s use of microservices (they had 5000 of them), and the way high-growth software necessarily gets rewritten a lot (a phenomenon I dubbed Sacrificial Architecture ) ❄ ❄ ❄ ❄ ❄ Axios published their post-mortem on their recent supply chain compromise . It’s quite a story, the attackers spent a couple of weeks developing contact with the lead maintainer, leading to a video call where the meeting software indicated something on the maintainer’s system was out of date. That led to the maintainer installing the update, which in fact was a Remote Access Trojan (RAT). they tailored this process specifically to me by doing the following: they reached out masquerading as the founder of a company they had cloned the companys founders likeness as well as the company itself. they then invited me to a real slack workspace. this workspace was branded to the companies ci and named in a plausible manner. the slack was thought out very well, they had channels where they were sharing linked-in posts, the linked in posts i presume just went to the real companys account but it was super convincing etc. they even had what i presume were fake profiles of the team of the company but also number of other oss maintainers. they scheduled a meeting with me to connect. the meeting was on ms teams. the meeting had what seemed to be a group of people that were involved. the meeting said something on my system was out of date. i installed the missing item as i presumed it was something to do with teams, and this was the RAT. everything was extremely well co-ordinated looked legit and was done in a professional manner. Simon Willison has a summary and further links . ❄ ❄ ❄ ❄ ❄ I recently bumped into Diátaxis , a framework for organizing technical documentation. I only looked at it briefly, but there’s much to like. In particular I appreciated how it classified four forms of documentation: Tutorials: to learn how to use the product How-to guides: for users to follow to achieve particular goals with the product Reference: to describe what the product does Explanations: background and context to educate the user on the product’s rationale The distinction between tutorials and how-to guides is interesting A tutorial serves the needs of the user who is at study. Its obligation is to provide a successful learning experience. A how-to guide serves the needs of the user who is at work. Its obligation is to help the user accomplish a task. I also appreciated its point of pulling explanations out into separate areas. The idea is that other forms should contain only minimal explanations, linking to the explanation material for more depth. That way we keep the flow on the goal and allow the user to seek deeper explanations in their own way. The study/work distinction between explanation and reference mirrors that same distinction between tutorials and how-to guides. ❄ ❄ ❄ ❄ ❄ For eight years, Lalit Maganti wanted a set of tools for working with SQLite. But it would be hard and tedious work, “getting into the weeds of SQLite source code, a fiendishly difficult codebase to understand”. So he didn’t try it. But after the November inflection point , he decided to tackle this need. His account of this exercise is an excellent description of the benefits and perils of developing with AI agents. Through most of January, I iterated, acting as semi-technical manager and delegating almost all the design and all the implementation to Claude. Functionally, I ended up in a reasonable place: a parser in C extracted from SQLite sources using a bunch of Python scripts, a formatter built on top, support for both the SQLite language and the PerfettoSQL extensions, all exposed in a web playground. But when I reviewed the codebase in detail in late January, the downside was obvious: the codebase was complete spaghetti. I didn’t understand large parts of the Python source extraction pipeline, functions were scattered in random files without a clear shape, and a few files had grown to several thousand lines. It was extremely fragile; it solved the immediate problem but it was never going to cope with my larger vision, never mind integrating it into the Perfetto tools. The saving grace was that it had proved the approach was viable and generated more than 500 tests, many of which I felt I could reuse. He threw it all away and worked more closely with the AI on the second attempt, with lots of thinking about the design, reviewing all the code, and refactoring with every step In the rewrite, refactoring became the core of my workflow. After every large batch of generated code, I’d step back and ask “is this ugly?” Sometimes AI could clean it up. Other times there was a large-scale abstraction that AI couldn’t see but I could; I’d give it the direction and let it execute. If you have taste, the cost of a wrong approach drops dramatically because you can restructure quickly. He ended up with a working system, and the AI proved its value in allowing him to tackle something that he’d been leaving on the todo pile for years. But even with the rewrite, the AI had its potholes. His conclusion of the relative value of AI in different scenarios: When I was working on something I already understood deeply, AI was excellent…. When I was working on something I could describe but didn’t yet know, AI was good but required more care…. When I was working on something where I didn’t even know what I wanted, AI was somewhere between unhelpful and harmful… At the heart of this is that AI works at its best when there is an objectively checkable answer. If we want an implementation that can pass some tests, then AI does a good job. But when it came to the public API: I spent several days in early March doing nothing but API refactoring, manually fixing things any experienced engineer would have instinctively avoided but AI made a total mess of. There’s no test or objective metric for “is this API pleasant to use” and “will this API help users solve the problems they have” and that’s exactly why the coding agents did so badly at it. ❄ ❄ ❄ ❄ ❄ I became familiar with Ryan Avent’s writing when he wrote the Free Exchange column for The Economist. His recent post talks about how James Talarico and Zohran Mamdani have made their religion an important part of their electoral appeal, and their faith is centered on caring for others. He explains that a focus on care leads to an important perspective on economic growth. The first thing to understand is that we should not want growth for its own sake. What is good about growth is that it expands our collective capacities: we come to know more and we are able to do more. This, in turn, allows us to alleviate suffering, to discover more things about the universe, and to spend more time being complete people.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Principles of Mechanical Sympathy</title>
  <link>https://martinfowler.com/articles/mechanical-sympathy-principles.html</link>
  <pubDate>Sun, 17 May 2026 00:05:16 +0200</pubDate>
  <description>Modern hardware is remarkably fast, but software often fails to leverage it. Caer Sanders has found it valuable to guide their work with mechanical sympathy - the practice of creating software that is sympathetic to its underlying hardware. They distill this practice into everyday principles: predictable memory access, awareness of cache lines, single-writer, and natural batching. more…</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Feedback Flywheel</title>
  <link>https://martinfowler.com/articles/reduce-friction-ai/feedback-flywheel.html</link>
  <pubDate>Sun, 17 May 2026 00:05:16 +0200</pubDate>
  <description>Rahul Garg finishes his series on reducing the friction in AI-Assisted Development. He proposes a structured feedback practice that harvests learnings from AI sessions and feeds them back into the team&#39;s shared artifacts, turning individual experience into collective improvement. more…</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 2</title>
  <link>https://martinfowler.com/fragments/2026-04-02.html</link>
  <pubDate>Sun, 17 May 2026 00:05:16 +0200</pubDate>
  <description>As we see LLMs churn out scads of code, folks have increasingly turned to Cognitive Debt as a metaphor for capturing how a team can lose understanding of what a system does. Margaret-Anne Storey thinks a good way of thinking about these problems is to consider three layers of system health : Technical debt lives in code. It accumulates when implementation decisions compromise future changeability. It limits how systems can change. Cognitive debt lives in people. It accumulates when shared understanding of the system erodes faster than it is replenished. It limits how teams can reason about change. Intent debt lives in artifacts. It accumulates when the goals and constraints that should guide the system are poorly captured or maintained. It limits whether the system continues to reflect what we meant to build and it limits how humans and AI agents can continue to evolve the system effectively. While Im getting a bit bemused by debt metaphor proliferation, this way of thinking does make a fair bit of sense. The article includes useful sections to diagnose and mitigate each kind of debt. The three interact with each other, and the article outlines some general activities teams should do to keep it all under control In the article she references a recent paper by Shaw and Nave at the Wharton School that adds LLMs to Kahnemans two-system model of thinking . Kahnemans book, Thinking Fast and Slow, is one of my favorite books. Its central idea is that humans have two systems of cognition. System 1 (intuition) makes rapid decisions, often barely-consciously. System 2 (deliberation) is when we apply deliberate thinking to a problem. He observed that to save energy we default to intuition, and that sometimes gets us into trouble when we overlook things that we would have spotted had we applied deliberation to the problem. Shaw and Nave consider AI as System 3 A consequence of System 3 is the introduction of cognitive surrender, characterized by uncritical reliance on externally generated artificial reasoning, bypassing System 2. Crucially, we distinguish cognitive surrender, marked by passive trust and uncritical evaluation of external information, from cognitive offloading, which involves strategic delegation of cognition during deliberation. Its a long paper, that goes into detail on this Tri-System theory of cognition and reports on several experiments theyve done to test how well this theory can predict behavior (at least within a lab). Ive seen a few illustrations recently that use the symbols as part of an icon to illustrate code. That strikes me as rather odd, I cant think of any programming language that uses to surround program elements. Why that and not, say, { }? Obviously the reason is that they are thinking of HTML (or maybe XML), which is even more obvious when they use in their icons. But programmers dont program in HTML. Ajey Gore thinks about if coding agents make coding free, what becomes the expensive thing ? His answer is verification. What does correct mean for an ETA algorithm in Jakarta traffic versus Ho Chi Minh City? What does a successful driver allocation look like when youre balancing earnings fairness, customer wait time, and fleet utilisation simultaneously? When hundreds of engineers are shipping into ~900 microservices around the clock, correct isnt one definition its thousands of definitions, all shifting, all context-dependent. These arent edge cases. Theyre the entire job. And theyre precisely the kind of judgment that agents cannot perform for you. Increasingly Im seeing a view that agents do really well when they have good, preferably automated, verification for their work. This encourages such things as Test Driven Development . Thats still a lot of verification to do, which suggests we should see more effort to find ways to make it easier for humans to comprehend larger ranges of tests. While I agree with most of what Ajey writes here, I do have a quibble with his view of legacy migration. He thinks its a delusion that agentic coding will finally crack legacy modernisation. I agree with him that agentic coding is overrated in a legacy context, but I have seen compelling evidence that LLMs help a great deal in understanding what legacy code is doing . The big consequence of Ajeys assessment is that well need to reorganize around verification rather than writing code: If agents handle execution, the human job becomes designing verification systems, defining quality, and handling the ambiguous cases agents cant resolve. Your org chart should reflect this. Practically, this means your Monday morning standup changes. Instead of what did we ship? the question becomes what did we validate? Instead of tracking output, youre tracking whether the output was right. The team that used to have ten engineers building features now has three engineers and seven people defining acceptance criteria, designing test harnesses, and monitoring outcomes. Thats the reorganisation. Its uncomfortable because it demotes the act of building and promotes the act of judging. Most engineering cultures resist this. The ones that dont will win. One the questions comes up when we think of LLMs-as-programmers is whether there is a future for source code. David Cassel on The New Stack has an article summarizing several views of the future of code . Some folks are experimenting with entirely new languages built with the LLM in mind, others think that existing languages, especially strictly typed languages like TypeScript and Rust will be the best fit for LLMs. Its an overview article, one that has lots of quotations, but not much analysis in itself - but its worth a read as a good overview of the discussion. Im interested to see how all this will play out. I do think theres still a role for humans to work with LLMs to build useful abstractions in which to talk about what the code does - essentially the DDD notion of Ubiquitous Language . Last year Unmesh and I talked about growing a language with LLMs. As Unmesh put it Programming isnt just typing coding syntax that computers can understand and execute; its shaping a solution. We slice the problem into focused pieces, bind related data and behaviour together, andcruciallychoose names that expose intent. Good names cut through complexity and turn code into a schematic everyone can follow. The most creative act is this continual weaving of names that reveal the structure of the solution that maps clearly to the problem we are trying to solve.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 9</title>
  <link>https://martinfowler.com/fragments/2026-04-09.html</link>
  <pubDate>Sun, 17 May 2026 00:05:16 +0200</pubDate>
  <description>I mostly link to written material here, but Ive recently listened to two excellent podcasts that I can recommend. Anyone who regularly reads these fragments knows that Im a big fan of Simon Willison, his (also very fragmentary) posts have earned a regular spot in my RSS reader. But the problem with fragments, however valuable, is that they dont provide a cohesive overview of the situation. So his podcast with Lenny Rachitsky is a welcome survey of that state of world as seen through a discerning pair of eyeballs. He paints a good picture of how programming has changed for him since the November inflection point, important patterns for this work, and his concern about the security bomb nestled inside the beast. My other great listening was on a regular podcast that I listen to, as Gergely Orosz interviewed Thuan Pham - the former CTO of Uber. As with so many of Gergelys podcasts, they focused on Thuan Phams fascinating career direction, giving listeners an opportunity to learn from a successful professional. Theres also an informative insight into Ubers use of microservices (they had 5000 of them), and the way high-growth software necessarily gets rewritten a lot (a phenomenon I dubbed Sacrificial Architecture ) Axios published their post-mortem on their recent supply chain compromise . Its quite a story, the attackers spent a couple of weeks developing contact with the lead maintainer, leading to a video call where the meeting software indicated something on the maintainers system was out of date. That led to the maintainer installing the update, which in fact was a Remote Access Trojan (RAT). they tailored this process specifically to me by doing the following: they reached out masquerading as the founder of a company they had cloned the companys founders likeness as well as the company itself. they then invited me to a real slack workspace. this workspace was branded to the companies ci and named in a plausible manner. the slack was thought out very well, they had channels where they were sharing linked-in posts, the linked in posts i presume just went to the real companys account but it was super convincing etc. they even had what i presume were fake profiles of the team of the company but also number of other oss maintainers. they scheduled a meeting with me to connect. the meeting was on ms teams. the meeting had what seemed to be a group of people that were involved. the meeting said something on my system was out of date. i installed the missing item as i presumed it was something to do with teams, and this was the RAT. everything was extremely well co-ordinated looked legit and was done in a professional manner. Simon Willison has a summary and further links . I recently bumped into Diátaxis , a framework for organizing technical documentation. I only looked at it briefly, but theres much to like. In particular I appreciated how it classified four forms of documentation: Tutorials: to learn how to use the product How-to guides: for users to follow to achieve particular goals with the product Reference: to describe what the product does Explanations: background and context to educate the user on the products rationale The distinction between tutorials and how-to guides is interesting A tutorial serves the needs of the user who is at study. Its obligation is to provide a successful learning experience. A how-to guide serves the needs of the user who is at work. Its obligation is to help the user accomplish a task. I also appreciated its point of pulling explanations out into separate areas. The idea is that other forms should contain only minimal explanations, linking to the explanation material for more depth. That way we keep the flow on the goal and allow the user to seek deeper explanations in their own way. The study/work distinction between explanation and reference mirrors that same distinction between tutorials and how-to guides. For eight years, Lalit Maganti wanted a set of tools for working with SQLite. But it would be hard and tedious work, getting into the weeds of SQLite source code, a fiendishly difficult codebase to understand. So he didnt try it. But after the November inflection point , he decided to tackle this need. His account of this exercise is an excellent description of the benefits and perils of developing with AI agents. Through most of January, I iterated, acting as semi-technical manager and delegating almost all the design and all the implementation to Claude. Functionally, I ended up in a reasonable place: a parser in C extracted from SQLite sources using a bunch of Python scripts, a formatter built on top, support for both the SQLite language and the PerfettoSQL extensions, all exposed in a web playground. But when I reviewed the codebase in detail in late January, the downside was obvious: the codebase was complete spaghetti. I didnt understand large parts of the Python source extraction pipeline, functions were scattered in random files without a clear shape, and a few files had grown to several thousand lines. It was extremely fragile; it solved the immediate problem but it was never going to cope with my larger vision, never mind integrating it into the Perfetto tools. The saving grace was that it had proved the approach was viable and generated more than 500 tests, many of which I felt I could reuse. He threw it all away and worked more closely with the AI on the second attempt, with lots of thinking about the design, reviewing all the code, and refactoring with every step In the rewrite, refactoring became the core of my workflow. After every large batch of generated code, Id step back and ask is this ugly? Sometimes AI could clean it up. Other times there was a large-scale abstraction that AI couldnt see but I could; Id give it the direction and let it execute. If you have taste, the cost of a wrong approach drops dramatically because you can restructure quickly. He ended up with a working system, and the AI proved its value in allowing him to tackle something that hed been leaving on the todo pile for years. But even with the rewrite, the AI had its potholes. His conclusion of the relative value of AI in different scenarios: When I was working on something I already understood deeply, AI was excellent. When I was working on something I could describe but didnt yet know, AI was good but required more care. When I was working on something where I didnt even know what I wanted, AI was somewhere between unhelpful and harmful At the heart of this is that AI works at its best when there is an objectively checkable answer. If we want an implementation that can pass some tests, then AI does a good job. But when it came to the public API: I spent several days in early March doing nothing but API refactoring, manually fixing things any experienced engineer would have instinctively avoided but AI made a total mess of. Theres no test or objective metric for is this API pleasant to use and will this API help users solve the problems they have and thats exactly why the coding agents did so badly at it. I became familiar with Ryan Avents writing when he wrote the Free Exchange column for The Economist. His recent post talks about how James Talarico and Zohran Mamdani have made their religion an important part of their electoral appeal, and their faith is centered on caring for others. He explains that a focus on care leads to an important perspective on economic growth. The first thing to understand is that we should not want growth for its own sake. What is good about growth is that it expands our collective capacities: we come to know more and we are able to do more. This, in turn, allows us to alleviate suffering, to discover more things about the universe, and to spend more time being complete people.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Principles of Mechanical Sympathy</title>
  <link>https://martinfowler.com/articles/mechanical-sympathy-principles.html</link>
  <pubDate>Sun, 17 May 2026 00:05:16 +0200</pubDate>
  <description>Modern hardware is remarkably fast, but software often fails to leverage it. Caer Sanders has found it valuable to guide their work with mechanical sympathy - the practice of creating software that is sympathetic to its underlying hardware. They distill this practice into everyday principles: predictable memory access, awareness of cache lines, single-writer, and natural batching. more</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Feedback Flywheel</title>
  <link>https://martinfowler.com/articles/reduce-friction-ai/feedback-flywheel.html</link>
  <pubDate>Sun, 17 May 2026 00:05:16 +0200</pubDate>
  <description>Rahul Garg finishes his series on reducing the friction in AI-Assisted Development. He proposes a structured feedback practice that harvests learnings from AI sessions and feeds them back into the team&#39;s shared artifacts, turning individual experience into collective improvement. more</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 21</title>
  <link>https://martinfowler.com/fragments/2026-04-21.html</link>
  <pubDate>Sun, 17 May 2026 00:05:15 +0200</pubDate>
  <description>Last week Thoughtworks released the 34th volume of our Technology Radar . This radar is our biannual survey of our experience of the technology scene, highlighting tools, techniques, platforms, and languages that we’ve used or otherwise caught our eye. This edition contains 118 blips, each briefly describing our impressions of one of these elements. As we would expect, the radar is dominated by AI-oriented topics. Part of this is revisiting familiar ground with LLM-assisted eyes: An interesting consequence of AI in software development is that it’s not only forcing us to look to the future; it’s also pushing us to revisit the foundations of our craft. While assembling this edition, we found ourselves returning to many established techniques, from pair programming to zero trust architecture, and from mutation testing to DORA metrics. We also revisited core principles of software craftsmanship, such as clean code, deliberate design, testability and accessibility as a first-class concern. This is not nostalgia, but a necessary counterweight to the speed at which AI tools can generate complexity. We also observed a resurgence of the command line: After years of abstracting it away in the name of usability, agentic tools are bringing developers back to the terminal as a primary interface. I was especially happy to see my colleague Jim Gumbley added to the writing team, he’s been a regular source of security information for me over the years, including working on this site’s Threat Modeling Guide . Having a strong security presence on the radar team is especially important given the serious security concerns around using LLMs. One of the themes of the radar is securing “permission hungry” agents: “Permission hungry” describes the bind at the heart of the current agent moment: the agents worth building are the ones that need access to everything. OpenClaw and Claude Cowork supervise real work tasks; Gas Town coordinates agent swarms across entire codebases. These agents require broad access to private data, external communication and real systems — each arguing that the payoff justifies it. However, like a skier who’s just learned to turn and confidently points themselves at the hardest black run, the safeguards haven’t caught up with that ambition. The appetite for access collides with unsolved problems. Prompt injection means models still can’t reliably distinguish trusted instructions from untrusted input. Given all of this, many of this radar’s blips are about Harness Engineering, indeed the radar meeting was a major source of ideas for Birgitta’s excellent article on the subject. The radar includes several blips suggesting the guides and sensors necessary for a well-fitting harness. I expect that when the next radar appears in six months time, that list will increase. ❄ ❄ ❄ ❄ ❄ Mike Mason looks what happens when developers aren’t reading the code . The Python codebase Claude produced was largely working. Unit tests passed, and a few hours of real-world testing showed it was successfully managing a fairly complex piece of my infrastructure. But somewhere around 100KB of total code I noticed something: the main file had grown to about 50KB (2,000 lines) and Claude Code, when it needed to make edits, had started reaching for sed to find and modify code within that file. When I saw that, it was a serious alarm bell. As well as the experience of “a friend”, he ponders the 500,000 lines of Claude Code after the leak. Both things are true: there is good architecture in Claude Code, and there is also an incomprehensible mess. That’s actually the point. You don’t get to know which is which without reading the code. His conclusion is a rough framework. Throw-away analysis scripts are fine to vibe away. Tooling you need to maintain and durable code, needs regular human review - even if it’s just a human asking a model to evaluate the code with some hints as to what good code looks like The moment you say “I’m getting uncomfortable with how big this is getting, can we do something better?” it does the right thing: sensible decomposition, new classes, sometimes even unit tests for the new thing. It knew, it just didn’t volunteer it. He does recommend being serious with CLAUDE.md , I don’t know if he’s tried many of the patterns that Rahul Garg has recently posted to break the similar frustration loop that he saw. ❄ ❄ ❄ ❄ ❄ Dan Davies poses an annoying philosophy thought experiment for us to consider how we feel about LLMs indulging in ghost writing. ❄ ❄ ❄ ❄ ❄ DOGE dismantled many useful things during their brief period with the wood chipper. One of these was DirectFile, a government program that supported people filing their taxes online. Don Moynihan has talked to many folks involved in Direct File, has penned a worthwhile essay that isn’t just relevant to DirectFile and other U.S. government technology projects, but indeed any technology initiative in a large organization. Moynihan highlights: a paradox of government reform: the simpler a potential change appears, the more likely that it has not been implemented because it features deceptive complexity that others have tried and failed to resolve. I’ve heard that tale in many a large corporation too One way government initiatives are different is that, at its best, it’s built on an attitude of public service Many who worked on Direct File drew a sharp contrast with DOGE and their approach to building tech products. One point of distinction was DOGE’s seeming disinterest in public interest goals and of the public itself: “if you do not think government has a responsibility to serve people, I think it draws into question how good are you going to be at making government work better for people if you just don’t believe in that underlying principle” The tragedy for U.S. taxpayers like me is that we’ve lost an effective way to go through the annual hassle of taxes. In addition the IRS is much weaker - it’s lost 25% of its staff and its budget is 40% below what it was in 2010. Much though we hate tax collectors, this isn’t a good thing. An efficient tax system is an important part of national security, many historians consider the ability to raise taxes effectively was an important reason why Britain won its century-long struggle with France in the Eighteenth century. A wonky tax system is also a major reason why the French monarchy, so powerful at the start of that century, fell to revolution. Indeed there is considerable evidence that increasing the budget of the IRS would more than pay for itself by increasing revenue .</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 14</title>
  <link>https://martinfowler.com/fragments/2026-04-14.html</link>
  <pubDate>Sun, 17 May 2026 00:05:15 +0200</pubDate>
  <description>I attended the first Pragmatic Summit early this year, and while there host Gergely Orosz interviewed Kent Beck and myself on stage . The video runs for about half-an-hour. I always enjoy nattering with Kent like this, and Gergely pushed into some worthwhile topics. Given the timing, AI dominated the conversation - we compared it to earlier technology shifts, the experience of agile methods, the role of TDD, the danger of unhealthy performance metrics, and how to thrive in an AI-native industry. ❄ ❄ ❄ ❄ ❄ Perl is a language I used a little, but never loved. However the definitive book on it, by its designer Larry Wall, contains a wonderful gem. The three virtues of a programmer: hubris, impatience - and above all - laziness . Bryan Cantrill also loves this virtue : Of these virtues, I have always found laziness to be the most profound: packed within its tongue-in-cheek self-deprecation is a commentary on not just the need for abstraction, but the aesthetics of it. Laziness drives us to make the system as simple as possible (but no simpler!) — to develop the powerful abstractions that then allow us to do much more, much more easily. Of course, the implicit wink here is that it takes a lot of work to be lazy Understanding how to think about a problem domain by building abstractions (models) is my favorite part of programming. I love it because I think it’s what gives me a deeper understanding of a problem domain, and because once I find a good set of abstractions, I get a buzz from the way they make difficulties melt away, allowing me to achieve much more functionality with less lines of code. Cantrill worries that AI is so good at writing code, we risk losing that virtue, something that’s reinforced by brogrammers bragging about how they produce thirty-seven thousand lines of code a day. The problem is that LLMs inherently lack the virtue of laziness. Work costs nothing to an LLM. LLMs do not feel a need to optimize for their own (or anyone’s) future time, and will happily dump more and more onto a layercake of garbage. Left unchecked, LLMs will make systems larger, not better — appealing to perverse vanity metrics, perhaps, but at the cost of everything that matters. As such, LLMs highlight how essential our human laziness is: our finite time forces us to develop crisp abstractions in part because we don’t want to waste our (human!) time on the consequences of clunky ones. The best engineering is always borne of constraints, and the constraint of our time places limits on the cognitive load of the system that we’re willing to accept. This is what drives us to make the system simpler, despite its essential complexity. This reflection particularly struck me this Sunday evening. I’d spent a bit of time making a modification of how my music playlist generator worked. I needed a new capability, spent some time adding it, got frustrated at how long it was taking, and wondered about maybe throwing a coding agent at it. More thought led to realizing that I was doing it in a more complicated way than it needed to be. I was including a facility that I didn’t need, and by applying yagni , I could make the whole thing much easier, doing the task in just a couple of dozen lines of code. If I had used an LLM for this, it may well have done the task much more quickly, but would it have made a similar over-complication? If so would I just shrug and say LGTM? Would that complication cause me (or the LLM) problems in the future? ❄ ❄ ❄ ❄ ❄ Jessica Kerr (Jessitron) has a simple example of applying the principle of Test-Driven Development to prompting agents . She wants all updates to include updating the documentation. Instructions – We can change AGENTS.md to instruct our coding agent to look for documentation files and update them. Verification – We can add a reviewer agent to check each PR for missed documentation updates. This is two changes, so I can break this work into two parts. Which of these should we do first? Of course my initial comment about TDD answers that question ❄ ❄ ❄ ❄ ❄ Mark Little prodded an old memory of mine as he wondered about to work with AIs that are over-confident of their knowledge and thus prone to make up answers to questions, or to act when they should be more hesitant. He draws inspiration from an old, low-budget, but classic SciFi movie: Dark Star . I saw that movie once in my 20s (ie a long time ago), but I still remember the crisis scene where a crew member has to use philosophical argument to prevent a sentient bomb from detonating . Doolittle: You have no absolute proof that Sergeant Pinback ordered you to detonate. Bomb #20: I recall distinctly the detonation order. My memory is good on matters like these. Doolittle: Of course you remember it, but all you remember is merely a series of sensory impulses which you now realize have no real, definite connection with outside reality. Bomb #20: True. But since this is so, I have no real proof that you’re telling me all this. Doolittle: That’s all beside the point. I mean, the concept is valid no matter where it originates. Bomb #20: Hmmmm…. Doolittle: So, if you detonate… Bomb #20: In nine seconds…. Doolittle: …you could be doing so on the basis of false data. Bomb #20: I have no proof it was false data. Doolittle: You have no proof it was correct data! Bomb #20: I must think on this further. Doolittle has to expand the bomb’s consciousness, teaching it to doubt its sensors. As Little puts it: That’s a useful metaphor for where we are with AI today. Most AI systems are optimised for decisiveness. Given an input, produce an output. Given ambiguity, resolve it probabilistically. Given uncertainty, infer. This works well in bounded domains, but it breaks down in open systems where the cost of a wrong decision is asymmetric or irreversible. In those cases, the correct behaviour is often deferral, or even deliberate inaction. But inaction is not a natural outcome of most AI architectures. It has to be designed in. In my more human interactions, I’ve always valued doubt, and distrust people who operate under undue certainty. Doubt doesn’t necessarily lead to indecisiveness, but it does suggest that we include the risk of inaccurate information or faulty reasoning into decisions with profound consequences. If we want AI systems that can operate safely without constant human oversight, we need to teach them not just how to decide, but when not to. In a world of increasing autonomy, restraint isn’t a limitation, it’s a capability. And in many cases, it may be the most important one we build.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Alan Turing play in Cambridge MA</title>
  <link>https://martinfowler.com/articles/202604-turing.html</link>
  <pubDate>Sun, 17 May 2026 00:05:15 +0200</pubDate>
  <description>Last night I saw Central Square Theater’s excellent production of Breaking the Code . It’s about Alan Turing, who made a monumental contribution to both my profession and the fate of free democracies. Well worth seeing if you’re in the Boston area this month.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Structured-Prompt-Driven Development (SPDD)</title>
  <link>https://martinfowler.com/articles/structured-prompt-driven/</link>
  <pubDate>Sun, 17 May 2026 00:05:15 +0200</pubDate>
  <description>LLM programming assistants have demonstrated considerable value, but mostly with individual developers. The internal IT organization in Thoughtworks has been using them for their teams and have developed a method and workflow called Structured Prompt-Driven Development (SPDD). Wei Zhang and Jessie Jie Xia describe a simple example of this workflow with details in github. This workflow treats the prompts as a first-class artifact, kept with the code in version control, and used to align development with business needs. They have found that developers need three key skills to be effective: alignment, abstraction-first, and iterative review. more…</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 21</title>
  <link>https://martinfowler.com/fragments/2026-04-21.html</link>
  <pubDate>Sun, 17 May 2026 00:05:15 +0200</pubDate>
  <description>Last week Thoughtworks released the 34th volume of our Technology Radar . This radar is our biannual survey of our experience of the technology scene, highlighting tools, techniques, platforms, and languages that weve used or otherwise caught our eye. This edition contains 118 blips, each briefly describing our impressions of one of these elements. As we would expect, the radar is dominated by AI-oriented topics. Part of this is revisiting familiar ground with LLM-assisted eyes: An interesting consequence of AI in software development is that its not only forcing us to look to the future; its also pushing us to revisit the foundations of our craft. While assembling this edition, we found ourselves returning to many established techniques, from pair programming to zero trust architecture, and from mutation testing to DORA metrics. We also revisited core principles of software craftsmanship, such as clean code, deliberate design, testability and accessibility as a first-class concern. This is not nostalgia, but a necessary counterweight to the speed at which AI tools can generate complexity. We also observed a resurgence of the command line: After years of abstracting it away in the name of usability, agentic tools are bringing developers back to the terminal as a primary interface. I was especially happy to see my colleague Jim Gumbley added to the writing team, hes been a regular source of security information for me over the years, including working on this sites Threat Modeling Guide . Having a strong security presence on the radar team is especially important given the serious security concerns around using LLMs. One of the themes of the radar is securing permission hungry agents: Permission hungry describes the bind at the heart of the current agent moment: the agents worth building are the ones that need access to everything. OpenClaw and Claude Cowork supervise real work tasks; Gas Town coordinates agent swarms across entire codebases. These agents require broad access to private data, external communication and real systems each arguing that the payoff justifies it. However, like a skier whos just learned to turn and confidently points themselves at the hardest black run, the safeguards havent caught up with that ambition. The appetite for access collides with unsolved problems. Prompt injection means models still cant reliably distinguish trusted instructions from untrusted input. Given all of this, many of this radars blips are about Harness Engineering, indeed the radar meeting was a major source of ideas for Birgittas excellent article on the subject. The radar includes several blips suggesting the guides and sensors necessary for a well-fitting harness. I expect that when the next radar appears in six months time, that list will increase. Mike Mason looks what happens when developers arent reading the code . The Python codebase Claude produced was largely working. Unit tests passed, and a few hours of real-world testing showed it was successfully managing a fairly complex piece of my infrastructure. But somewhere around 100KB of total code I noticed something: the main file had grown to about 50KB (2,000 lines) and Claude Code, when it needed to make edits, had started reaching for sed to find and modify code within that file. When I saw that, it was a serious alarm bell. As well as the experience of a friend, he ponders the 500,000 lines of Claude Code after the leak. Both things are true: there is good architecture in Claude Code, and there is also an incomprehensible mess. Thats actually the point. You dont get to know which is which without reading the code. His conclusion is a rough framework. Throw-away analysis scripts are fine to vibe away. Tooling you need to maintain and durable code, needs regular human review - even if its just a human asking a model to evaluate the code with some hints as to what good code looks like The moment you say Im getting uncomfortable with how big this is getting, can we do something better? it does the right thing: sensible decomposition, new classes, sometimes even unit tests for the new thing. It knew, it just didnt volunteer it. He does recommend being serious with CLAUDE.md , I dont know if hes tried many of the patterns that Rahul Garg has recently posted to break the similar frustration loop that he saw. Dan Davies poses an annoying philosophy thought experiment for us to consider how we feel about LLMs indulging in ghost writing. DOGE dismantled many useful things during their brief period with the wood chipper. One of these was DirectFile, a government program that supported people filing their taxes online. Don Moynihan has talked to many folks involved in Direct File, has penned a worthwhile essay that isnt just relevant to DirectFile and other U.S. government technology projects, but indeed any technology initiative in a large organization. Moynihan highlights: a paradox of government reform: the simpler a potential change appears, the more likely that it has not been implemented because it features deceptive complexity that others have tried and failed to resolve. Ive heard that tale in many a large corporation too One way government initiatives are different is that, at its best, its built on an attitude of public service Many who worked on Direct File drew a sharp contrast with DOGE and their approach to building tech products. One point of distinction was DOGEs seeming disinterest in public interest goals and of the public itself: if you do not think government has a responsibility to serve people, I think it draws into question how good are you going to be at making government work better for people if you just dont believe in that underlying principle The tragedy for U.S. taxpayers like me is that weve lost an effective way to go through the annual hassle of taxes. In addition the IRS is much weaker - its lost 25% of its staff and its budget is 40% below what it was in 2010. Much though we hate tax collectors, this isnt a good thing. An efficient tax system is an important part of national security, many historians consider the ability to raise taxes effectively was an important reason why Britain won its century-long struggle with France in the Eighteenth century. A wonky tax system is also a major reason why the French monarchy, so powerful at the start of that century, fell to revolution. Indeed there is considerable evidence that increasing the budget of the IRS would more than pay for itself by increasing revenue .</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 14</title>
  <link>https://martinfowler.com/fragments/2026-04-14.html</link>
  <pubDate>Sun, 17 May 2026 00:05:15 +0200</pubDate>
  <description>I attended the first Pragmatic Summit early this year, and while there host Gergely Orosz interviewed Kent Beck and myself on stage . The video runs for about half-an-hour. I always enjoy nattering with Kent like this, and Gergely pushed into some worthwhile topics. Given the timing, AI dominated the conversation - we compared it to earlier technology shifts, the experience of agile methods, the role of TDD, the danger of unhealthy performance metrics, and how to thrive in an AI-native industry. Perl is a language I used a little, but never loved. However the definitive book on it, by its designer Larry Wall, contains a wonderful gem. The three virtues of a programmer: hubris, impatience - and above all - laziness . Bryan Cantrill also loves this virtue : Of these virtues, I have always found laziness to be the most profound: packed within its tongue-in-cheek self-deprecation is a commentary on not just the need for abstraction, but the aesthetics of it. Laziness drives us to make the system as simple as possible (but no simpler!) to develop the powerful abstractions that then allow us to do much more, much more easily. Of course, the implicit wink here is that it takes a lot of work to be lazy Understanding how to think about a problem domain by building abstractions (models) is my favorite part of programming. I love it because I think its what gives me a deeper understanding of a problem domain, and because once I find a good set of abstractions, I get a buzz from the way they make difficulties melt away, allowing me to achieve much more functionality with less lines of code. Cantrill worries that AI is so good at writing code, we risk losing that virtue, something thats reinforced by brogrammers bragging about how they produce thirty-seven thousand lines of code a day. The problem is that LLMs inherently lack the virtue of laziness. Work costs nothing to an LLM. LLMs do not feel a need to optimize for their own (or anyones) future time, and will happily dump more and more onto a layercake of garbage. Left unchecked, LLMs will make systems larger, not better appealing to perverse vanity metrics, perhaps, but at the cost of everything that matters. As such, LLMs highlight how essential our human laziness is: our finite time forces us to develop crisp abstractions in part because we dont want to waste our (human!) time on the consequences of clunky ones. The best engineering is always borne of constraints, and the constraint of our time places limits on the cognitive load of the system that were willing to accept. This is what drives us to make the system simpler, despite its essential complexity. This reflection particularly struck me this Sunday evening. Id spent a bit of time making a modification of how my music playlist generator worked. I needed a new capability, spent some time adding it, got frustrated at how long it was taking, and wondered about maybe throwing a coding agent at it. More thought led to realizing that I was doing it in a more complicated way than it needed to be. I was including a facility that I didnt need, and by applying yagni , I could make the whole thing much easier, doing the task in just a couple of dozen lines of code. If I had used an LLM for this, it may well have done the task much more quickly, but would it have made a similar over-complication? If so would I just shrug and say LGTM? Would that complication cause me (or the LLM) problems in the future? Jessica Kerr (Jessitron) has a simple example of applying the principle of Test-Driven Development to prompting agents . She wants all updates to include updating the documentation. Instructions We can change AGENTS.md to instruct our coding agent to look for documentation files and update them. Verification We can add a reviewer agent to check each PR for missed documentation updates. This is two changes, so I can break this work into two parts. Which of these should we do first? Of course my initial comment about TDD answers that question Mark Little prodded an old memory of mine as he wondered about to work with AIs that are over-confident of their knowledge and thus prone to make up answers to questions, or to act when they should be more hesitant. He draws inspiration from an old, low-budget, but classic SciFi movie: Dark Star . I saw that movie once in my 20s (ie a long time ago), but I still remember the crisis scene where a crew member has to use philosophical argument to prevent a sentient bomb from detonating . Doolittle: You have no absolute proof that Sergeant Pinback ordered you to detonate. Bomb #20: I recall distinctly the detonation order. My memory is good on matters like these. Doolittle: Of course you remember it, but all you remember is merely a series of sensory impulses which you now realize have no real, definite connection with outside reality. Bomb #20: True. But since this is so, I have no real proof that youre telling me all this. Doolittle: Thats all beside the point. I mean, the concept is valid no matter where it originates. Bomb #20: Hmmmm. Doolittle: So, if you detonate Bomb #20: In nine seconds. Doolittle: you could be doing so on the basis of false data. Bomb #20: I have no proof it was false data. Doolittle: You have no proof it was correct data! Bomb #20: I must think on this further. Doolittle has to expand the bombs consciousness, teaching it to doubt its sensors. As Little puts it: Thats a useful metaphor for where we are with AI today. Most AI systems are optimised for decisiveness. Given an input, produce an output. Given ambiguity, resolve it probabilistically. Given uncertainty, infer. This works well in bounded domains, but it breaks down in open systems where the cost of a wrong decision is asymmetric or irreversible. In those cases, the correct behaviour is often deferral, or even deliberate inaction. But inaction is not a natural outcome of most AI architectures. It has to be designed in. In my more human interactions, Ive always valued doubt, and distrust people who operate under undue certainty. Doubt doesnt necessarily lead to indecisiveness, but it does suggest that we include the risk of inaccurate information or faulty reasoning into decisions with profound consequences. If we want AI systems that can operate safely without constant human oversight, we need to teach them not just how to decide, but when not to. In a world of increasing autonomy, restraint isnt a limitation, its a capability. And in many cases, it may be the most important one we build.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Alan Turing play in Cambridge MA</title>
  <link>https://martinfowler.com/articles/202604-turing.html</link>
  <pubDate>Sun, 17 May 2026 00:05:15 +0200</pubDate>
  <description>Last night I saw Central Square Theaters excellent production of Breaking the Code . Its about Alan Turing, who made a monumental contribution to both my profession and the fate of free democracies. Well worth seeing if youre in the Boston area this month.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Structured-Prompt-Driven Development (SPDD)</title>
  <link>https://martinfowler.com/articles/structured-prompt-driven/</link>
  <pubDate>Sun, 17 May 2026 00:05:15 +0200</pubDate>
  <description>LLM programming assistants have demonstrated considerable value, but mostly with individual developers. The internal IT organization in Thoughtworks has been using them for their teams and have developed a method and workflow called Structured Prompt-Driven Development (SPDD). Wei Zhang and Jessie Jie Xia describe a simple example of this workflow with details in github. This workflow treats the prompts as a first-class artifact, kept with the code in version control, and used to align development with business needs. They have found that developers need three key skills to be effective: alignment, abstraction-first, and iterative review. more</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: May 5</title>
  <link>https://martinfowler.com/fragments/2026-05-05.html</link>
  <pubDate>Sun, 17 May 2026 00:05:14 +0200</pubDate>
  <description>Over the last couple of months Rahul Garg published a series of posts here on how to reduce the friction in AI-assisted programming . To make it easier to put these ideas into practice he’s now built an open-source framework to operationalize these patterns . AI coding assistants jump straight to code, silently make design decisions, forget constraints mid-conversation, and produce output nobody reviewed against real engineering standards. Lattice fixes this with composable skills in three tiers – atoms, molecules, refiners – that embed battle-tested engineering disciplines (Clean Architecture, DDD, design-first methodology, secure coding, and more), plus a living context layer (the .lattice/ folder) that accumulates your project’s standards, decisions, and review insights. The system gets smarter with use – after a few feature cycles, atoms aren’t applying generic rules, they’re applying your rules, informed by your history. It can be installed as a Claude Code plugin or downloaded for use with any AI tool. ❄ ❄ ❄ ❄ ❄ This is also a good point to note that the article by my colleagues Wei Zhang and Jessie Jie Xia on Structured-Prompt-Driven Development (SPDD) has generated an enormous amount of traffic, and quite a few questions. They have thus added a Q&amp;A section to the article that answers a dozen of them. ❄ ❄ ❄ ❄ ❄ Jessica Kerr (Jessitron) posted a merry tidbit of building a tool to work with conversation logs. She observes the double feedback loop involved. There are (at least) two feedback loops running here. One is the development loop, with Claude doing what I ask and then me checking whether that is indeed what I want.[…] Then there’s a meta-level feedback loop, the “is this working?” check when I feel resistance. Frustration, tedium, annoyance–these feelings are a signal to me that maybe this work could be easier. The double loop here is both changing the thing we are building but also changing the thing we are using to build the thing we are building. As developers using software to build software, we have potential to mold our own work environment. With AI making software change superfast, changing our program to make debugging easier pays off immediately. Also, this is fun! Indeed it is, and makes me think that agents are allowing us to (re)discover one of the Great Lost Joys of software development - that of molding my development environment to exactly fit the problem and my personal tastes. A while ago I wrote about this under the name Internal Reprogramability . It was a central feature of the Smalltalk and Lisp communities but was mostly lost as we got complex and polished IDEs (although the unix command line gives a hint of its appeal). ❄ ❄ ❄ ❄ ❄ Ashley MacIsaac is a musician from Cape Breton, who plays folk-influenced music (I have a couple of his albums in my collection). Google generated an AI overview that asserted that had been convicted of crimes, including sexual assault and was on the national sex-offender registry. These were completely false, confusing him with another man with the same name. MacIsaac is suing Google for defamation : “This was not a search engine just scanning through things and giving somebody else’s story […] It was published by them. And to me, that is defamation. The guardrails were not there to prevent Google AI from publishing that content.” MacIsaac’s point is that Google must take responsibility for what a tool it controls publishes. MacIsaac suffered genuine harm here, not just to his reputation, but he also had a concert canceled and the claims affect his performing. “I felt that tangible fear from something that was published by a media company,” he said in an interview with The Canadian Press. “I feared for my own safety going on stage because of what I was labelled as. And I don’t know how long this will follow me.” Too often tech companies try to dodge the consequences of their actions. There are genuine issues about the difficulties of monitoring what’s published at scale, but that’s a responsibility that they should face up to. ❄ ❄ ❄ ❄ ❄ Stephen O’Grady (RedMonk) takes a serious look at how much the big tech companies are spending on AI build-outs . The sums involved are staggering, not just in absolute terms (over $100 Billion), but also compared to the revenues of the companies involved. Firms like Amazon, Alphabet, and Microsoft are spending over 50% of their revenues (not profits). Meta and Oracle hit or pass 75% of revenues. That level of investment would have been unthinkable a decade ago. Today, the chart suggests it’s table stakes There is a notable exception: Apple. Here they are clearly Thinking Different, eyeballing the chart they seem closer to 10% of revenues. ❄ ❄ ❄ ❄ ❄ Most folks I talk to about agentic programming are using models in the cloud: Claude, Codex, and the like. Everyone agrees these are the most powerful models, the ones that triggered the November Inflection . But do we need to use the Most Powerful Models, particularly when we have to ship data to them, and pay handsomely for the privilege? Willem van den Ende considers an alternative , that local models are Good Enough. Assumptions - We are all figuring this out. - Quality of a harness (coding agent + “skills” + extensions) can matter as least as much as the model - Running open models and an open coding agent + custom extensions takes time, but pays off in understanding and a stable base where engineering effort compounds - Open, local, models have (for me) crossed the point where they are good enough for daily work with a coding agent. The post describes in detail his setup for local model work. It includes sandboxing with Nono, which is something to consider even if using a Cloud model - such powerful tools need a Zero Trust Architecture . ❄ ❄ ❄ ❄ ❄ In case you haven’t noticed, those last two fragments resonate. Apple isn’t playing the cloud AI model game, they are saving a huge amount of money, and if local models end up being the future, they’ll be looking rather wise. Van den Ende’s post led me to a podcast by Nate B Jones that argues that Apple is replaying a fifty-year old strategy here. All those years ago anyone who used a computer bought time on a mainframe, the Apple II put far less capable compute into the home and small office. From there came spreadsheets, desktop publishing, and the modern home computer - things that weren’t possible using mainframes. He sees the rise of John Ternus as CEO isn’t merely a switch to a known insider successor - but a bet that the future of AI is sophisticated hardware in the home, office, and pocket. If Open Source models are Good Enough, then why spend money sending tokens - containing your sensitive data - to the AI megacorps? ❄ ❄ ❄ ❄ ❄ Talking of five decades in the past, it was in 1974 that Fred Brooks opened one of the most influential books in our profession with these paragraphs: No scene from prehistory is quite so vivid as that of the mortal struggles of great beasts in the tar pits. In the mind’s eye one sees dinosaurs, mammoths, and sabertoothed tigers struggling against the grip of the tar. The fiercer the struggle, the more entangling the tar, and no beast is so strong or so skillful but that he ultimately sinks. Large-system programming has over the past decade been such a tar pit, and many great and powerful beasts have thrashed violently in it. Most have emerged with running systems — few have met goals, schedules, and budgets. Large and small, massive or wiry, team after team has become entangled in the tar. No one thing seems to cause the difficulty — any particular paw can be pulled away. But the accumulation of simultaneous and interacting factors brings slower and slower motion. Everyone seems to have been surprised by the stickiness of the problem, and it is hard to discern the nature of it. But we must try to understand it if we are to solve it. With the title of his recent post Kent Beck summons up that imagery as the Genie Tarpit . After explaining why skilled software development is about building both features and futures, he observes that these AI tools aren’t doing a good job of producing software with the kind of internal quality that is needed for a good future. Here’s what I’ve observed — genies naturally live down &amp; to the left of muddling. The “plausible deniability” task orientation of the genie leaves it claiming success even though the code doesn’t work at all. And complexity piles on complexity until even the genie can’t pretend to make progress any more. It’s still an open question whether, or to what extent, internal quality matters in the age of agentic programming. One view is, as Laura Tacho puts it, “The Venn Diagram of Developer Experience and Agent Experience is a circle”. Well organized elements, with good naming, help The Genie understand code, so are important if we can continue to go beyond small disposable systems. The other view is that such internal quality doesn’t matter, that the galaxy brain of LLMs will make sense of the biggest bowls of spaghetti. Maybe not now, but after a couple more inflections. That’s the fundamental question. Can The Genie evade the tar pit, or will it struggle fruitlessly against the tar’s sticky grip?</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 29</title>
  <link>https://martinfowler.com/fragments/2026-04-29.html</link>
  <pubDate>Sun, 17 May 2026 00:05:14 +0200</pubDate>
  <description>Chris Parsons has updated his guide on using AI to code . This is his third update, what I like about it is that he gives a lot of concrete information about how he uses AI, with sufficient detail that we can learn from him. His advice also resonates with the better advice I’ve seen out there, so the article makes a good overview of the state of using AI for software development. I wrote the previous version of this post in March 2025, updated it once in August, and it has been linked from almost everything I have written about AI engineering since. The fundamentals from that post still hold: keep changes small, build guardrails, document ruthlessly, and make sure every change gets verified before it ships. One thing has had to move with the volume. “Verified” used to mean “read by you”. With modern agent throughput, it has to mean “checked by tests, by type checkers, by automated gates, or by you where your judgement matters”. The check still happens; it just does not always happen in your head. Like Simon Willison, he makes a clear distinction between vibe coding, where you don’t look at or care about the code, and agentic engineering. He recommends either Claude Code or Codex CLI. He considers the inner harness provided by his preferred tools to be a key part of their advantage. He sees verification is the key thing to focus on: A team that can generate five approaches and verify all five in an afternoon will outpace a team that generates one and waits a week for feedback. The game is not “how fast can we build” any more. It is “how fast can we tell whether this is right”. That shifts where to invest. Build better review surfaces, not better prompts. Make feedback unnecessary where you can by having the agent verify against a realistic environment before it asks a human, and make feedback instant where you cannot. The key role of the programmer is in training the AI to write software properly, and the most important thing skilled agentic programmers can do is pass that skill onto other developers. And if you are a senior engineer worried that your job is quietly turning into approving diffs: it is. The way out is to train the AI so the diffs are right the first time, to make yourself the person on the team who shapes the harness, and to make that work the visible thing you are measured on. That role compounds in a way that reviewing never will. ❄ ❄ ❄ ❄ ❄ Early this month Birgitta Böckeler wrote a superb article on Harness Engineering . (That’s not just my opinion, judging by the crazy traffic it’s attracted.) Birgitta has now recorded a video discussion with Chris Ford on Harness Engineering , which is well worth a watch. In it they focus on discussing the role of computational sensors in the harness, such as static analysis and tests. LLMs are great for exploratory and fuzzy rules, but once you have something that really is objective, converting it to a formal, unambiguous, deterministic format can give you more assurance Birgitta did some experiments to explore the benefits of adding sensors, including a deep dive on using static analysis. She found it’s more useful as agents can really address every warning, and don’t slack off like humans do. ❄ ❄ ❄ ❄ ❄ Adam Tornhill considers an age-old question: how long should a function be? This question is still relevent in the age of agentic programming. AI models do not “understand” code the way humans do. They infer meaning from patterns in tokens and depend heavily on what is explicitly expressed in the code. Research shows that naming plays a critical role. When meaningful identifiers are replaced with arbitrary names, model performance drops significantly. Current models rely heavily on literal features—names, structure, and local context—rather than inferred semantics. Like me, he doesn’t think the answer is to think about how many lines should be in a function, instead it’s all about providing better structure. He has a good example of how a well-chosen function defines useful concepts, where a function wraps four lines of code, returning a new concept that enters the vocabulary of the program. Functions are the first unit of structure in a codebase. They define how logic is grouped, how intent is communicated, and how change is localized. If the function boundaries are wrong, everything built on top of them becomes harder to understand and harder to evolve. This fits with my writing that the key to function length is the separation between intention and implementation : If you have to spend effort into looking at a fragment of code to figure out what it’s doing, then you should extract it into a function and name the function after that “what”. That way when you read it again, the purpose of the function leaps right out at you, and most of the time you won’t need to care about how the function fulfills its purpose - which is the body of the function. ❄ ❄ ❄ ❄ ❄ Many folks in my feeds recommended Nilay Patel’s post on Why People Hate AI . He thinks that many people in the software world have “software brain”: The simplest definition I’ve come up with is that it’s when you see the whole world as a series of databases that can be controlled with the structured language of software code. Like I said, this is a powerful way of seeing things. So much of our lives run through databases, and a bunch of important companies have been built around maintaining those databases and providing access to them. Zillow is a database of houses. Uber is a database of cars and riders. YouTube is a database of videos. The Verge’s website is a database of stories. You can go on and on and on. Once you start seeing the world as a bunch of databases, it’s a small jump to feeling like you can control everything if you can just control the data. Software Brain views people into databases, and oddly enough, a lot of people don’t like that. Which is why so many polls reveal the negative feelings folks have about the AI movement. Even taking the time to consider how much of your life is captured in databases makes people unhappy. No one wants to be surveilled constantly, and especially not in a way that makes tech companies even more powerful. But getting everything in a database so software can see it is a preoccupation of the AI industry. It’s why all the meeting systems have AI note takers in them now. Patel draws a similarity that I’ve often made - that between programmers and lawyers. Lawyers who draw up contracts are creating a protocol for how the parties in the contract should behave. As Patel puts it: If the heart of software brain is the idea that thinking in the structured language of code can make things happen in the real world, well, the heart of lawyer brain is that thinking in the structured legal language of statutes and citations can also make things happen. Hell, it can give you power over society. The difference, of course, is that law is non-deterministic. Litigation is resolving what happens when people have different ideas about how those contracts should execute. ❄ ❄ ❄ ❄ ❄ I was chatting recently with a company who wanted to use AI to make sense of their internal data. The potential was great, but the problem was that the data a mess. People put stuff into fields that didn’t make sense, and there was little consistency about how people classified important entities. As someone commented the hardest problem with internal data is precise, consistent definitions You can imagine my astonishment. (i.e. none at all - this has been a constant theme during all my decades with computers.) The difficulty of getting such definitions undermines much of the hopes of Software Brain This resonates with our relationship with LLMs when programming. Precise and consistent definitions strike me as crucial to effective communication with The Genie. These definitions need to grow in the conversation, and be tended over time. Conceptual modeling will be a key skill for agentic programming and whatever comes next. (At least I hope it will, since it’s a part of programming I really enjoy.) ❄ ❄ ❄ ❄ ❄ Patel’s article refers to Ezra Klein’s post about the new feeling in San Francisco . You might think that A.I. types in Silicon Valley, flush with cash, are on top of the world right now. I found them notably insecure. They think the A.I. age has arrived and its winners and losers will be determined, in part, by speed of adoption. The argument is simple enough: The advantages of working atop an army of A.I. assistants and coders will compound over time, and to begin that process now is to launch yourself far ahead of your competition later. And so they are racing one another to fully integrate A.I. into their lives and into their companies. But that doesn’t just mean using A.I. It means making themselves legible to the A.I. That legibility is the heart of Patel’s observation. That’s why I see many colleagues of mine dumping all their email, meeting notes, slide decks and everything else into files that AI can read and work with. This works to the strengths of AI, we know that AI is really good at querying unstructured information. So I can figure out what’s buried in my notes in a way that’s far more effective than hoping I’m typing the right search regex. I’ve been using Gemini a fair bit for exactly this on the web, finding it easier to write a question to it than to throw search terms at Google. Gemini keeps a record of my past requests, and uses that to help it tune what I’m looking for. As Klein observes: [The AI] is constantly referring back to other things it knows, or thinks it knows, about me. Sycophancy, in my experience, has given way to an occasionally unsettling attentiveness; a constant drawing of connections between my current concerns and my past queries, like a therapist desperate to prove he’s been paying close attention. The result is a strange amalgam of feeling seen and feeling caricatured. Like myself, Klein is a writer, and is faced by the same temptation that I have when I think about AI and writing. Maybe instead of toiling over articles, I should ask an LLM to create an AGENTS.md file that summarizes my writing style, and every few days ask it to compose an article on some subject, read it, tweak it, and then publish my erudite musings. But that’s not at all appealing to me. I want understanding to grow in my brain , not the LLM’s transient session. Writing to explain my thinking to others is how I refine that thinking, “chiseling that idea into something publishable” as Klein puts it. To have an AI write for me is to cripple my own mind.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Bliki: Mythical Man Month</title>
  <link>https://martinfowler.com/bliki/MythicalManMonth.html</link>
  <pubDate>Sun, 17 May 2026 00:05:14 +0200</pubDate>
  <description>In the early 1960s, Fred Brooks managed the development of IBM&#39;s System/360 computer systems. After it was done he penned his thoughts in the book The Mythical Man-Month which became one of the most influential books on software development after its publication in 1975. Reading it in 2026, we&#39;ll find some of it outdated, but it also retains many lessons that are still relevant today. The book contains Brooks&#39;s law: “Adding manpower to a late software project makes it later.” The issue here is communication, as the number of people grows, the number of communication paths between those people grows exponentially. Unless these paths are skillfully designed, then work quickly falls apart. Perhaps my most enduring lesson from this book is the importance of conceptual integrity I will contend that conceptual integrity is the most important consideration in system design. It is better to have a system omit certain anomalous features and improvements, but to reflect one set of design ideas, than to have one that contains many good but independent and uncoordinated ideas. He argues that conceptual integrity comes from both simplicity and straightforwardness - the latter being how easily we can compose elements. This point of view has been a strong influence upon my career, the pursuit of conceptual integrity underpins much of my work. The anniversary edition of this book is the one to get, because it also includes his even-more influential 1986 essay “No Silver Bullet”.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>What is Code</title>
  <link>https://martinfowler.com/articles/what-is-code.html</link>
  <pubDate>Sun, 17 May 2026 00:05:14 +0200</pubDate>
  <description>Increasingly humans delegate writing code to agents. Will there even be source code in the future? To wrestle with this question, we have to understand what code is. Unmesh Joshi sees code as having two distinct but intertwined purposes: instructions to a machine and a conceptual model of the problem domain . He explores why it&#39;s vital to build a vocabulary to talk to the machine, how programming languages are thinking tools, and how this affects our future as we work with LLMs. more…</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: May 5</title>
  <link>https://martinfowler.com/fragments/2026-05-05.html</link>
  <pubDate>Sun, 17 May 2026 00:05:14 +0200</pubDate>
  <description>Over the last couple of months Rahul Garg published a series of posts here on how to reduce the friction in AI-assisted programming . To make it easier to put these ideas into practice hes now built an open-source framework to operationalize these patterns . AI coding assistants jump straight to code, silently make design decisions, forget constraints mid-conversation, and produce output nobody reviewed against real engineering standards. Lattice fixes this with composable skills in three tiers atoms, molecules, refiners that embed battle-tested engineering disciplines (Clean Architecture, DDD, design-first methodology, secure coding, and more), plus a living context layer (the .lattice/ folder) that accumulates your projects standards, decisions, and review insights. The system gets smarter with use after a few feature cycles, atoms arent applying generic rules, theyre applying your rules, informed by your history. It can be installed as a Claude Code plugin or downloaded for use with any AI tool. This is also a good point to note that the article by my colleagues Wei Zhang and Jessie Jie Xia on Structured-Prompt-Driven Development (SPDD) has generated an enormous amount of traffic, and quite a few questions. They have thus added a Q&amp;A section to the article that answers a dozen of them. Jessica Kerr (Jessitron) posted a merry tidbit of building a tool to work with conversation logs. She observes the double feedback loop involved. There are (at least) two feedback loops running here. One is the development loop, with Claude doing what I ask and then me checking whether that is indeed what I want.[] Then theres a meta-level feedback loop, the is this working? check when I feel resistance. Frustration, tedium, annoyancethese feelings are a signal to me that maybe this work could be easier. The double loop here is both changing the thing we are building but also changing the thing we are using to build the thing we are building. As developers using software to build software, we have potential to mold our own work environment. With AI making software change superfast, changing our program to make debugging easier pays off immediately. Also, this is fun! Indeed it is, and makes me think that agents are allowing us to (re)discover one of the Great Lost Joys of software development - that of molding my development environment to exactly fit the problem and my personal tastes. A while ago I wrote about this under the name Internal Reprogramability . It was a central feature of the Smalltalk and Lisp communities but was mostly lost as we got complex and polished IDEs (although the unix command line gives a hint of its appeal). Ashley MacIsaac is a musician from Cape Breton, who plays folk-influenced music (I have a couple of his albums in my collection). Google generated an AI overview that asserted that had been convicted of crimes, including sexual assault and was on the national sex-offender registry. These were completely false, confusing him with another man with the same name. MacIsaac is suing Google for defamation : This was not a search engine just scanning through things and giving somebody elses story [] It was published by them. And to me, that is defamation. The guardrails were not there to prevent Google AI from publishing that content. MacIsaacs point is that Google must take responsibility for what a tool it controls publishes. MacIsaac suffered genuine harm here, not just to his reputation, but he also had a concert canceled and the claims affect his performing. I felt that tangible fear from something that was published by a media company, he said in an interview with The Canadian Press. I feared for my own safety going on stage because of what I was labelled as. And I dont know how long this will follow me. Too often tech companies try to dodge the consequences of their actions. There are genuine issues about the difficulties of monitoring whats published at scale, but thats a responsibility that they should face up to. Stephen OGrady (RedMonk) takes a serious look at how much the big tech companies are spending on AI build-outs . The sums involved are staggering, not just in absolute terms (over $100 Billion), but also compared to the revenues of the companies involved. Firms like Amazon, Alphabet, and Microsoft are spending over 50% of their revenues (not profits). Meta and Oracle hit or pass 75% of revenues. That level of investment would have been unthinkable a decade ago. Today, the chart suggests its table stakes There is a notable exception: Apple. Here they are clearly Thinking Different, eyeballing the chart they seem closer to 10% of revenues. Most folks I talk to about agentic programming are using models in the cloud: Claude, Codex, and the like. Everyone agrees these are the most powerful models, the ones that triggered the November Inflection . But do we need to use the Most Powerful Models, particularly when we have to ship data to them, and pay handsomely for the privilege? Willem van den Ende considers an alternative , that local models are Good Enough. Assumptions - We are all figuring this out. - Quality of a harness (coding agent + skills + extensions) can matter as least as much as the model - Running open models and an open coding agent + custom extensions takes time, but pays off in understanding and a stable base where engineering effort compounds - Open, local, models have (for me) crossed the point where they are good enough for daily work with a coding agent. The post describes in detail his setup for local model work. It includes sandboxing with Nono, which is something to consider even if using a Cloud model - such powerful tools need a Zero Trust Architecture . In case you havent noticed, those last two fragments resonate. Apple isnt playing the cloud AI model game, they are saving a huge amount of money, and if local models end up being the future, theyll be looking rather wise. Van den Endes post led me to a podcast by Nate B Jones that argues that Apple is replaying a fifty-year old strategy here. All those years ago anyone who used a computer bought time on a mainframe, the Apple II put far less capable compute into the home and small office. From there came spreadsheets, desktop publishing, and the modern home computer - things that werent possible using mainframes. He sees the rise of John Ternus as CEO isnt merely a switch to a known insider successor - but a bet that the future of AI is sophisticated hardware in the home, office, and pocket. If Open Source models are Good Enough, then why spend money sending tokens - containing your sensitive data - to the AI megacorps? Talking of five decades in the past, it was in 1974 that Fred Brooks opened one of the most influential books in our profession with these paragraphs: No scene from prehistory is quite so vivid as that of the mortal struggles of great beasts in the tar pits. In the minds eye one sees dinosaurs, mammoths, and sabertoothed tigers struggling against the grip of the tar. The fiercer the struggle, the more entangling the tar, and no beast is so strong or so skillful but that he ultimately sinks. Large-system programming has over the past decade been such a tar pit, and many great and powerful beasts have thrashed violently in it. Most have emerged with running systems few have met goals, schedules, and budgets. Large and small, massive or wiry, team after team has become entangled in the tar. No one thing seems to cause the difficulty any particular paw can be pulled away. But the accumulation of simultaneous and interacting factors brings slower and slower motion. Everyone seems to have been surprised by the stickiness of the problem, and it is hard to discern the nature of it. But we must try to understand it if we are to solve it. With the title of his recent post Kent Beck summons up that imagery as the Genie Tarpit . After explaining why skilled software development is about building both features and futures, he observes that these AI tools arent doing a good job of producing software with the kind of internal quality that is needed for a good future. Heres what Ive observed genies naturally live down &amp; to the left of muddling. The plausible deniability task orientation of the genie leaves it claiming success even though the code doesnt work at all. And complexity piles on complexity until even the genie cant pretend to make progress any more. Its still an open question whether, or to what extent, internal quality matters in the age of agentic programming. One view is, as Laura Tacho puts it, The Venn Diagram of Developer Experience and Agent Experience is a circle. Well organized elements, with good naming, help The Genie understand code, so are important if we can continue to go beyond small disposable systems. The other view is that such internal quality doesnt matter, that the galaxy brain of LLMs will make sense of the biggest bowls of spaghetti. Maybe not now, but after a couple more inflections. Thats the fundamental question. Can The Genie evade the tar pit, or will it struggle fruitlessly against the tars sticky grip?</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Fragments: April 29</title>
  <link>https://martinfowler.com/fragments/2026-04-29.html</link>
  <pubDate>Sun, 17 May 2026 00:05:14 +0200</pubDate>
  <description>Chris Parsons has updated his guide on using AI to code . This is his third update, what I like about it is that he gives a lot of concrete information about how he uses AI, with sufficient detail that we can learn from him. His advice also resonates with the better advice Ive seen out there, so the article makes a good overview of the state of using AI for software development. I wrote the previous version of this post in March 2025, updated it once in August, and it has been linked from almost everything I have written about AI engineering since. The fundamentals from that post still hold: keep changes small, build guardrails, document ruthlessly, and make sure every change gets verified before it ships. One thing has had to move with the volume. Verified used to mean read by you. With modern agent throughput, it has to mean checked by tests, by type checkers, by automated gates, or by you where your judgement matters. The check still happens; it just does not always happen in your head. Like Simon Willison, he makes a clear distinction between vibe coding, where you dont look at or care about the code, and agentic engineering. He recommends either Claude Code or Codex CLI. He considers the inner harness provided by his preferred tools to be a key part of their advantage. He sees verification is the key thing to focus on: A team that can generate five approaches and verify all five in an afternoon will outpace a team that generates one and waits a week for feedback. The game is not how fast can we build any more. It is how fast can we tell whether this is right. That shifts where to invest. Build better review surfaces, not better prompts. Make feedback unnecessary where you can by having the agent verify against a realistic environment before it asks a human, and make feedback instant where you cannot. The key role of the programmer is in training the AI to write software properly, and the most important thing skilled agentic programmers can do is pass that skill onto other developers. And if you are a senior engineer worried that your job is quietly turning into approving diffs: it is. The way out is to train the AI so the diffs are right the first time, to make yourself the person on the team who shapes the harness, and to make that work the visible thing you are measured on. That role compounds in a way that reviewing never will. Early this month Birgitta Böckeler wrote a superb article on Harness Engineering . (Thats not just my opinion, judging by the crazy traffic its attracted.) Birgitta has now recorded a video discussion with Chris Ford on Harness Engineering , which is well worth a watch. In it they focus on discussing the role of computational sensors in the harness, such as static analysis and tests. LLMs are great for exploratory and fuzzy rules, but once you have something that really is objective, converting it to a formal, unambiguous, deterministic format can give you more assurance Birgitta did some experiments to explore the benefits of adding sensors, including a deep dive on using static analysis. She found its more useful as agents can really address every warning, and dont slack off like humans do. Adam Tornhill considers an age-old question: how long should a function be? This question is still relevent in the age of agentic programming. AI models do not understand code the way humans do. They infer meaning from patterns in tokens and depend heavily on what is explicitly expressed in the code. Research shows that naming plays a critical role. When meaningful identifiers are replaced with arbitrary names, model performance drops significantly. Current models rely heavily on literal featuresnames, structure, and local contextrather than inferred semantics. Like me, he doesnt think the answer is to think about how many lines should be in a function, instead its all about providing better structure. He has a good example of how a well-chosen function defines useful concepts, where a function wraps four lines of code, returning a new concept that enters the vocabulary of the program. Functions are the first unit of structure in a codebase. They define how logic is grouped, how intent is communicated, and how change is localized. If the function boundaries are wrong, everything built on top of them becomes harder to understand and harder to evolve. This fits with my writing that the key to function length is the separation between intention and implementation : If you have to spend effort into looking at a fragment of code to figure out what its doing, then you should extract it into a function and name the function after that what. That way when you read it again, the purpose of the function leaps right out at you, and most of the time you wont need to care about how the function fulfills its purpose - which is the body of the function. Many folks in my feeds recommended Nilay Patels post on Why People Hate AI . He thinks that many people in the software world have software brain: The simplest definition Ive come up with is that its when you see the whole world as a series of databases that can be controlled with the structured language of software code. Like I said, this is a powerful way of seeing things. So much of our lives run through databases, and a bunch of important companies have been built around maintaining those databases and providing access to them. Zillow is a database of houses. Uber is a database of cars and riders. YouTube is a database of videos. The Verges website is a database of stories. You can go on and on and on. Once you start seeing the world as a bunch of databases, its a small jump to feeling like you can control everything if you can just control the data. Software Brain views people into databases, and oddly enough, a lot of people dont like that. Which is why so many polls reveal the negative feelings folks have about the AI movement. Even taking the time to consider how much of your life is captured in databases makes people unhappy. No one wants to be surveilled constantly, and especially not in a way that makes tech companies even more powerful. But getting everything in a database so software can see it is a preoccupation of the AI industry. Its why all the meeting systems have AI note takers in them now. Patel draws a similarity that Ive often made - that between programmers and lawyers. Lawyers who draw up contracts are creating a protocol for how the parties in the contract should behave. As Patel puts it: If the heart of software brain is the idea that thinking in the structured language of code can make things happen in the real world, well, the heart of lawyer brain is that thinking in the structured legal language of statutes and citations can also make things happen. Hell, it can give you power over society. The difference, of course, is that law is non-deterministic. Litigation is resolving what happens when people have different ideas about how those contracts should execute. I was chatting recently with a company who wanted to use AI to make sense of their internal data. The potential was great, but the problem was that the data a mess. People put stuff into fields that didnt make sense, and there was little consistency about how people classified important entities. As someone commented the hardest problem with internal data is precise, consistent definitions You can imagine my astonishment. (i.e. none at all - this has been a constant theme during all my decades with computers.) The difficulty of getting such definitions undermines much of the hopes of Software Brain This resonates with our relationship with LLMs when programming. Precise and consistent definitions strike me as crucial to effective communication with The Genie. These definitions need to grow in the conversation, and be tended over time. Conceptual modeling will be a key skill for agentic programming and whatever comes next. (At least I hope it will, since its a part of programming I really enjoy.) Patels article refers to Ezra Kleins post about the new feeling in San Francisco . You might think that A.I. types in Silicon Valley, flush with cash, are on top of the world right now. I found them notably insecure. They think the A.I. age has arrived and its winners and losers will be determined, in part, by speed of adoption. The argument is simple enough: The advantages of working atop an army of A.I. assistants and coders will compound over time, and to begin that process now is to launch yourself far ahead of your competition later. And so they are racing one another to fully integrate A.I. into their lives and into their companies. But that doesnt just mean using A.I. It means making themselves legible to the A.I. That legibility is the heart of Patels observation. Thats why I see many colleagues of mine dumping all their email, meeting notes, slide decks and everything else into files that AI can read and work with. This works to the strengths of AI, we know that AI is really good at querying unstructured information. So I can figure out whats buried in my notes in a way thats far more effective than hoping Im typing the right search regex. Ive been using Gemini a fair bit for exactly this on the web, finding it easier to write a question to it than to throw search terms at Google. Gemini keeps a record of my past requests, and uses that to help it tune what Im looking for. As Klein observes: [The AI] is constantly referring back to other things it knows, or thinks it knows, about me. Sycophancy, in my experience, has given way to an occasionally unsettling attentiveness; a constant drawing of connections between my current concerns and my past queries, like a therapist desperate to prove hes been paying close attention. The result is a strange amalgam of feeling seen and feeling caricatured. Like myself, Klein is a writer, and is faced by the same temptation that I have when I think about AI and writing. Maybe instead of toiling over articles, I should ask an LLM to create an AGENTS.md file that summarizes my writing style, and every few days ask it to compose an article on some subject, read it, tweak it, and then publish my erudite musings. But thats not at all appealing to me. I want understanding to grow in my brain , not the LLMs transient session. Writing to explain my thinking to others is how I refine that thinking, chiseling that idea into something publishable as Klein puts it. To have an AI write for me is to cripple my own mind.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
<item>
  <title>Bliki: Mythical Man Month</title>
  <link>https://martinfowler.com/bliki/MythicalManMonth.html</link>
  <pubDate>Sun, 17 May 2026 00:05:14 +0200</pubDate>
  <description>In the early 1960s, Fred Brooks managed the development of IBM&#39;s System/360 computer systems. After it was done he penned his thoughts in the book The Mythical Man-Month which became one of the most influential books on software development after its publication in 1975. Reading it in 2026, we&#39;ll find some of it outdated, but it also retains many lessons that are still relevant today. The book contains Brooks&#39;s law: Adding manpower to a late software project makes it later. The issue here is communication, as the number of people grows, the number of communication paths between those people grows exponentially. Unless these paths are skillfully designed, then work quickly falls apart. Perhaps my most enduring lesson from this book is the importance of conceptual integrity I will contend that conceptual integrity is the most important consideration in system design. It is better to have a system omit certain anomalous features and improvements, but to reflect one set of design ideas, than to have one that contains many good but independent and uncoordinated ideas. He argues that conceptual integrity comes from both simplicity and straightforwardness - the latter being how easily we can compose elements. This point of view has been a strong influence upon my career, the pursuit of conceptual integrity underpins much of my work. The anniversary edition of this book is the one to get, because it also includes his even-more influential 1986 essay No Silver Bullet.</description>
  <dc:source>Blogs/Martin_Fowler</dc:source>
</item>
</channel>
</rss>
