<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Florens Verschelde</title>
  <id>urn:uuid:c6e9028c-f392-5f5f-9a97-3267a03f1956</id>
  <link href="https://fvsch.com/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="https://fvsch.com" rel="alternate" type="text/html"/>
  <updated>2020-08-20T20:16:38+02:00</updated>
  <icon>https://fvsch.com/assets/icon.png?v=qb92t0</icon>
  <author>
    <name>Florens Verschelde</name>
  </author>
  <entry xml:lang="en">
    <id>urn:uuid:e3de44ba-880f-53a6-acc6-40ef6e786d67</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/devtools-retrospective"/>
    <title>A DevTools retrospective… and also maybe gift me a laptop?</title>
    <published>2020-08-20T00:00:00+02:00</published>
    <updated>2020-08-20T00:00:00+02:00</updated>
    <summary>As my volunteer work in the Firefox DevTools project is coming to a close, I’ve been looking back and thinking: what if I got a small something out of it, beyond a bit experience, as a small thank you?</summary>
    <content type="html">&lt;p&gt;&lt;a class=&quot;article-warning&quot; data-icon=&quot;💶&quot; href=&quot;https://ko-fi.com/fvsch&quot; style=&quot;text-decoration:none; border-color: skyblue&quot;&gt;
Do you want to gift me money on Ko-fi so that I can buy a new laptop? Now is your chance!
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So my laptop died a few months ago and I’ve made do with my work computer instead, but recently I’ve considered buying a new one to not depend on work equipment.&lt;/p&gt;
&lt;figure&gt;&lt;img width=&quot;280&quot; src=&quot;https://fvsch.com/articles/devtools-retrospective/old-laptop.jpg&quot; alt=&quot;Laptop with Firefox and DevTools stickers.&quot;&gt;&lt;img width=&quot;280&quot; src=&quot;https://fvsch.com/articles/devtools-retrospective/idiot-son.jpg&quot; alt=&quot;Cat chewing a house plant.&quot;&gt;&lt;figcaption&gt;
    My 5 year old laptop, which won’t start up anymore; and my 1 year old son, who — to add insult to injury — ate through the charger cable.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In the past 2 years, I’ve also spent a lot of my time — including time off from my day job — to work on &lt;a href=&quot;https://firefox-dev.tools/&quot;&gt;Firefox DevTools&lt;/a&gt; as a volunteer contributor. As my involvement in this project is coming to a close, I’ve been looking back and thinking: what if I got a small something out of it, beyond a bit experience, as a small thank you?&lt;/p&gt;
&lt;p&gt;So it’s decided: I am asking you, kind reader, to consider chipping in to help me pay for a new laptop. I’m targeting something like a Macbook Air (€1200 with VAT) or the cheapest Macbook Pro (€1500), or a similar laptop with Windows or Linux. &lt;a href=&quot;https://ko-fi.com/fvsch&quot;&gt;I’ve set up a Ko-fi page with a €1350 goal&lt;/a&gt;, and you can give anything you want, if you appreciate the work I did.&lt;/p&gt;
&lt;p&gt;I’m &lt;em&gt;not&lt;/em&gt; in any financial hardship. I could buy a laptop without help, but I’ll appreciate it if I can cover most or some of the cost this way. Read on if you want more context on why I’m asking for contributions.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;What did I do as a contributor?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;I started contributing to Firefox DevTools in Spring 2018, initially to work on small UI details and land CSS fixes for the Inspector, Console or Network Monitor. Over two years I have:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Helped dozens of volunteers, Outreachy candidates and Google Summer of Code candidates get started with the DevTools codebase (usually over Bugzilla and Slack). I’ve also led or participated in code reviews.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bugzilla.mozilla.org/buglist.cgi?bug_status=RESOLVED&amp;amp;bug_status=VERIFIED&amp;amp;resolution=FIXED&amp;amp;emailtype1=exact&amp;amp;query_format=advanced&amp;amp;emailassigned_to1=1&amp;amp;list_id=15382408&amp;amp;email1=florens%40fvsch.com&quot;&gt;Fixed 106 bugs&lt;/a&gt; of various sizes (from a few hours to week-long efforts).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/firefox-devtools/ux/issues&quot;&gt;Participated in many UX discussions&lt;/a&gt; to provide feedback and ideas.&lt;/li&gt;
&lt;li&gt;Led a few design projects, including an icon refresh (shipped) and a redesign of the Settings page (accepted but not implemented).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One example: if you’re using Firefox DevTools on Linux, your experience may have improved overnight &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1458224&quot;&gt;thanks to this simple CSS patch&lt;/a&gt; that fixed a lot of issues with font sizes and icon alignments; it took just a few lines of code, but days of investigation and tests!&lt;/p&gt;
&lt;p&gt;I’ve worked remotely with &lt;a href=&quot;https://violasong.com&quot;&gt;Victoria Wang&lt;/a&gt;, &lt;a href=&quot;https://patrickbrosset.com&quot;&gt;Patrick Brosset&lt;/a&gt;, &lt;a href=&quot;http://nicolaschevobbe.com&quot;&gt;Nicolas Chevobbe&lt;/a&gt;, &lt;a href=&quot;https://digitarald.de&quot;&gt;Harald Kirschner&lt;/a&gt; (hire him as a product manager!), &lt;a href=&quot;https://mtigley.dev&quot;&gt;Micah Tigley&lt;/a&gt;, &lt;a href=&quot;https://jasonlaster.github.io&quot;&gt;Jason Laster&lt;/a&gt;, &lt;a href=&quot;https://davidwalsh.name&quot;&gt;David Walsh&lt;/a&gt; (looking for his next role too!), &lt;a href=&quot;https://yzen.github.io&quot;&gt;Yura Zenevich&lt;/a&gt; and many others.&lt;/p&gt;
&lt;p&gt;I also participated in three Mozilla All Hands weeks, which were fun! (But they were also work, somewhat tiring, and it did require me to use up my job’s paid time off.&lt;sup id=&quot;fnref1:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;
&lt;p&gt;On the visible side, here are some of the design-y work I’ve done. Click through to to see full-size images.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&quot;https://fvsch.com/articles/devtools-retrospective/devtools-figma-docs@2x.png&quot;&gt;&lt;img class=&quot;border&quot; width=&quot;300&quot; src=&quot;https://fvsch.com/articles/devtools-retrospective/devtools-figma-docs@300w.png&quot; alt=&quot;Screenshot of my DevTools Figma project&quot;&gt;&lt;/a&gt;
  &lt;figcaption&gt;An overview of design documents I’ve worked on in my Figma “DevTools” project. It’s mostly icon work and small mock-ups to explain ideas.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://fvsch.com/articles/devtools-retrospective/devtools-toolbox-icons@2x.png&quot;&gt;&lt;img class=&quot;border&quot; width=&quot;340&quot; src=&quot;https://fvsch.com/articles/devtools-retrospective/devtools-toolbox-icons@1x.png&quot; alt=&quot;Exploration and final set of icons for the DevTools tabs.&quot;&gt;&lt;/a&gt;
  &lt;figcaption&gt;A redesign of the DevTools tab icons, made to follow the 2017 &lt;a href=&quot;&quot; https:&gt;Photon Design System&lt;/a&gt; guidelines. This was my most visible user-facing work.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;a href=&quot;https://fvsch.com/articles/devtools-retrospective/devtools-settings-redesign@2x.png&quot;&gt;&lt;img class=&quot;border&quot; width=&quot;600&quot; src=&quot;https://fvsch.com/articles/devtools-retrospective/devtools-settings-redesign@2x.png&quot; alt=&quot;Screenshots of a redesign of the DevTools Settings page.&quot;&gt;&lt;/a&gt;
  &lt;figcaption&gt;I’ve worked on a redesign of the DevTools settings page, to break it up in more readable chunks. You can &lt;a href=&quot;https://fx-devtools-settings.netlify.app/&quot;&gt;try out the HTML prototype&lt;/a&gt; (but do try it in Firefox, since it uses CSS Logical Properties not supported by Chrome and Safari at this time), and &lt;a href=&quot;https://github.com/fvsch/devtools-settings&quot;&gt;check out the repository&lt;/a&gt;.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;&lt;span&gt;Why am I asking for money?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Because I felt like it! 😁&lt;/p&gt;
&lt;p&gt;Just to make it clear, I don’t think I’m entitled to money here. Nobody has to give me anything! (Though if I did feel entitled to actual &lt;em&gt;pay&lt;/em&gt; for my DevTools work, at 1-2 days a week for something like 18 months straight, I wouldn’t be asking for $1.5k.)&lt;/p&gt;
&lt;p&gt;Anyway, here’s how the story went.&lt;/p&gt;
&lt;p&gt;I didn’t start contributing to DevTools with money in mind. I wanted to scratch some itches, and I was excited about having patches I wrote improving a UI used by hundreds of thousands. And I also wanted to help out so that we don’t end up in a Chrome monoculture for good.&lt;/p&gt;
&lt;p&gt;But after 6-12 months of doing good work, I ended up thinking: I like the work and it seems to be useful, let’s get paid for it and do it full time!&lt;/p&gt;
&lt;p&gt;Sadly I’m pretty bad at advocating for myself, and I never actually applied for a role. I did reach out to a few team members, but what I heard was that there was no budget for more people, beyond maybe short-term contracts (not an option for me at the time).&lt;/p&gt;
&lt;p&gt;Then in the last year it became clear that Mozilla management didn’t see DevTools as a vital investment, and in 2020 reorgs and layoffs have strongly impacted DevTools.&lt;/p&gt;
&lt;p&gt;So that was it. Combined with some frustrations about not being able to do or land some work on my side because I could not secure reviews or other help&lt;sup id=&quot;fnref1:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, I mentally called it quits.&lt;/p&gt;
&lt;p&gt;What prompted me to ask random people for money was this tweet:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&apos;re worried about the future of #Firefox, #Gecko, #MDN and other projects from #Mozilla there&apos;s one way you can help: contribute.&lt;/p&gt;
&lt;p&gt;I always hang out in our #Introduction channel, so if you feel like coding and want to help just ping me.&lt;/p&gt;
&lt;p&gt;—&lt;a href=&quot;https://twitter.com/gabrielesvelto/status/1293633109032935426&quot;&gt;@gabrielesvelto&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have some &lt;em&gt;feelings&lt;/em&gt; about that.&lt;/p&gt;
&lt;p&gt;We’re talking about doing free work here. If you want those projects to succeed, are worried they might not, and want to make an impact, that could mean &lt;em&gt;a lot&lt;/em&gt; of free work. Is that work going to lead to burnout or an increased risk of burnout?&lt;/p&gt;
&lt;p&gt;Meanwhile, this work is adding value to projects and products (especially Gecko and Firefox) that Mozilla Corporation derives money from. That money funds big compensations for the C-Suite, and salaries for engineers that surpass anything I ever earned myself&lt;sup id=&quot;fnref1:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote-ref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. That’s true for many other contributors too, of course, and is part of a wider criticism of open-source.&lt;/p&gt;
&lt;p&gt;(And maybe talking about this just after the layoffs is ill-timed, but the layoffs are the reason why people are calling for a surge of contributions.)&lt;/p&gt;
&lt;p&gt;Finally, &lt;a href=&quot;https://twitter.com/fvsch/status/1293641513679884288&quot;&gt;as I replied on Twitter&lt;/a&gt;, if you want people to work for free you should provide some incentives, including psychological ones. For large projects like Gecko or Firefox, the technical complexity, specific processes and bureaucracy&lt;sup id=&quot;fnref1:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote-ref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; mean it’s hard to just “scratch an itch” and see results quickly. Instead, one incentive can be to ship fixes and features to tens of thousands or millions of users — an appeal that smaller projects can’t provide.&lt;/p&gt;
&lt;p&gt;But to be able to do that and not waste your time, you will need support from the core team. So you need that team to not be fired, cut in half or otherwise scaled down. And you will need some confidence that once your efforts pane out and your work is ready to ship to users, the product will not have been discontinued (ahem, Firefox OS). And if we cannot guarantee that, because strategic decisions lie in the hands of unaccountable corporate management, should we really call for people to provide free work?&lt;/p&gt;
&lt;p&gt;So I was thinking about all that, all those mixed feelings about open-source, and I ended up at: you know what, fuck it, I want some money too. My friends and family always tell me I’m super bad at valuing myself and asking for money, promotions or raises — so maybe I should break the circle.&lt;/p&gt;
&lt;p&gt;I’m asking for money. A symbolic amount, really, compared to the work I did, but still an amount that I can use for something practical.&lt;/p&gt;
&lt;p&gt;If it works out, great, and if it doesn’t… eh. 🤷‍♀️&lt;/p&gt;
&lt;p&gt;Thanks for reading this far, &lt;a href=&quot;https://ko-fi.com/fvsch&quot;&gt;do contribute if you want to&lt;/a&gt;, and please share this with friends (especially if they use Firefox DevTools 😇).&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id=&quot;fn:1&quot;&gt;
&lt;p&gt;Mozilla shoulders all other costs of inviting volunteer to the Mozilla All Hands, including plane tickets, hotel rooms, catering, and expense stipends. I don’t have exact numbers, but the total cost for inviting a single volunteer three times is probably in the $10–15k range?&amp;#160;&lt;a href=&quot;#fnref1:1&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:2&quot;&gt;
&lt;p&gt;For example, for the Settings page implementation, I was motivated to do a lot of the work but would have required mentorship or timely help. One developer also had ongoing work on that panel that he could share with me, but he was moved from the DevTools team to the Firefox for Android project.&amp;#160;&lt;a href=&quot;#fnref1:2&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:3&quot;&gt;
&lt;p&gt;To be clear, I don’t have any issue with current or former Mozilla employees, especially the great people I’ve been lucky to work with. My point is that when taking a step back and looking at Mozilla or company-sponsored open-source projects in general, some people are getting compensated and others not, and maybe it should give us pause.&amp;#160;&lt;a href=&quot;#fnref1:3&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:4&quot;&gt;
&lt;p&gt;It seems largely similar in Chromium or WebKit; it comes with the scope of the project, not with Mozilla or Google being bad at it.&amp;#160;&lt;a href=&quot;#fnref1:4&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:20b1f605-d09c-5bfe-a5d4-15d419ebaf68</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/kirby-typing"/>
    <title>PHP code typing with Kirby CMS</title>
    <published>2020-08-12T00:00:00+02:00</published>
    <updated>2020-08-12T00:00:00+02:00</updated>
    <summary>Kirby 3 is built with PHP 7 and uses type annotations extensively, but this often won’t be reflected in an IDE. This post explores why, and what workarounds you can use.</summary>
    <content type="html">&lt;p&gt;As I was updating this site and writing &lt;a href=&quot;https://fvsch.com/kirby-logic&quot;&gt;my previous post on Kirby CMS&lt;/a&gt;, I sometimes struggled to get good code autocompletion.&lt;/p&gt;
&lt;p&gt;Kirby 3 is built with PHP 7 and uses type annotations extensively, but this often won’t be reflected in an IDE.
This post explores why, and what workarounds you can use.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Why you might want completions&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Unlike CMSes like WordPress and Drupal which come with default themes that generate HTML code for you out of the box, Kirby uses a very hands-off approach to building web pages.
You can start from scratch and write your own PHP controllers and templates to render what you want, using &lt;a href=&quot;https://getkirby.com/docs/guide/templates/php-api&quot;&gt;Kirby’s PHP API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That’s great if you want to build something bespoke.
But it also means that you’re going to spend a lot of time looking at the &lt;a href=&quot;https://getkirby.com/docs/reference/objects/page&quot;&gt;reference documentation&lt;/a&gt;.
Before you know it, you’re juggling between your code editor and 15 open documentation tabs.&lt;/p&gt;
&lt;figure&gt;&lt;img class=&quot;border&quot; src=&quot;https://fvsch.com/articles/kirby-typing/kirby-page-reference.png&quot; alt=&quot;Screenshot of the Kirby Reference page for the $page object&quot;&gt;&lt;/figure&gt;
&lt;p&gt;That’s where code completion — sometimes called “intellisense” — helps.
It lets you be a bit lazy and access a lot of that reference information directly in your code editor, as you type.&lt;/p&gt;
&lt;p&gt;Ideally, when your editor sees a PHP variable like &lt;code&gt;$page&lt;/code&gt; in a Kirby template, it should known that &lt;code&gt;$page&lt;/code&gt; is an instance of the &lt;code&gt;Kirby\Cms\Page&lt;/code&gt; class.
And it should offer code completions and suggestions from that class.
No need to check the docs!&lt;/p&gt;
&lt;p&gt;Sadly, this doesn’t work out of the box with Kirby, in a few common situations.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Runtime magic&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Kirby tries to be approchable for people who are not pro developers or PHP programmers.
Its creator, &lt;a href=&quot;https://bastianallgeier.com&quot;&gt;Bastian Allgeier&lt;/a&gt;, is a designer and developer, and wants to cater to a wider audience of designers, webmasters, tinkerers, etc.
So Kirby tries to do some magic things like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load templates (and controllers, if any) by file name. Follow the convention, and your code will run[^2].&lt;/li&gt;
&lt;li&gt;Automatically inject variables in your templates and in your controllers.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means that the template for a page of type &lt;code&gt;&apos;article&apos;&lt;/code&gt; can be named &lt;code&gt;site/templates/article.php&lt;/code&gt; and be as simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;h1&amp;gt;&amp;lt;?= $page-&amp;gt;title() ?&amp;gt;&amp;lt;/h1&amp;gt;
&amp;lt;?= $page-&amp;gt;text() ?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The downside of this approach is that your code editor has no information about this &lt;code&gt;$page&lt;/code&gt; variable.
It cannot know if it’s a string, a number, or an object (and what kind of object).
Your editor might even warn you that this variable was never defined, and highlight it as an error!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://getkirby.com/docs/guide/templates/controllers&quot;&gt;Kirby controllers&lt;/a&gt; use a different kind of magic.
Controllers are defined as functions returned by a script, such as &lt;code&gt;site/controllers/article.php&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
return function ($page, $site) {
  // Inject two new variables, $content and $articles,
  // in the &apos;article&apos; template
  return [
    &apos;content&apos; =&amp;gt; $page-&amp;gt;text()-&amp;gt;markdown(),
    &apos;articles&apos; =&amp;gt; $site-&amp;gt;find(&apos;blog&apos;)-&amp;gt;children()
  ];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the &lt;code&gt;$page&lt;/code&gt; and &lt;code&gt;$site&lt;/code&gt; parameters in the function?
Kirby will look at the function’s parameter names and automatically provide an instance of the current page for a parameter named &lt;code&gt;$page&lt;/code&gt;, of the Site object for a parameter named &lt;code&gt;$site&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;This lets us ask for a few built-in objects in any order we want, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;return function ($site, $kirby, $page) { … }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But again, the downside is that this is magic happening deep inside Kirby’s own code, and your IDE will have no idea what those variables are.
No code completion for you!&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Adding type hints and annotations&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;I’ve found a couple solution to this problem.&lt;/p&gt;
&lt;p&gt;For controllers, since we’re using function parameters to “request” specific objects, we can add &lt;a href=&quot;https://www.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration&quot;&gt;type declarations using PHP’s syntax&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/controllers/article.php
use Kirby\Cms\App;
use Kirby\Cms\Page;
use Kirby\Cms\Site;

return function (Site $site, App $kirby, Page $page) {
  …
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that for backward compatibility with Kirby 2, Kirby 3 provides shorter &lt;a href=&quot;https://getkirby.com/docs/reference/@/aliases&quot;&gt;class aliases&lt;/a&gt;, including &lt;code&gt;Kirby&lt;/code&gt; (for &lt;code&gt;Kirby\Cms\App&lt;/code&gt;), &lt;code&gt;Page&lt;/code&gt; and &lt;code&gt;Site&lt;/code&gt;.
Personally, I like using the longer, more explicit class names.&lt;/p&gt;
&lt;p&gt;Now, let’s talk about templates.
Templates will receive variables defined by Kirby (like the &lt;code&gt;$page&lt;/code&gt; and &lt;code&gt;$kirby&lt;/code&gt; objects), and any other variable you return in a controller.&lt;/p&gt;
&lt;p&gt;Sadly there is no way in PHP to declare “hey I know that this &lt;code&gt;$page&lt;/code&gt; variable exists in this context, and I’m going to tell you what it is”.
Unlike in TypeScript where you could write something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;declare var page: Page;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The good news is, there is a widely supported way to declare variable types in PHP, &lt;a href=&quot;https://github.com/php-fig/fig-standards/blob/2668020622d9d9eaf11d403bc1d26664dfc3ef8e/proposed/phpdoc-tags.md#517-var&quot;&gt;using the PHPDoc &lt;code&gt;@var&lt;/code&gt; tag&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/templates/article.php

/** @var Kirby\Cms\Page $page */

&amp;lt;h1&amp;gt;&amp;lt;?= $page-&amp;gt;title() ?&amp;gt;&amp;lt;/h1&amp;gt;
&amp;lt;?= $page-&amp;gt;text() ?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PHPDoc comments are not a native PHP feature, so declaring a type this way won’t affect how your code runs at all.
But most code editors and IDEs with good PHP support will understand this special comment syntax, and will read it as “there is a variable $page in the current scope, and it’s an instance of the &lt;code&gt;Kirby\Cms\Page&lt;/code&gt; class”.&lt;/p&gt;
&lt;p&gt;And if you have created a &lt;a href=&quot;https://getkirby.com/docs/guide/templates/page-models&quot;&gt;Page Model&lt;/a&gt; for this template, you can declare the type using the page model’s class name, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/models/article.php

class ArticlePage extends Kirby\Cms\Page {
  public function getArticleBody(): string {
    if ($this-&amp;gt;content()-&amp;gt;body()-&amp;gt;isNotEmpty()) {
      return $this-&amp;gt;content()-&amp;gt;body()-&amp;gt;markdown();
    }
    return &apos;&apos;;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/templates/article.php

/** @var ArticlePage $page */

&amp;lt;h1&amp;gt;&amp;lt;?= $page-&amp;gt;title() ?&amp;gt;&amp;lt;/h1&amp;gt;
&amp;lt;?= $page-&amp;gt;getArticleBody() ?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;span&gt;Making sure your editor has good PHP support&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Finally, if you want to see code completions in your editor or IDE of choice, type annotations may not be enough.
You also need an editor or IDE with good PHP support!&lt;/p&gt;
&lt;p&gt;Personally, I use &lt;a href=&quot;https://www.jetbrains.com/phpstorm/&quot;&gt;PhpStorm&lt;/a&gt; when writing more than a couple lines of PHP, because it just works.
The downside is that it’s paid software, and not cheap.&lt;/p&gt;
&lt;p&gt;I’ve also tried using &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt; with the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client&quot;&gt;PHP Intelephense&lt;/a&gt; extension. It works well, but do follow the “Quick Start” steps in the extension’s page.&lt;/p&gt;
&lt;p&gt;There are a few other options, such as &lt;a href=&quot;https://netbeans.apache.org/&quot;&gt;NetBeans&lt;/a&gt;, but I haven’t tried them out.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:f859a063-0612-5eac-ab80-b43c5cd4baea</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/kirby-logic"/>
    <title>Where should you put logic code in Kirby CMS?</title>
    <published>2020-06-01T12:00:00+02:00</published>
    <updated>2020-06-01T12:00:00+02:00</updated>
    <summary>Kirby CMS has a few options for where to put your PHP logic: in templates, controllers, page models or page methods. My favorite is page models, and we can work around their main limitation easily.</summary>
    <content type="html">&lt;p&gt;This blog has been running on &lt;a href=&quot;https://getkirby.com&quot;&gt;Kirby CMS&lt;/a&gt; for 6 years.
And I’ve been migrating it from Kirby 2 to Kirby 3, which is taking a bit of work.&lt;/p&gt;
&lt;p&gt;I like to do a bunch of custom things with my site’s structure and content,
so instead of relying on “the Kirby way” I’m implementing a bunch of stuff myself.&lt;/p&gt;
&lt;p&gt;That means writing PHP code that queries and manipulates data
— often called “logic” code by programmers —
and I’ve struggled a bit with understanding where I can put this code in the project.&lt;/p&gt;
&lt;div id=&quot;toc&quot;&gt;&lt;/div&gt;
&lt;h2&gt;&lt;span&gt;Logic in templates? Sure.&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;One thing I like about Kirby is that it’s powerful and doesn’t require you to jump through hooks.
So if you want to put all your logic in a &lt;a href=&quot;https://getkirby.com/docs/guide/templates/basics&quot;&gt;template file&lt;/a&gt;, you can!&lt;/p&gt;
&lt;p&gt;In the following example, we’re querying all the child pages of the current page, keeping the “Published” ones and sorting them by date, then listing the latest 20 pages.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/templates/blog.php
$posts = $page-&amp;gt;children()
  -&amp;gt;filterBy(&apos;isPublished&apos;, true)
  -&amp;gt;orderBy(&apos;date&apos;, &apos;desc&apos;)
  -&amp;gt;limit(20);
?&amp;gt;

&amp;lt;h1&amp;gt;&amp;lt;?= $page-&amp;gt;title() ?&amp;gt;&amp;lt;/h1&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;?php foreach($posts as $item): ?&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;?= $item-&amp;gt;title() ?&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;?php endforeach; ?&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(This template would be used for any page which has the &lt;code&gt;&apos;blog&apos;&lt;/code&gt; content type, meaning its content file is something like &lt;code&gt;content/my-page/blog.txt&lt;/code&gt;. See &lt;a href=&quot;https://getkirby.com/docs/guide/content/creating-pages#creating-published-pages-manually&quot;&gt;“Creating pages” in the Kirby documentation&lt;/a&gt; for more information.)&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Or in controllers, maybe?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Putting logic in template is great for people just getting started with Kirby, or people who may not be professional programmers.
But if you want to separate your logic from the template, you can move it to &lt;a href=&quot;https://getkirby.com/docs/guide/templates/controllers&quot;&gt;a controller function&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/controllers/blog.php

return function ($page) {
  $posts = $page-&amp;gt;children()
    -&amp;gt;filterBy(&apos;isPublished&apos;, true)
    -&amp;gt;orderBy(&apos;date&apos;, &apos;desc&apos;)
    -&amp;gt;limit(20);
  return [&apos;posts&apos; =&amp;gt; $posts];
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But what if you want to share some logic between different templates, controllers or page types?
It would be awesome if you could call something like &lt;code&gt;$page-&amp;gt;getPublishedChildren()&lt;/code&gt;, from anywhere.&lt;/p&gt;
&lt;p&gt;Kirby offers two ways to do that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://getkirby.com/docs/reference/plugins/extensions/page-methods&quot;&gt;Page Methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://getkirby.com/docs/guide/templates/page-models&quot;&gt;Page Models&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;span&gt;Creating a page method&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Page methods apply to all &lt;code&gt;$page&lt;/code&gt; objects, regardless of page type.
They’re a slightly older technique in Kirby, and in Kirby 3 you need to declare a plugin to add page methods:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/plugins/my-plugin/index.php

Kirby::plugin(&apos;me/my-plugin&apos;, [
  &apos;pageMethods&apos; =&amp;gt; [
    &apos;getPublishedChildren&apos; =&amp;gt; function() {
      return $this-&amp;gt;children()
        -&amp;gt;filterBy(&apos;isPublished&apos;, true)
        -&amp;gt;orderBy(&apos;date&apos;, &apos;desc&apos;)
        -&amp;gt;limit(20);
    }
  ]
]);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a few downsides to page methods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Having to register a plugin is a bit awkward.&lt;/li&gt;
&lt;li&gt;If you’re using PHP types and intellisense in your editor, your editor has no way to know that &lt;code&gt;$this&lt;/code&gt; in your page method is an instance of Kirby’s &lt;code&gt;Page&lt;/code&gt; class. That means you don’t get code completions and validation in your editor.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;span&gt;Creating a page model&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;The newer option is to use page models, which are defined as PHP classes extending Kirby’s &lt;code&gt;Page&lt;/code&gt; class.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/models/blog.php

class BlogPage extends Page {
  public function getPublishedChildren() {
    return $this-&amp;gt;children()
      -&amp;gt;filterBy(&apos;isPublished&apos;, true)
      -&amp;gt;orderBy(&apos;date&apos;, &apos;desc&apos;)
      -&amp;gt;limit(20);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks a little bit cleaner to me, mainly because we avoid creating a plugin.
And since we’re extending the &lt;code&gt;Page&lt;/code&gt; class, now our code editors will know what methods the &lt;code&gt;$this&lt;/code&gt; object has&lt;sup id=&quot;fnref1:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;And reading the Kirby documentation, and especially the &lt;a href=&quot;https://getkirby.com/docs/guide&quot;&gt;Guide&lt;/a&gt;, it looks like Page Models are the preferred mechanism for adding functionality to page objects.&lt;/p&gt;
&lt;p&gt;But they have one big downside: they only apply to a given page type, the &lt;code&gt;&apos;blog&apos;&lt;/code&gt; page type in this example.
If you try to use &lt;code&gt;$page-&amp;gt;getPublishedChildren()&lt;/code&gt; on a page with a different type, you get an error.&lt;/p&gt;
&lt;p&gt;Are we forced to use Page Methods in a plugin to share functionality between pages? Well, maybe not.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Creating a default page model&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Our page models already inherit from Kirby’s &lt;code&gt;Page&lt;/code&gt; class.
What if we added one more class to that hierarchy?&lt;/p&gt;
&lt;p&gt;Let’s start by moving our method to a new class, which we’ll call &lt;code&gt;DefaultPage&lt;/code&gt;.
Ideally we should pick a name that doesn’t match a page type (meaning we don’t have pages with a content file named &lt;code&gt;default.txt&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/models/default.php

class DefaultPage extends Page {
  public function getPublishedChildren() {
    /* ... */
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can create a class for our &lt;code&gt;&apos;blog&apos;&lt;/code&gt; page type, which extends &lt;code&gt;DefaultPage&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/models/blog.php
require_once &apos;default.php&apos;;

class BlogPage extends DefaultPage {
  // Add methods specific to the &apos;blog&apos; type
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that we have to require &lt;code&gt;default.php&lt;/code&gt; explicitly to load the &lt;code&gt;DefaultPage&lt;/code&gt; class,
because Kirby won’t load it automatically since it doesn’t match a real page type.&lt;/p&gt;
&lt;p&gt;If we add a new page type, we can create an empty page model that inherits from &lt;code&gt;DefaultPage&lt;/code&gt;,
and it will inherit the same shared methods.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php // site/models/article.php
require_once &apos;default.php&apos;;

class ArticlePage extends DefaultPage {}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fair warning: this means that to fully emulate a global “page method”,
we have to create a page model for every single page type we have!&lt;/p&gt;
&lt;p&gt;If that’s too verbose, your options are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Going back to using &lt;a href=&quot;https://getkirby.com/docs/reference/plugins/extensions/page-methods&quot;&gt;the page method technique&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/getkirby/ideas/issues/556&quot;&gt;Upvoting this feature request&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id=&quot;fn:1&quot;&gt;
&lt;p&gt;At least if we’re using a code editor with good support for PHP, like PhpStorm; in my experience &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt; was too limited out of the box, and I couldn’t figure out which plugins to try to improve that.&amp;#160;&lt;a href=&quot;#fnref1:1&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:b5a4426e-1289-521f-8fa9-bba009912d93</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/body-copy-sizes"/>
    <title>A short history of body copy sizes on the Web</title>
    <published>2020-01-01T00:00:00+01:00</published>
    <updated>2020-01-01T00:00:00+01:00</updated>
    <summary>It’s hard to pick a font size that is just right, especially as you try to adapt to different screens and scenarios. Looking at the recent history of how we got here can give us some perspective.</summary>
    <content type="html">&lt;p&gt;It’s hard to pick a font size that is &lt;em&gt;just right&lt;/em&gt;, especially as you try to adapt to different screens and scenarios. Looking at the recent history of how we got here can give us some perspective.&lt;/p&gt;
&lt;p&gt;When I started working on Web stuff around 2005, there were two extremely popular font styles for body copy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10px Verdana;&lt;/li&gt;
&lt;li&gt;11px Arial.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those two styles appeared on maybe 90 percent of professionally built sites, to be seen by users on IE5, IE5.5 and IE6 on Windows XP and earlier versions. They also looked similar, thanks to heavy font hinting, lack of font smoothing or sub-pixel rendering, and the fact that Verdana has a bigger x-height so 10px Verdana was roughly equal to 11px Arial, only with slightly wider letters.&lt;/p&gt;
&lt;p&gt;Ten and 11 pixels may seem puny today, but in the early 2000s that was deemed readable for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the 800×600 and 1024×768 screens of the late 1990s and early 2000s had biggish pixels, so the result was on the small side but not as small as it might look today;&lt;/li&gt;
&lt;li&gt;designers and their clients were accustomed to 9, 10 and 11 point sizes for body copy in print (books, magazines, leaflets…), and the prospect of using bigger values felt like shouting at readers.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With relatively little experience of the Web as an autonomous medium, graphic designers and marketing departments relied on previous knowledge — from e.g. QuarkXPress and Microsoft Word. “How do I translate this point size, which works in my leaflet or magazine ad, to a HTML size?”&lt;/p&gt;
&lt;p&gt;Of course, there is no way to reliably translate typographic points to pixels, because pixels &lt;em&gt;don’t have a universal physical size&lt;/em&gt;. Screens have different pixel-per-inch ratios. The original Macintosh had a 72ppi screen (or perhaps 68ppi?&lt;sup id=&quot;fnref1:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;). Twenty years later, in 2004, screens in the 80–90ppi range were common. A few years laters, pixels had gotten a bit smaller, and screens were often in the 90–120ppi range, while most iPhones had a 160ppi resolution&lt;sup id=&quot;fnref1:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. Despite the popular misconception, and even before the Retina transition started, the Web’s resolution &lt;em&gt;was not 72ppi&lt;/em&gt;; it never was a single thing.&lt;/p&gt;
&lt;p&gt;However, we don’t need to work out the exact points-per-inch resolution of every device to make sensible design choices. &lt;em&gt;Trying things out&lt;/em&gt; should always trump dpi, ppi, Retina, or even pixel counts.&lt;/p&gt;
&lt;p&gt;In November 2006, iA’s Oliver Reichenstein ran a simple experiment: he compared a magazine’s body copy at arms’ length and a typical site’s body copy at a common, eye-to-desktop-screen distance. The website’s text looked much smaller. Oliver &lt;a href=&quot;https://ia.net/know-how/100e2r&quot;&gt;argued for setting the body copy to the browser’s default&lt;/a&gt;, or &lt;code&gt;100%&lt;/code&gt;, which by convention is &lt;code&gt;16px&lt;/code&gt; in common browsers. In 2006, and even a few years later, it was a revolutionary proposition. Web designers and clients thought it was extreme. Five years later, we still had to &lt;a href=&quot;http://romy.tetue.net/stop-arial-11px&quot;&gt;fight for the death of 11px body copy (example, in French)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Text that is too small takes more time to read. Users may have to lean towards the screen, hold mobile devices closer, squint, or just concentrate more. As designers and developers, we strive to not ask for such extra effort from people who use or read our work.&lt;/p&gt;
&lt;p&gt;On average, online text got bigger — at least in nominal pixel sizes — in the late 2000s and early 2010s. The exact reasons why are anyone’s guess, so here’s mine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the medium’s matured, thanks to arguments such as the one spelled out by Oliver Reichenstein;&lt;/li&gt;
&lt;li&gt;text in the 10–12px range looked tiny on the iPhone and other early smartphones (resolutions in the 150–200ppi range).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I remember web designers and developers leading the way with blogs, professional news sites and the occasional client project set in “bigger” sizes in the 14–18px range. This evolution seeped into generalist news sites, one high-profile redesign after the other; nowadays, theguardian.com has &lt;code&gt;1.0625rem&lt;/code&gt; (17px) text with &lt;code&gt;1.5&lt;/code&gt; line-height, nytimes.com used &lt;code&gt;17px&lt;/code&gt; font-size and &lt;code&gt;26px&lt;/code&gt; line-height circa 2017, and in 2019 it went up to &lt;code&gt;20px&lt;/code&gt; font-size and &lt;code&gt;30px&lt;/code&gt; line-height on wide screens; body copy in the 18–21 pixel range is common (Medium, bostonglobe.com, newyorker.com, liberation.fr…).&lt;/p&gt;
&lt;p&gt;Those numbers only mark a point in time. Back in 2003, 12px Arial might have been a generously big, very readable option for that majority of users on 800×600 screens with font smoothing turned off. The feeling that the browser’s default font-size was too big, which was very present when Oliver’s article was published in 2006, was partly a cultural thing, but it had some technical reasons as well. Likewise, we’ve started to feel that 16px is rather small for all but the smaller screens, again for a combination of reasons.&lt;/p&gt;
&lt;p&gt;Then there is the very big body copy trend. In April 2012, influential web designer Jeffrey Zeldman &lt;a href=&quot;http://www.zeldman.com/2012/04/18/redesigning-in-public-again/&quot;&gt;redesigned his site with 24px Georgia body copy&lt;/a&gt; (and 32px for the opening paragraph of each post). Many fellow designers were puzzled, and some complained that the design was hard to read or reported having to zoom out on laptops or desktop computers to be able to read comfortably. Still, that design lasts today. Other designers have used similar sizes, e.g. Trent Walton &lt;a href=&quot;http://trentwalton.com/2012/06/19/fluid-type/&quot;&gt;stated his preference for 20–24px text&lt;/a&gt; (or 125–150%).&lt;/p&gt;
&lt;p&gt;The most recent example of that trend is Jeremy Keith’s &lt;a href=&quot;https://resilientwebdesign.com/&quot;&gt;Resilient Web Design&lt;/a&gt; online book. Jeremy uses a CSS lock&lt;sup id=&quot;fnref1:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote-ref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; to vary the font size depending on viewport width, between two boundaries: &lt;code&gt;100%&lt;/code&gt; and &lt;code&gt;250%&lt;/code&gt;. At 320px, you get (with default browser settings) a 16px font size. At 1600px, you get 40px text. Obviously this is a design choice, and I recon that — similar to what Trent Walton described — the intended effect for laptop and desktop use is that readers lean back and read with their faces away from the screen rather than leaning towards it. It’s a design that invites taking your time instead of skimming through the text.&lt;/p&gt;
&lt;p&gt;While that design makes for a good reading experience on smaller screens (especially smartphones and tablets, in my tests), I find it difficult on larger screens. The main issues I have with it are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Seeing very few lines of text at a time. For instance, 10 lines of text on a 13 inch laptop. I have some level of attention deficiency when reading, and this setup removes a lot of visual context when I try to scroll and read; I generally try to fight the attention deficiency by selecting every other paragraph I’m reading, but when the design only shows one or two paragraphs at a time, that doesn’t help.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each line of text is &lt;em&gt;physically wide&lt;/em&gt;, requiring the reader’s eyes to span a wider angle than usual. This could have two undesired effects: readers might end up doing more fixations to read the same line of text (e.g. 3–5 instead of 2–4); and in more extreme examples, the wider eye movements could cause eye strain or fatigue.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s say someone is seating in a sofa with a laptop on their lap, and currently reading an article. And let’s suppose that for this person in this setting with their own preferences and current level of fatigue etc., there exists an ideal font size for reading whatever text they’re reading. For instance, it could be 22px.&lt;/p&gt;
&lt;p&gt;The reading process involves &lt;a href=&quot;https://en.wikipedia.org/wiki/Eye_movement_in_reading#Saccades&quot;&gt;saccades and fixations&lt;/a&gt;. On each fixation (which can span a quarter of a second), they are only seeing a small portion of text in focus:&lt;/p&gt;
&lt;figure class=&quot;full&quot;&gt;&lt;img class=&quot;border&quot; width=&quot;640&quot; src=&quot;https://fvsch.com/articles/body-copy-sizes/fixation-simulation-1.png&quot; alt=&quot;Visual simulation of an eye fixation on medium sized text&quot;&gt;&lt;figcaption&gt;Look, I’m no scientist, so I’m hoping that’s actually representative of the real thing but take it with a spoonful of salt okay?&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now if the same text was quite bigger, but the other parameters (like the eye–screen distance) didn’t change, I’m guessing the result would look like this:&lt;/p&gt;
&lt;figure class=&quot;full&quot;&gt;&lt;img class=&quot;border&quot; width=&quot;640&quot; src=&quot;https://fvsch.com/articles/body-copy-sizes/fixation-simulation-2.png&quot; alt=&quot;Visual simulation of an eye fixation on big sized text&quot;&gt;&lt;/figure&gt;
&lt;p&gt;With the focus area staying the same size, and bigger text, I suspect that the eye can discern fewer letters correctly on each fixation. This is why my hypothesis is that for really big text (like Resilient Web Design’s &lt;code&gt;250%&lt;/code&gt; body copy on wider screens), readers will need to use more eye fixations to read the same text, and might lose in reading speed and experience fatigue sooner. I don’t have the skills needed to test this hypothesis, but I’d be wary of the &lt;em&gt;very big text&lt;/em&gt; trend.&lt;/p&gt;
&lt;p&gt;Personally, I favor more limited tweaks in font size. I like starting with a &lt;code&gt;100%&lt;/code&gt; basis for small screens, bump it for large phones or tablets (say, &lt;code&gt;110%&lt;/code&gt; or &lt;code&gt;115%&lt;/code&gt;), and maybe go up to &lt;code&gt;125%&lt;/code&gt; on laptops and larger screens. Then I tweak those values depending on the font I’m using, the look I’m going for, and what I’ve seen in testing on a variety of devices.&lt;/p&gt;
&lt;p&gt;I’m also sad that we’re somehow chasing after device makers, operating system and browser developers, and trying to tweak font sizes every other year to adapt to what is out there in the market. The very concept of raising font size a bit depending on the screen width should raise eyebrows. Isn’t it the device’s job to make sure that &lt;code&gt;font-size: 100%&lt;/code&gt; is readable?&lt;/p&gt;
&lt;p&gt;In theory, CSS pixels should &lt;a href=&quot;https://www.w3.org/TR/css-values/#absolute-lengths&quot;&gt;match a “reference pixel” defined as an angle of vision&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;reference pixel&lt;/strong&gt; is the visual angle of one pixel on a device with a pixel density of 96dpi and a distance from the reader of an arm’s length. For a nominal arm’s length of 28 inches, the visual angle is therefore about 0.0213 degrees. For reading at arm’s length, 1px thus corresponds to about 0.26 mm (1/96 inch).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But this rule can only be followed if hardware makers, operating system and browser developers all collaborate towards that goal, which is rare enough. Especially since hardware vendors are more interested in selling screens optimized for video resolutions (“1080p”, “4K”), even when it makes the whole UI awkwardly small.&lt;/p&gt;
&lt;p&gt;In theory again, browser makers should be able to change the &lt;code&gt;16px&lt;/code&gt; default font size to adapt to modern devices. But too much existing content relies on this default size not ever changing.&lt;/p&gt;
&lt;p&gt;And so, we guess; we test; we tweak.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id=&quot;fn:1&quot;&gt;
&lt;p&gt;Whether technically correct or an approximation (my own calculations show a resolution of 68dpi), the “72dpi” resolution allowed designers to easily transpose point sizes to pixel sizes. Since there are 72 typographic points in an inch, at 72dpi each pixel is exactly one point.&amp;#160;&lt;a href=&quot;#fnref1:1&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:2&quot;&gt;
&lt;p&gt;Retina displays didn’t change the “system point per inch” resolution, instead each system point was mapped to a 2×2 square of &lt;em&gt;physical pixels&lt;/em&gt;. Since the CSS &lt;code&gt;px&lt;/code&gt; unit works similarly as system points on those devices, and doubling the physical pixel resolution did not impact the size of HTML text, I skipped talking about resolutions measured in physical pixels (e.g. 320ppi) altogether.&amp;#160;&lt;a href=&quot;#fnref1:2&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:3&quot;&gt;
&lt;p&gt;A Responsive Web Design technique that lets you transition smoothly between two property values when the screen gets smaller or bigger technique. First &lt;a href=&quot;https://madebymike.com.au/writing/precise-control-responsive-typography/&quot;&gt;implemented in CSS by Mike Riethmuller&lt;/a&gt;, and &lt;a href=&quot;https://blog.typekit.com/2016/08/17/flexible-typography-with-css-locks/&quot;&gt;developed further by Tim Brown&lt;/a&gt;. See &lt;a href=&quot;https://fvsch.com/css-locks&quot;&gt;The math of CSS locks&lt;/a&gt; for details.&amp;#160;&lt;a href=&quot;#fnref1:3&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content>
  </entry>
  <entry xml:lang="fr">
    <id>urn:uuid:1538d319-7e34-5613-a949-11fdbbab9f01</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/et-tu-cliques-a-cote-du-bouton"/>
    <title>Et tu cliques cliques cliques, à côté du bouton ♫</title>
    <published>2019-12-24T00:00:00+01:00</published>
    <updated>2019-12-24T00:00:00+01:00</updated>
    <summary>Que ce soit à la souris ou sur un écran tactile, un pourcentage de nos clics tombera à côté de la cible, voire sur le bouton voisin. C’est un peu frustrant, mais n’est-ce pas une fatalité ? Regardons ça de plus près.</summary>
    <content type="html">&lt;p&gt;Que ce soit à la souris ou sur un écran tactile, un pourcentage de nos clics tombera à côté de la cible, voire sur le bouton voisin. C’est un peu frustrant, mais n’est-ce pas une fatalité ? Regardons ça de plus près.&lt;/p&gt;
&lt;p&gt;Par exemple, vous avez forcément croisé la fameuse pop-up d’inscription à une indispensable newsletter :&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/zoneclic-intro-1.jpg&quot; alt=&quot;Fig. 1 Inscris-toi !!!&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Bien planquée dans le coin supérieur droit, une petite croix : le bouton de fermeture. Alors on se dépêche, vite on clique, et rien. Quoi ? Zoomons un peu pour voir ce qu’il se passe.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/zoneclic-intro-2.jpg&quot; alt=&quot;Fig. 2 Seule l’icone est cliquable.&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Dans sa grande bonté, le/la designer a dessiné une icone de 8 pixels de côté. Puis, lors du développement, l’intégrateur ou la développeuse ne s’est pas posé de question et a utilisé une image de 8 pixels de côté également, sans réserver un espace cliquable plus grand.&lt;/p&gt;
&lt;p&gt;Et maintenant, troisième étape de notre histoire, l’utilisateur·trice clique à côté. Alors on réessaye, parce qu’on n’a toujours pas l’intention de s’inscrire à cette newsletter. En se méfiant, après ce premier échec et un début de frustration.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;À la souris, il faut ralentir considérablement et amener le pointeur bien au dessus de l’icone, et surveiller si la flèche se transforme en main ou si un autre changement visuel signale que là, c’est bon, on peut y aller. Ensuite, on clique, mais attention : le mouvement ou la vibration du clic peuvent déplacer le pointeur de quelques pixels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sur un écran tactile, on prendra son doigt le plus fin et approchera l’écran presque orthogonalement pour essayer de « cliquer » le plus précisément possible. Il est rare qu’un effet visuel vienne indiquer clairement quelle partie de la pulpe du doigt a réellement touché l’écran, et s’il y en avait un il serait de toute façon masqué par notre doigt. Alors il faudra peut-être trois, cinq ou 10 essais pour fermer cette satané pop-up.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Et ça, c’est dans dans des conditions favorables : sans handicap, sans tendinite, et sans tenter d’utiliser un site important en marchant dans la rue avec un smartphone à la main.&lt;/p&gt;
&lt;p&gt;Alors que tout ça aurait pu être beaucoup plus simple avec un bouton adapté :&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/zoneclic-intro-3.jpg&quot; alt=&quot;Fig. 3 Bouton de fermeture de 40px de côté, avec une icone un peu plus visible de 12px&quot;&gt;&lt;/figure&gt;
&lt;h2&gt;&lt;span&gt;Pourquoi c’est pas facile de cliquer un petit bouton ?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Et par opposition, pourquoi agrandir la zone cliquable d’un bouton faciliterait la vie des utilisateurs·trices ?&lt;/p&gt;
&lt;p&gt;C’est le moment « pour marcher il faut mettre un pied d’vant l’autre » de cet article, mais restez avec moi, ça va bien se passer.&lt;/p&gt;
&lt;p&gt;Première raison : pour utiliser un bouton, un lien ou tout élément interactif d’une interface, il faut déjà le trouver. Intuitivement, on comprend qu’un élément trop petit sera difficile à identifier dans une interface car peu visible.&lt;/p&gt;
&lt;p&gt;Vous connaissez l’expression « chercher une aiguille dans une botte de foin » ? À votre avis, si une botte de foin contient une aiguille, un parapluie et un lampadaire IKEA de 2 mètres, lequel de ces objets sera le plus simple à trouver en premier ? Et en deuxième ?&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/zoneclic-intro-4.jpg&quot; alt=&quot;Fig. 4 Meule de foin dont dépassent une lampe et un parapluie.&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Bien sûr la taille n’est pas le seul critère, d’autres rentrent en ligne de compte :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;La forme. Une forme conventionnelle sera identifiée plus vite et plus souvent. Par exemple : le manche recourbé d’un parapluie cane, le texte souligné d’un lien, l’aspect surélevé d’un bouton-poussoir.&lt;/li&gt;
&lt;li&gt;L’emplacement. La position dans la page (à un endroit attendu ou moins conventionnel), et la position par rapport à d’autres éléments (voir la loi de proximité dans la &lt;a href=&quot;https://fr.wikipedia.org/wiki/Psychologie_de_la_forme&quot;&gt;Psychologie de la forme&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Le contraste des éléments graphiques entre eux et avec leur environnement (&lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html&quot;&gt;&lt;cite lang=&quot;en&quot;&gt;Understanding Success Criterion 1.4.11: Non-text Contrast&lt;/cite&gt;&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ces critères participent à ce que le psychologue James J. Gibson a nommé &lt;a href=&quot;https://fr.wikipedia.org/wiki/Affordance&quot;&gt;l’affordance d’un objet&lt;/a&gt;, sujet très large et passionnant, mais que je vous propose de laisser de côté pour revenir à nos boutons.&lt;/p&gt;
&lt;p&gt;Mettons que vous ayez aperçu et identifié le bouton de fermeture de cette satanée pop-up. Deuxième étape : il faut maintenant l’activer. Pour ça, il faut l’atteindre, et ça c’est un vrai petit voyage :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Avec une souris ou un pavé tactile, il faut déplacer le pointeur entre sa position actuelle et la position de la cible.&lt;/li&gt;
&lt;li&gt;Sur un écran tactile, de smartphone par exemple, il faut déplacer un doigt vers la cible sans toucher l’écran, par exemple déplacer un pouce de sa position de repos vers le bouton.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dans les deux cas, c’est un mouvement qui comprend une phase d’accélération et une phase de décélération. Et parfois, on s’arrête trop vite ou trop tard, un peu comme en voiture ou à vélo.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/zoneclic-intro-5.jpg&quot; alt=&quot;&quot;&gt;&lt;/figure&gt;
&lt;p&gt;En ergonomie des interfaces humain-machine (IHM), on retrouve cette problématique dans la &lt;a href=&quot;https://fr.wikipedia.org/wiki/Loi_de_Fitts&quot;&gt;loi de Fitts&lt;/a&gt;. Dans les années 1950, en étudiant entre autres les cockpits d’avion de l’armée américaine, Paul Fitts estime que le temps moyen nécessaire pour atteindre physiquement une cible (par exemple un bouton ou un levier) dépend de deux paramètres : 1. la distance de la cible et 2. la taille de la cible.&lt;/p&gt;
&lt;p&gt;(Pour plus de détail, on pourra lire &lt;a href=&quot;https://ux.stackexchange.com/questions/22738/has-fitts-law-been-adapted-to-touch-screens/23258#23258&quot;&gt;cette explication très intéressante en anglais&lt;/a&gt; de la formule proposée par Fitts et son application aux interfaces informatiques.)&lt;/p&gt;
&lt;p&gt;En gros, les cibles éloignées et les petites cibles demandent plus de temps. Les petites cibles éloignées étant les plus exigeantes.&lt;/p&gt;
&lt;p&gt;Cela s’explique en partie par un phénomène connu dans l’expérience humaine et en sciences cognitives : le compromis entre la vitesse d’exécution et la précision (&lt;a href=&quot;https://www.frontiersin.org/articles/10.3389/fnins.2014.00150/full&quot;&gt;&lt;cite lang=&quot;en&quot;&gt;The speed-accuracy tradeoff: history, physiology, methodology, and behavior&lt;/cite&gt;&lt;/a&gt;). Par exemple, si je veux appuyer rapidement sur un bouton sur un écran tactile, je ne vais pas prendre le temps de regarder le mouvement de mon doigt pour obtenir une information qui me permet de corriger ou optimiser mon mouvement : je risque donc de perdre en précision.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/zoneclic-intro-6.jpg&quot; alt=&quot;&quot;&gt;&lt;/figure&gt;
&lt;p&gt;En tant que designers et développeurs·euses d’interfaces, nous pouvons faire deux suppositions :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Nos utilisateurs et utilisatrices seront souvent pressées ou distraites, et ne manipuleront pas nos interfaces délicatement et patiemment comme s’il s’agissait de fragiles nouveaux-nés.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;En cas d’« erreur » de manipulation, par exemple un clic enregistré à côté d’un bouton voire sur le bouton voisin, nos utilisateurs et utilisatrices ne comprendront pas les raisons techniques et ergonomiques précises de l’erreur, et pourront percevoir l’interface comme buguée (ça m’a emmené ailleurs que prévu, j’ai dû cliquer plusieurs fois pour que ça marche, etc.), lente (j’ai dû cliquer plusieurs fois avant que ça ne finisse par réagir, ça « lague »), et plus généralement comme frustrante voire hostile.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;En conséquence, notre boulot est de réduire autant que possible le nombre d’erreurs, et pour ça la loi de Fitts nous apprend que nous pouvons jouer sur la distance et sur la taille des éléments interactifs.&lt;/p&gt;
&lt;p&gt;Petit problème : pour la distance, c’est compliqué. Elle est rarement fixe et prévisible, nous ne connaissons pas l’état du défilement de la page, la position de départ du curseur, etc. Sur smartphone, il existe des usages courants comme l’utilisation du pouce et une position de repos vers le bas de l’écran, mais même certaines distances courtes peuvent être difficiles d’accès (car il faut tordre la main ou le poignet). Donc pragmatiquement, on peut considérer qu’on a toujours une chance importante de se retrouver dans un cas difficile.&lt;/p&gt;
&lt;p&gt;Il nous reste donc une variable majeure pour faciliter la vie des utilisatrices et utilisateurs : &lt;strong&gt;agrandissons-donc ces fichus boutons !&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Okay mais tout le monde sait ça hein, c’est la base !&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Malheureusement cette question n’est pas si connue que ça, car nous tombons tous et toutes quotidiennement sur des interfaces qui ne respectent pas ce principe. Et pas seulement sur des sites perso ou amateurs. Voici quelques exemples.&lt;/p&gt;
&lt;figure&gt;&lt;img width=&quot;360&quot; alt=&quot;Capture d’écran du site Inc.com avec &quot; src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/exemple-inc.png&quot;&gt;&lt;figcaption&gt;
    Sur Inc. (publication de Mansueto Ventures qui publie également le magazine Fast Company), les boutons de l’en-tête ont une taille cliquable minuscule (deuxième ligne, en vert). Ma proposition de correction (troisième ligne, en rose) utilise une taille minimum de 36 × 36 pixels. Idéalement il faudrait prévoir plus grand encore, et pour cela mieux répartir les boutons entre la gauche et la droite.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Mais peut-être que du côté de la presse en ligne on ne sait pas coder, alors que chez les vrais développeurs si, on sait ? Faut voir, parce que chez GitHub ce n’est pas ça non plus :&lt;/p&gt;
&lt;figure&gt;&lt;img width=&quot;360&quot; alt=&quot;Capture d’écran du menu mobile de github.com&quot; src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/exemple-github.png&quot;&gt;&lt;figcaption&gt;
    Les boutons de l’en-tête se limitent à la surface de l’icone, et cliquer à côté est excessivement aisé. La hauteur du champ de recherche pourrait être augmentée, et principaux liens du menu sont plutôt corrects sauf le lien “Marketplace” au milieu de la liste. Enfin, le texte des liens ou l’icone de profil touche le bord gauche du bouton, ce qui reste risqué : un peu de padding ne ferait pas de mal.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Peut-être que ça se passe mieux dans les entreprises spécialisées en design ? Hmm, pas toujours. On ne va pas prendre d’exemple sur le site d’Adobe, ce serait cruel. Mais si on regarde du côté d’Invision, il y a du bon et du moins bon :&lt;/p&gt;
&lt;figure&gt;&lt;img width=&quot;1106&quot; alt=&quot;Capture d’écran de la navigation principale sur le site d’Invision&quot; src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/exemple-invision.png&quot;&gt;&lt;figcaption&gt;
    Chez Invision, lorsqu’un bouton est visuellement grand, sa zone cliquable est grande également (logique…). Par contre dans l’en-tête, la plupart des liens se limitent à la surface du texte. Mention spéciale pour le petit triangle à côté de “Design Community” : il suffit de cliquer un pixel plus à droite pour cliquer dans le vide.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Un autre problème courant, qu’on peut voir dans les colonnes du pied de page, c’est l’utilisation de &lt;code&gt;line-height&lt;/code&gt; pour espacer les éléments d’un menu. Un &lt;code&gt;line-height&lt;/code&gt; important donne une bonne hauteur cliquable, mais dès que le texte passe sur deux ligne c’est la catastrophe. À votre avis, dans les cinq lignes de texte suivantes, combien y a-t-il de liens ?&lt;/p&gt;
&lt;blockquote&gt;
  Invision For&lt;br&gt;&lt;br&gt;
  Startups&lt;br&gt;&lt;br&gt;
  Pricing&lt;br&gt;&lt;br&gt;
  Students &amp;amp;&lt;br&gt;&lt;br&gt;
  Teachers
&lt;/blockquote&gt;
&lt;p&gt;Mon conseil : combinez un &lt;code&gt;line-height&lt;/code&gt; raisonnable (par exemple &lt;code&gt;line-height: 1.5&lt;/code&gt;) et du padding pour obtenir une hauteur de bouton correcte, et testez avec du texte sur plusieurs lignes. Et si ça « casse » le design, parlez-en aux designers !&lt;/p&gt;
&lt;p&gt;Autre écueil : attention à ne pas suggérer visuellement une surface cliquable, avec des couleurs de fond, des bordures, des décorations visuelles ou des effets au survol… tout en implémentant une zone cliquable plus petite. Vous invitez vos utilisateurs·trices à cliquer dans le vide !&lt;/p&gt;
&lt;figure&gt;&lt;img width=&quot;620&quot; alt=&quot;Capture d’écran de la navigation principale sur le site de Google Design&quot; src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/exemple-google-design.png&quot;&gt;&lt;figcaption&gt;
    Sur la page d’accueil de Google Design, les catégories principales montrent une bordure au survol, qui suggère une zone cliquable très large. Malheureusement, ce n’est pas le cas, et les liens ont une surface beaucoup plus réduite.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;&lt;span&gt;Les tailles de bouton recommandées pour les interfaces tactiles&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Une taille de bouton suffisante, c’est quoi au juste ? Ça va dépendre en partie du public (certaines interfaces « expertes » ont une forte densité d’information et des contrôles plus petits que la moyenne), mais aussi du support.&lt;/p&gt;
&lt;p&gt;Sur mobile, on peut suivre les recommandations d’Apple pour iOS et de Google pour Android, qui sont assez proches.&lt;/p&gt;
&lt;figure&gt;&lt;img alt=&quot;Schéma représentant une barre d’outils d’application mobile avec trois boutons larges et espacés, et un contre-exemple avec 9 boutons plus petits et serrés&quot; src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/guidelines-ios-button-size.png&quot;&gt;&lt;figcaption&gt;
    Les &lt;cite lang=&quot;en&quot;&gt;Human Interface Guidelines&lt;/cite&gt; d’iOS recommandent une zone réactive de 44 pixels de côté minimum, et déconseillent les boutons trop rapprochés. Source : &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/#general-layout-considerations&quot;&gt;General Layout Considerations&lt;/a&gt;, © Apple.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img alt=&quot;Schéma représentant deux icones-bouton et un bouton textuel. Dans les trois cas la zone cliquable dessinée en surimpression est plus large que l’icone ou que la bordure du bouton.&quot; src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/guidelines-material-touch-button-size.png&quot;&gt;&lt;figcaption&gt;
    Les recommandations &lt;cite lang=&quot;en&quot;&gt;Material Foundation&lt;/cite&gt; de Google spécifient des zones réactives de 48 pixels de côté minimum, avec au moins 8 pixels entre les boutons. Source : &lt;a href=&quot;https://material.io/design/layout/spacing-methods.html#touch-targets&quot;&gt;Material Design: Spacing methods&lt;/a&gt;, © Google.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Un exemple intéressant dans cette dernière illustration : en (3), le bouton utilise un style standard de Material Design avec une hauteur de 36 pixels. Mais la zone tactile doit toujours faire au moins 48 pixels de haut, alors que faire ?&lt;/p&gt;
&lt;p&gt;Sur certaines plateformes, on peut rendre la zone tactile d’un élément plus grande que l’élément lui-même. Par exemple, avec React Native on pourra utiliser la propriété &lt;code&gt;hitSlop&lt;/code&gt; de certains composants.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&amp;lt;Button hitSlop={{ top: 6, bottom: 6, left: 0, right: 0 }}&amp;gt;&amp;lt;/Button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sur le Web, c’est plus compliqué. Deux solutions :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Utiliser un élément &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; ou &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; transparent avec du padding, et placer un &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; stylé comme un bouton à l’intérieur.&lt;/li&gt;
&lt;li&gt;Utiliser un pseudo-élément positionné en absolu pour élargir la zone cliquable.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.my-button {
  position: relative;
}

.my-button::after {
  content: &quot;&quot;;
  position: absolute;
  top: -6px;
  bottom: -6px;
  left: 0;
  right: 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apparté sur les unités de mesure : iOS utilise des « points système », notés &lt;code&gt;pt&lt;/code&gt; ; Android des “device points” notés &lt;code&gt;dp&lt;/code&gt; ; et sur le Web on travaille en « pixels CSS », notés &lt;code&gt;px&lt;/code&gt;. Ces trois unités sont équivalentes dans la plupart des cas, et peuvent correspondre à des quantités variables de pixels physiques (suivant la densité de l’écran et la configuration du système d’exploitation).&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Les tailles de bouton pour les interfaces à la souris&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Sur des interfaces qui ont été conçues principalement pour la souris, les boutons sont généralement plus petits. Par exemple sur macOS, les boutons des barres d’outils font 22 pixels de haut, et les &lt;cite lang=&quot;en&quot;&gt;Human Interface Guidelines&lt;/a&gt; pour macOS &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/macos/buttons/push-buttons/&quot;&gt;ne recommandent pas de taille minimum&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img alt=&quot;Schéma représentant deux designs comportant des icones d’environ 20 pixels de côté, avec dessiné en surimpression une zone cliquable de 24 pixels de côté.&quot; src=&quot;https://fvsch.com/articles/et-tu-cliques-a-cote-du-bouton/guidelines-material-click-button-size.png&quot;&gt;&lt;figcaption&gt;
    Material Design &lt;del&gt;recommande&lt;/del&gt; &lt;ins&gt;recommandait encore récemment&lt;/ins&gt; une taille minimum de 24 pixels pour les boutons dans les interfaces à la souris. On notera que dans ces deux exemples on pourrait facilement agrandir les zones cliquables sans modifier le design visuel. Source : &lt;a href=&quot;https://material.io/design/layout/spacing-methods.html#touch-targets&quot;&gt;Material Design: Spacing methods&lt;/a&gt;, © Google.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Personnellement, je trouve la recommandation de Material Design un peu faible, et préfère utiliser des zones cliquables de 30, 40 pixels ou plus. De plus, il devient compliqué de savoir si une interface sera utilisée à la souris, sur un écran tactile (en particulier avec les grandes tablettes et les ordinateurs portables tactiles), pour ne parler que des cas les plus courants ! Donc la pertinence de recommandations pour les interfaces à la souris est, à mon sens, de plus en plus contestable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mise à jour :&lt;/strong&gt; les designers de Google semblent d’accord, et viennent de mettre à jour la documentation de Material Design pile au moment où nous publions cet article ! La nouvelle recommandation, à la page « Accessibilité », suggère &lt;a href=&quot;https://material.io/design/usability/accessibility.html#layout-typography&quot;&gt;des zones cliquables de 44 pixels de côté minimum&lt;/a&gt; pour les interfaces à la souris ou au stylet.&lt;/p&gt;
&lt;p&gt;Même à la souris et sans handicap ou difficulté motrice, une cible de 24 pixels peut être difficile à atteindre, par exemple si on est un peu pressé.&lt;/p&gt;
&lt;p&gt;Pour l’illustrer, voici un petit jeu de précision. Le but de ce jeu est de cliquer une vingtaine de cibles carrées de 24 pixels, toutes affichées exactement une seconde. Sachant que le temps de réaction humain moyen est de 250 millisecondes, cela laisse 750 millisecondes pour déplacer le pointeur et cliquer.&lt;/p&gt;
&lt;p&gt;Je vous propose une première partie en gardant les paramètres par défaut, et on se retrouve juste après. Prêt·e ?&lt;/p&gt;
&lt;div id=&quot;click-precision-game&quot;&gt;&lt;/div&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://click-precision-game.netlify.com/build/bundle.css&quot;&gt;
&lt;script defer src=&quot;https://click-precision-game.netlify.com/build/bundle.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;Alors, comment ça s’est passé ? Si vous avez obtenu un score de moins de 90, comment pouvez-vous l’améliorer (donc diminuer le taux d’erreurs) ? Vous avez trois leviers :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Augmenter la taille de la cible. Ne serait-ce qu’en montant à 32 pixels, la différence devrait être sensible.&lt;/li&gt;
&lt;li&gt;Diminuer la taille du conteneur, et donc les distances à parcourir.&lt;/li&gt;
&lt;li&gt;Augmenter le temps de jeu, pour être moins pressé·e.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;N’hésitez pas à faire quelques essais. Et si jamais vous en tiriez quelques enseignements pour les interfaces que vous designez ou développez, ce serait bien sûr parfaitement fortuit. Bonnes parties, et joyeux Noël !&lt;/p&gt;
&lt;p&gt;P.S.&amp;nbsp;: le code du jeu &lt;a href=&quot;https://github.com/fvsch/click-precision-game&quot;&gt;est open-source&lt;/a&gt; et on peut le retrouver &lt;a href=&quot;https://click-precision-game.netlify.com/&quot;&gt;sur cette page&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="fr">
    <id>urn:uuid:0f96bcf5-5174-5dd8-9ab8-eed88a3bd7d3</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/interview-alsacreations"/>
    <title>Interview sur Alsacréations</title>
    <published>2019-12-03T00:00:00+01:00</published>
    <updated>2019-12-03T00:00:00+01:00</updated>
    <summary>J’ai donné une petite interview pour le site alsacreations.com, un site communautaire autour des technologies web.</summary>
    <content type="html">&lt;p&gt;J’ai donné une petite interview pour le site &lt;a href=&quot;https://www.alsacreations.com&quot;&gt;alsacreations.com&lt;/a&gt;, un site communautaire autour des technologies web créé par &lt;a href=&quot;https://goetter.fr/&quot;&gt;Raphaël Goetter&lt;/a&gt; et &lt;a href=&quot;https://www.blup.fr/&quot;&gt;Rodolphe Rimelé&lt;/a&gt; et auquel j’ai beaucoup participé il y a une dizaine d’années.&lt;/p&gt;
&lt;p&gt;Il y a deux questions qui m’ont particulièrement intéressé, et je me permets de recopier mes réponses ici pour mémoire.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Alsacréations: Comment envisages-tu le futur de CSS ou de l’intégration en général ? (on pense notamment à CSS-in-JS)&lt;/h3&gt;
&lt;p&gt;Pour les CSS dans le JavaScript, c’est plus un symptôme de ce qu’il se passe depuis quelques années. Je n’ai rien contre ces outils, moi ça ne me gêne pas d’utiliser &lt;a href=&quot;https://www.styled-components.com/&quot;&gt;styled-components&lt;/a&gt; ou &lt;a href=&quot;https://css-blocks.com/&quot;&gt;CSS Blocks&lt;/a&gt;, pas plus que Sass ou d’autres. Mais ça fait partie d’une tendance générale à empiler des couches techniques les unes par dessus les autres, qui correspond mieux aux schémas mentaux des développeurs·euses «classiques» que des intégrateurs·trices.&lt;/p&gt;
&lt;p&gt;On se retrouve avec des projets où pour participer sur les styles ou l’accessibilité il faut pouvoir utiliser Git mais aussi Docker, maitriser JavaScript et React, configurer webpack, et écrire les styles dans une syntaxe JavaScript ou en pseudo-CSS. C’est quand même beaucoup d’obstacles pour des gens qui connaissent peu ces outils mais ont une expertise sur le HTML et l’accessibilité, le CSS, les navigateurs web, etc. Je parle régulièrement avec des gens qui faisaient de l’intégration web principalement ou en plus d’autres missions, et qui se sont rabattus sur du design ou de la gestion de projet car l’environnement technique devenait trop «pour et par les devs».&lt;/p&gt;
&lt;p&gt;Je ne sais pas comment ça va évoluer, mais en tout cas je m’intéresse à tout ce qui tire un peu dans le sens inverse : permettre à des designers et/ou intégrateurs·trices de produire des choses sans être bloqué par plein de couches techniques. Peut-être une simplification des frameworks JS pour que le JS s’efface autant que possible, comme dans &lt;a href=&quot;https://svelte.dev/&quot;&gt;Svelte&lt;/a&gt; ? Est-ce qu’il faut forcément coder, ou bien est-ce que la manipulation directe comme dans &lt;a href=&quot;https://webflow.com/&quot;&gt;Webflow&lt;/a&gt; ne fonctionnerait pas mieux au final ? Est-ce qu’on peut mélanger code et manipulation directe, pour bosser ensemble avec plusieurs profils ?&lt;/p&gt;
&lt;h3&gt;Alsacréations: Si tu avais à former un·e débutant·e en HTML et CSS, sur quels thèmes insisterais-tu particulièrement ?&lt;/h3&gt;
&lt;p&gt;Pour moi le plus important serait de rattacher HTML et CSS à un objectif plus large. Quand j’ai fait de la formation il y a 10 ans, j’avais une approche très linéaire, HTML et sémantique d’abord puis CSS ensuite, etc. Avec le recul je trouve ça un peu formel et pas très motivant.&lt;/p&gt;
&lt;p&gt;Avec des débutant·e·s en développement web voire en développement tout court, j’insisterais sur l’idée de créer quelque chose. Dans ce cadre, HTML et CSS ne sont que deux outils sur cinq ou 6. On peut bosser sur plein de sujets : de l’animation, des ébauches d’application multimédia (par exemple une application pour générer des sons ou de la musique), une galerie de photos, mettre en page un poème ou une tablature de guitare, etc. L’important serait de publier le résultat (par exemple sur GitHub Pages, Netlify, ou en codant directement sur Glitch) et le partager à quelques personnes : voilà ce que j’ai fait.&lt;/p&gt;
&lt;p&gt;Avec un public de développeurs·euses qui a déjà une formation initiale en développement “backend” mais très peu d’expérience en UI, le risque principal c’est le découragement car CSS est un domaine à part. Les compétences déjà acquises en Java, PHP, Python ou SQL ne se transfèrent pas vraiment à l’apprentissage de CSS (&lt;a href=&quot;https://www.youtube.com/watch?v=aHUtMbJw8iA&quot;&gt;Miriam Suzanne, Why Is CSS So Weird?&lt;/a&gt;). Et si on n’a pas anticipé ce problème, on peut se retrouver en pleine dissonance cognitive (&lt;a href=&quot;https://vimeo.com/channels/beyondtellerrand/373397621&quot;&gt;Natalya Shelburne, CSS at the Intersection&lt;/a&gt;) et abandonner.&lt;/p&gt;
&lt;p&gt;Du coup, s’il faut se familiariser avec un nouveau domaine et une culture de design et d’ergonomie, pourquoi ne pas enseigner directement ces concepts de design, en utilisant CSS comme un exercice d’application ? Et enseigner directement des bases d’accessibilité, en utilisant HTML pour la mise en application ? En somme, viser un résultat plutôt qu’enseigner un langage.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:3c3acfaf-912e-5382-af1c-17719a902650</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/static-site-generators"/>
    <title>Static site generators</title>
    <published>2018-10-07T00:00:00+02:00</published>
    <updated>2018-10-07T00:00:00+02:00</updated>
    <summary>I’ve been looking for a decent static site generator to build a simple, 10-page-or-so documentation site, and I’m failing. Here are some notes on my journey, to serve as a warning sign to future travellers, and thoughts on what static site generators could do better.</summary>
    <content type="html">&lt;p&gt;I’ve been looking for a decent static site generator to build a simple, 10-page-or-so documentation site, and I’m failing. Here are some notes on my journey, to serve as a warning sign to future travellers, and thoughts on what static site generators could do better.&lt;/p&gt;
&lt;nav id=&quot;toc&quot;&gt;&lt;/nav&gt;
&lt;h2&gt;&lt;span&gt;What is a static site generator, really?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;I’d like to try and describe static site generators as if we hadn’t encountered this class of software yet. Please do not skip this section, even if you already (think you) know the answer.&lt;/p&gt;
&lt;p&gt;Static site generators (SSG) are a class of software programs that help with creating websites. They’re often written by developers — professionals or hobbyists — to power their own small website or blog, and as such they tend to have a limited feature set.&lt;/p&gt;
&lt;p&gt;SSG are close parents to &lt;em&gt;scripts&lt;/em&gt;: short programs (a few hundred lines of code), which can be used from a command prompt to achieve specific tasks. They’re often built as a hobby by one developer, sometimes getting help from a few others months or years later. They almost never offer a graphical user interface.&lt;/p&gt;
&lt;p&gt;One core principle of those scripts is that they work with a source collection of plain text files (aka “content”), process those files to transform them to the HTML format, and wrap them in more HTML. This is often called a “build” step. Before running the build command, you would have a file tree that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;My Cool Website
└── content
    ├── 2018-09-07-cool-blog-post.txt
    └── 2018-10-01-other-blog-post.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And after typing some text to run a command in a “terminal” or “command prompt” application, you would get something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;My Cool Website
├── content
│   ├── 2018-09-07-cool-blog-post.txt
│   └── 2018-10-01-other-blog-post.txt
└── output
    ├── cool-blog-post.html
    ├── index.html
    ├── other-blog-post.html
    └── rss.xml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that setup, you get a simple blog with one HTML page for each blog post, an index (or home) page that lists published posts, and a RSS feed. You can write your own styles if you know CSS, or — with some SSG, but not all — use a “theme” or an example project as a starting point.&lt;/p&gt;
&lt;p&gt;Users of static site generators do this work on their own computer, and after the “output” folder has been generated they are expected to know how to put those files online to publish them. This can require software such as a FTP client, or more complex setups involving one or two additional command line tools.&lt;/p&gt;
&lt;p&gt;There are hundreds of static site generators, with many &lt;a href=&quot;https://www.staticgen.com/&quot;&gt;listed on StaticGen.com&lt;/a&gt;. Some of the most popular ones are &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;, &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt; and &lt;a href=&quot;https://hexo.io/&quot;&gt;Hexo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When it comes to editing features, there are not many to speak of. Users are expected to know what formats like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/HTML_basics&quot;&gt;HTML&lt;/a&gt; and &lt;a href=&quot;https://commonmark.org/help/&quot;&gt;Markdown&lt;/a&gt; are, how to prepare images themselves (in Photoshop, Gimp or similar software), how to insert images inside a page’s content by using code that may look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-md&quot;&gt;![My cat at 4 months](./sniffles-4months.jpg)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or even something more complex:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-md&quot;&gt;![My cat at 4 months]({{&amp;lt; imgproc sniffles-4months.jpg Resize &quot;600x&quot; /&amp;gt;}})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point we must recognize that static site generators are tools made by developers &lt;em&gt;for developers&lt;/em&gt;. They require a ton of previous knowledge: HTML, Markdown, command line interfaces, FTP or some other way to publish HTML files online, and of course you would need some kind of web hosting to put those files. Users will also need to dive into technical documentation for their tool of choice when they want to customize their site’s pages, work in “templates”, learn a templating language, and probably write some CSS.&lt;/p&gt;
&lt;p&gt;The most popular static site generator, Jekyll, may have the best documentation pages of the lot. And yet, it still puts a bunch of technical language front-and-center, on the project’s home page:&lt;/p&gt;
&lt;figure&gt;&lt;img class=&quot;border&quot; src=&quot;https://fvsch.com/articles/static-site-generators/jekyll-quick-start.png&quot; width=&quot;560&quot; alt=&quot;A series of 4 terminal commands, starting with: gem install bundler jekyll…&quot;&gt;&lt;figcaption&gt;Jekyll’s core message, “Get up and running in seconds”&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This is developer-talk, &lt;em&gt;because static site generators are tools made by developers for themselves and their peers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;On the other hand, “content management” software such as WordPress, Drupal or &lt;a href=&quot;https://getkirby.com/&quot;&gt;my personal favorite, Kirby&lt;/a&gt;, target a dual audience: content editors (who write and manage content), and developers (who set up the CMS and implement the site’s design and features).&lt;/p&gt;
&lt;p&gt;I’m making this point because, if we own up to who our software’s audience is, then we can consider what we can do for that audience and hopefully design less terrible software.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;A developer’s idea of elegance&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;I sometimes talk with developers who praise static site generators as &lt;em&gt;simple&lt;/em&gt;, which is hogwash. I’ve seen a few projects where content editors were handed off a site based on a SSG (especially in startups where devs may dominate the conversation), and could not manage any change without a lot of training and assistance. Nothing simple about that.&lt;/p&gt;
&lt;p&gt;What those developers probably mean is that they find static site generators &lt;em&gt;elegant&lt;/em&gt;. A programmer thinks that a solution is elegant when it uses their current knowledge to achieve something with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;few steps,&lt;/li&gt;
&lt;li&gt;no repetition, and&lt;/li&gt;
&lt;li&gt;not too much indirection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Static site generators are an elegant solution for developers because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It rests on our previous knowledge of: file and code formats, the command line, the plain-files-and-scripts philosophy, &lt;code&gt;git&lt;/code&gt; and &lt;code&gt;ssh&lt;/code&gt; or &lt;code&gt;scp&lt;/code&gt; and other deployment strategies, etc.&lt;/li&gt;
&lt;li&gt;Using this base knowledge, it offers solutions to boring, repetitive tasks. For example, Markdown syntax lets us avoid repeating &lt;code&gt;&amp;lt;p&amp;gt;…&amp;lt;/p&amp;gt;&lt;/code&gt; tags in our content; templates let us avoid tasks like manually repeating HTML structure and updating menus and content lists.&lt;/li&gt;
&lt;li&gt;Finally, using the filesystem to store content lets us work in a single place (avoiding database software and languages, which would be overkill for limited datasets).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’m all for elegant solutions that fit my mindset (aka just “elegant solutions”), and I’ve used static site generators in the past, including Pelican, Hyde, Jekyll and Metalsmith.&lt;/p&gt;
&lt;p&gt;Here’s the thing, though: it’s been a terrible experience. It turns out that &lt;strong&gt;static site generators are terrible at handling content&lt;/strong&gt;. Which is too bad, because that’s one of their very few features to begin with.&lt;/p&gt;
&lt;p&gt;Static site generators are elegant on principle, but are not designed to deal with content more complex than a handful of pages and a list of blog posts. And I’m not talking about the speed of building hundreds of pages or more (Jekyll is notoriously slow, Hugo is fast), but the sheer ability to &lt;em&gt;use content however you want&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Static site generators are bad at content&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Let’s back up a bit. Most static site generators were built — or at least are advertised — as alternatives to database-driven CMS software. We’ve already shown that they may only be a valid alternative for developers and technical users with a lot of prior knowledge. But they are missing another core capability of CMS software.&lt;/p&gt;
&lt;p&gt;In your average PHP or Python CMS, as in many Web frameworks, querying content from a relational database is always possible (and more or less straightforward). Linked objects and/or hierarchical content structures are built in features. Building home pages, landing pages, lists, modular pages, product pages with many sections, etc., out of content hierarchies or relations is standard practice; CMS software that was too limited to do that either evolved or was replaced by their competition.&lt;/p&gt;
&lt;p&gt;When I’ve used static site generators in the past ten years, there were a few pain points like lacking documentation and strange and incompatible conventions. But the nail in the coffin was always that it’s either impossible or way too hard to &lt;strong&gt;build a single page from several pieces of content&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Recently, I’ve looked at static site generators again, because I wanted to convert Kaliop’s &lt;a href=&quot;https://kaliop.github.io/frontend-style-guide/2.0/&quot;&gt;Frontend Style Guide&lt;/a&gt;, originally built with Kirby CMS, to something that could be built to static HTML in fewer steps &lt;em&gt;by developers&lt;/em&gt; (e.g. &lt;code&gt;npm install &amp;amp;&amp;amp; npm run build&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This site is made of a handful of long “chapter” pages, with a top-level table of contents which lists all chapters and their sections:&lt;/p&gt;
&lt;figure&gt;&lt;img class=&quot;border&quot; alt=&quot;Screenshot showing a full table of contents with 3 levels of titles&quot; src=&quot;https://fvsch.com/articles/static-site-generators/style-guide.png&quot; srcset=&quot;https://fvsch.com/articles/static-site-generators/style-guide.png 1x,https://fvsch.com/articles/static-site-generators/style-guide@2x.png 2x&quot;&gt;&lt;/figure&gt;
&lt;p&gt;For easier &lt;em&gt;content management&lt;/em&gt; and reordering of sections, I had broken each chapter into different section files, so that the index page is generated from three levels of content. Looking at the “General Rules” branch only, the content hierarchy looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;2.0
└─ general
   ├─ 1-readme
   ├─ 2-editorconfig
   ├─ 3-dry
   └─ 4-clean&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When generating the full table of contents, I expect to be able to write (in pseudo-code):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;for chapter in page.children:
    print chapter.title
    for section in chapter.children:
        print section.title&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similarly, when generating a chapter page, I expect that I can easily print the full chapter content:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;for section in page.children:
    print section.title
    print section.content&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Actual templates would be a bit more complex, since we need to wrap this content in HTML tags, and maybe pass it to template partials or components. But retrieving content from a hierarchy of local files should be straightforward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I haven’t found a single static site generator that handles this use case gracefully.&lt;/strong&gt; Instead, after reviewing the documentation of three dozens tools (listed on &lt;a href=&quot;https://www.staticgen.com/&quot;&gt;StaticGen&lt;/a&gt;), and actively trying to build my use case with a handful of them, I’ve seen a range of limitations and awkward workarounds:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sometimes it’s just not possible to work with content hierarchies and partial content. (Most JavaScript static site generators fall into that category.)&lt;/li&gt;
&lt;li&gt;Or you have to write some configuration code that populates “Collections” ahead of build time. (Too indirect, and breaks if you change your information architecture a bit. We need something a bit closer to direct manipulation!)&lt;/li&gt;
&lt;li&gt;Finally, a short list of tools allow listing a page’s children or “resources”, but have bugs when dealing with more than one level (&lt;a href=&quot;https://www.getgutenberg.io/&quot;&gt;Gutenberg&lt;/a&gt;) or impose arbitrary restrictions on what page “types” can list different types of content (&lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you &lt;em&gt;can&lt;/em&gt; list content from children, grandchildren or other locations, it’s often not possible to &lt;em&gt;process&lt;/em&gt; this content as Markdown or access its metadata.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;A few words of warning about Hugo&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt; static site generator, praised by many for its speed, is more capable than most. There are surely many good things to say about Hugo, but in the spirit of warning folks about the terribleness of software: I wouldn’t recommend it for anyone who wants a smooth learning experience.&lt;/p&gt;
&lt;p&gt;For one thing, it requires learning about way too many specific concepts: Sections, Lists, Taxonomies, Page Bundles, Leaf Bundles, Template lookup order, and the perfectly unintuitive differences between a &lt;code&gt;index.md&lt;/code&gt; page and a &lt;code&gt;_index.md&lt;/code&gt; page. (Or are those Sections, or Bundles? Is one a Section and the other a Page? Are Sections and Bundles “pages” too? I have no idea.)&lt;/p&gt;
&lt;p&gt;I particularly dislike the way Hugo &lt;a href=&quot;https://gohugo.io/templates/lookup-order/&quot;&gt;matches a page to a dozen possible template files&lt;/a&gt; or more — taking a page from Drupal’s book of idiosyncratic awfulness. Now, I understand that this is meant to make it possible to use different “themes”, and at worst your pages will still render with the theme’s &lt;code&gt;layouts/_default/list.html&lt;/code&gt; or &lt;code&gt;layouts/_default/single.html&lt;/code&gt; templates. But if you want to write your own HTML and CSS, the template lookup stuff is a nuisance (much like it is in the Drupal world when you want any kind of control over the HTML output).&lt;/p&gt;
&lt;p&gt;On top of the complexity of Hugo’s design, its documentation is often subpar. For instance, the &lt;a href=&quot;https://gohugo.io/content-management/page-resources/&quot;&gt;“Content Management &amp;gt; Page Resources” page&lt;/a&gt; does not explain much about, well, content, or content management. I expected a description showing content examples (some markdown or a file structure maybe), then information about how to use it in templates and other contexts. Instead, it’s a kind of API documentation listing “Properties” and “Methods”.&lt;/p&gt;
&lt;p&gt;The second issue is that while Hugo builds pages &lt;em&gt;fast&lt;/em&gt;, it often &lt;em&gt;builds the wrong thing fast&lt;/em&gt;. For instance, if I rename a &lt;code&gt;index.md&lt;/code&gt; file to &lt;code&gt;_index.md&lt;/code&gt;, I need to run the &lt;code&gt;hugo&lt;/code&gt; command two or three times to get it to render with the correct template. Using &lt;code&gt;hugo server&lt;/code&gt; seems even less reliable.&lt;/p&gt;
&lt;p&gt;Hugo is also unhelpful when a source Markdown file doesn’t render anything (there is no log or warning) or outputs a cryptic &lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&lt;/code&gt;. This doesn’t help the learning curve.&lt;/p&gt;
&lt;p&gt;That being said, I heard that Hugo is an interesting choice if you’re building hundreds or thousands of pages (because it builds these pages really quickly), in which case it might be worth spending a couple weeks learning to use it.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;What could we do differently?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;There are thousands of static site generators out there (counting a bunch of scripts with 5 stars on GitHub) because thousands of developers looked at existing solutions and said “eh, I’ll just build my own”.&lt;/p&gt;
&lt;p&gt;As a thought experiment, here are the core topics I would look at if I wanted to build my own tool from scratch.&lt;/p&gt;
&lt;h3&gt;1. Content naming conventions&lt;/h3&gt;
&lt;p&gt;Conventions for naming content files are necessary to allow users to create content files quickly and get predictable results. Those conventions must be short, clearly defined, clearly explained (you need great docs here), and intuitive (when possible, follow existing conventions rather than roll your own).&lt;/p&gt;
&lt;p&gt;Major issues to solve regarding source content:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What will translate to a web page?&lt;/li&gt;
&lt;li&gt;What will be ignored?&lt;/li&gt;
&lt;li&gt;How may users control the transformations that are applied to content (processing of Markdown or other formats, templates, image transformations)? Should this be defined in the content or elsewhere?&lt;/li&gt;
&lt;li&gt;How do you generate alternative views for the same page, e.g. a HTML page and a RSS or Atom feed?&lt;/li&gt;
&lt;li&gt;How can you generate category and tag pages? More generally, how do you generate any limited set of pages based on querying information from other content, without having to manually create a new (probably empty) content file?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of those concerns can be addressed in templates or in configuration or other code, but there’s value in using elegant conventions to let users directly manipulate or tweak content to get a desired result.&lt;/p&gt;
&lt;h3&gt;2. Give feedback on content-to-output mapping&lt;/h3&gt;
&lt;p&gt;The generator should provide, by default, feedback on which files are picked up, which are ignored, and why. When a file is picked up and processed in some way, it should say so too.&lt;/p&gt;
&lt;p&gt;To surface this information efficiently, the generator may need to come with a GUI (a web UI could work). I would be looking at tools like &lt;a href=&quot;https://fractal.build/&quot;&gt;Fractal&lt;/a&gt; for inspiration.&lt;/p&gt;
&lt;h3&gt;3. More code should happen in templates&lt;/h3&gt;
&lt;p&gt;Most generators use restrictive template engines, and feed a restricted context (aka a set of data and functions) to those templates. This seems to come from a misguided need to keep templates “simple”, “logic-less”, and other kinds of baloney. Look, in a big MVC application built by people with different roles and technical knowledge, it might be useful to have your very responsible backend developers write controllers and let designers or frontend developers write HTML in a sandbox. Well, that’s a questionable approach too.&lt;/p&gt;
&lt;p&gt;Anyway, as we said we’re making a tool for technically-minded people, and should give them power to actually do stuff. And since we’re talking about websites built ahead-of-time, the risks of logic in templates don’t apply as much.&lt;/p&gt;
&lt;p&gt;Another consequence of restricted templates is that SSG documentation will then tell you to work in a third place: not in content, not in templates, but in “config code”. This is a problem because A) it’s probably one place too many and B) it’s often unclear at what time this config code is running or how often: once for every page generation or template run, or just once at the start of a build?&lt;/p&gt;
&lt;p&gt;So, do more work in templates. We’re making somewhat simple websites here, it’s going to be okay. It’s cool if you can refactor your code to avoid duplication or separate concerns or whatever, but this shouldn’t be a requirement; users will only jump through hoops if they’re working on a big enough project to justify it.&lt;/p&gt;
&lt;h3&gt;4. A full content API&lt;/h3&gt;
&lt;p&gt;There should be a full content traversal API, which could be modelled after &lt;a href=&quot;https://getkirby.com/docs/templates/api&quot;&gt;Kirby’s API&lt;/a&gt; (itself inspired by jQuery). Other sources of inspiration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Globbing, e.g. &lt;code&gt;find(&apos;posts/*.md&apos;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;DOM traversal (there’s no reason why your content tree cannot be a tree of nodes).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One concern with offering a full content API in templates is that if you run a template for a thousand pages, and do the same work (including querying the filesystem) every time, you’re going to have performance issues. I have a few possible optimizations in mind (fragmenting content queries and memoization), but I’ll admit that as a UI developer this is not my forte.&lt;/p&gt;
&lt;p&gt;On top of traversing and retrieving content, templates should be able to transform formats (Markdown to HTML, parsing JSON and maybe YAML) and process images.&lt;/p&gt;
&lt;h3&gt;5. Markdown plus front-matter is too limiting&lt;/h3&gt;
&lt;p&gt;Originating in simple blog engines, static site generators treat pages as a single content chunk (often in Markdown) with some metadata sprinkled on top. If you need several long chunks of content (say, a product short description, long description, technical specs, and a list of vendors), you’re out of luck.&lt;/p&gt;
&lt;p&gt;Theoretically, you can put any kind of text content in YAML “front-matter”, but editing that content without breaking the YAML syntax is a pain.&lt;/p&gt;
&lt;p&gt;One workaround is using separate files in the same folder or in subfolder, than use the content API to retrieve them. Each such “resource” can have a body and metadata.&lt;/p&gt;
&lt;p&gt;Kirby does things a bit differently (though it’s always possible to use a page’s files or child pages too), using a custom format with arbitrary fields:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Title: My Title
----
Date: 2018-12-25
----
Desc: Short description for list views and SEO.
----
Body:
## A field contains arbitrary text
So we can use a field value and parse it as Markdown or whatever.
…
----
Notes: …
----
References: …&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other sources of inspiration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue single-file components, and, well, XML&lt;/li&gt;
&lt;li&gt;Inside the page’s body: shortcodes (WordPress, Hugo), Web Components, Vue components (Vuepress)&lt;/li&gt;
&lt;li&gt;Separators using HTML comment syntax, like &lt;code&gt;&amp;lt;!--more--&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m not sure what would be the “best” solution, but it should be possible to unlock more power without completely breaking with convention.&lt;/p&gt;
&lt;h3&gt;6. No theming system&lt;/h3&gt;
&lt;p&gt;A theming infrastructure imposes a lot of technical restrictions and indirection, such as template lookup orders (see: Hugo), having to specify a handful of default template names (Hugo, again), and having to specify strange content conventions for mapping content to those default template names (still Hugo). Frankly, if a user really wants a template inheritance and theme inheritance mechanism, they’ll probably bite the bullet and work with WordPress or Drupal.&lt;/p&gt;
&lt;p&gt;In the lightweight CMS world, having a theming system is the main reason why &lt;a href=&quot;https://getgrav.org/&quot;&gt;Grav&lt;/a&gt; is less straightforward than &lt;a href=&quot;https://getkirby.com/&quot;&gt;Kirby&lt;/a&gt; (which inspired it): themes and plugins rely on metadata in pages, so you have to put detailed config-as-metadata in every page to enable and configure theme and plugin features, which often feels like randomly pressing buttons and hoping to get a result.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:63e3255e-a704-591d-8822-1049013b3ea9</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/styling-buttons"/>
    <title>Styling buttons, the right way</title>
    <published>2018-05-09T00:00:00+02:00</published>
    <updated>2018-05-09T00:00:00+02:00</updated>
    <summary>Learn how to create an accessible button style which can be used with the correct semantic element: &lt;a&gt; or &lt;button&gt;.</summary>
    <content type="html">&lt;p&gt;If you’re building a website or a web app, you probably have buttons.
And maybe links that look like buttons?
Anyway, it’s important to get them right.&lt;/p&gt;
&lt;p&gt;In this tutorial we’ll create basic styles for &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements, and a custom &lt;code&gt;.btn&lt;/code&gt; CSS component.
You will find a demo page for each step of the process.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Demo&lt;/th&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://fvsch.com/articles/styling-buttons/step1-reset.html&quot;&gt;Step&amp;nbsp;1&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;#reset&quot;&gt;Resetting &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; styles&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://fvsch.com/articles/styling-buttons/step2-basic.html&quot;&gt;Step&amp;nbsp;2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;#component&quot;&gt;Writing a “button” CSS component&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://fvsch.com/articles/styling-buttons/step3-states.html&quot;&gt;Step&amp;nbsp;3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;#states&quot;&gt;Styling hover, focus and active states&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://fvsch.com/articles/styling-buttons/step4-final.html&quot;&gt;Step&amp;nbsp;4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;#sticky-focus&quot;&gt;Dealing with sticky focus styles&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;reset&quot;&gt;Resetting &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; styles&lt;/h2&gt;
&lt;p&gt;As a rule, 99.9% of the clickable things in your website or app should be either &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements.
If you’re not sure what element to use in a given situation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If it goes to a different URL or changes most of the page’s content, use a link (&lt;code&gt;&amp;lt;a href=&quot;some_url&quot;&amp;gt;…&amp;lt;/a&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Otherwise, use a generic button (&lt;code&gt;&amp;lt;button type=&quot;button&quot;&amp;gt;…&amp;lt;/button&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Using the correct element has a few advantages:
it’s SEO-friendly (especially for links!),
it works well with keyboard navigation,
and it improves accessibility for all users.&lt;/p&gt;
&lt;p&gt;In spite of this, developers rarely use the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element.
All over the Web, we can see a lot of buttons that trigger JavaScript actions,
and on closer inspection it turns out they’re coded with &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why is the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element so underused?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Knowledge:&lt;/strong&gt; many developers don’t know about it
(learning &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element&quot;&gt;HTML’s 100+ elements&lt;/a&gt; takes a little while).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Styling:&lt;/strong&gt; &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; comes with complex default styles,
which can make it hard to achieve a custom look.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thankfully, the styling part can be fixed!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/**
 * Reset button styles.
 * It takes a bit of work to achieve a neutral look.
 */
button {
  padding: 0;
  border: none;
  font: inherit;
  color: inherit;
  background-color: transparent;
  /* show a hand cursor on hover; some argue that we
  should keep the default arrow cursor for buttons */
  cursor: pointer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We end up with buttons that look like regular text.&lt;/p&gt;
&lt;figure class=&quot;frame&quot;&gt;&lt;iframe class=&quot;autoheight&quot; src=&quot;https://fvsch.com/articles/styling-buttons/step1-reset.html&quot; width=&quot;100%&quot; height=&quot;70&quot;&gt;&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;The downside of this approach is that now we have to style &lt;em&gt;all&lt;/em&gt; our buttons,
or users won’t recognize them (see: &lt;a href=&quot;https://en.wikipedia.org/wiki/Affordance&quot;&gt;affordance&lt;/a&gt;).
Another option is to use this style as a mixin (with Sass or another preprocessor) and apply it selectively:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@mixin button-reset {
  padding: 0;
  border: none;
  font: inherit;
  color: inherit;
  background-color: transparent;
  cursor: pointer;
}

.my-custom-button {
  @include button-reset;
  padding: 10px;
  background-color: skyblue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button type=&quot;button&quot;&amp;gt;
  I use default browser styles
&amp;lt;/button&amp;gt;

&amp;lt;button type=&quot;button&quot; class=&quot;my-custom-button&quot;&amp;gt;
  And I’m using custom styles instead
&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;component&quot;&gt;Writing a “button” CSS component&lt;/h2&gt;
&lt;p&gt;Now that we’ve removed default styles, let’s define our own button styles.
This is what we’d like to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a “button” style that can be applied to both links or buttons;&lt;/li&gt;
&lt;li&gt;we want to apply it selectively, because we’ll have other link and button styles in our pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This calls for a CSS component.
A CSS component is a style or collection of styles which we can apply using classes, often on top of a few different types of HTML elements.
You may be familiar with this concept from CSS frameworks like Bootstrap or Foundation.&lt;/p&gt;
&lt;p&gt;We’ll call this component &lt;code&gt;.btn&lt;/code&gt;
(like Bootstrap does, but we’ll restrict ourselves to a single color and size, to keep things simple).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.btn {
  /* default for &amp;lt;button&amp;gt;, but useful for &amp;lt;a&amp;gt; */
  display: inline-block;
  text-align: center;
  text-decoration: none;

  /* create a small space when buttons wrap on 2 lines */
  margin: 2px 0;

  /* invisible border (will be colored on hover/focus) */
  border: solid 1px transparent;
  border-radius: 4px;

  /* size comes from text &amp;amp; padding (no width/height) */
  padding: 0.5em 1em;

  /* make sure colors have enough contrast! */
  color: #ffffff;
  background-color: #9555af;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what our button component looks like:&lt;/p&gt;
&lt;figure class=&quot;frame&quot;&gt;&lt;iframe class=&quot;autoheight&quot; src=&quot;https://fvsch.com/articles/styling-buttons/step2-basic.html&quot; width=&quot;100%&quot; height=&quot;250&quot;&gt;&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;You may be wondering why contrast is such a big deal.
That second line of buttons looks nice; who doesn’t like a pastel look?&lt;/p&gt;
&lt;p&gt;Simply put: &lt;em&gt;with good contrast, you can reach more users&lt;/em&gt;.
Some of your users have imperfect vision.
Others may be looking at your site on their phone, on the go,
with the daylight making things harder to read.
You can &lt;a href=&quot;https://webaim.org/resources/contrastchecker/&quot;&gt;check your contrast ratios here&lt;/a&gt;,
and &lt;a href=&quot;https://www.nngroup.com/articles/low-contrast/&quot;&gt;read more about the UX of text contrast here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;states&quot;&gt;Styling hover, focus and active states&lt;/h2&gt;
&lt;p&gt;It’s cool that your button looks nice, but…
users are going to interact with it,
and they will need visual feedback when the button’s state changes.&lt;/p&gt;
&lt;p&gt;Browsers come with default styles for “focus” and “active” (i.e. pressed) states,
but by resetting button styles we’ve removed some of those.
We would also like to have styles for mouse-over,
and overall we want styles that are visible &lt;em&gt;and&lt;/em&gt; match our design.&lt;/p&gt;
&lt;p&gt;Let’s start with a style for the &lt;code&gt;:active&lt;/code&gt; state,
triggered briefly when your button or link is clicked:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* old-school &quot;down&quot; effect on clic + color tweak */
.btn:active {
  transform: translateY(1px);
  filter: saturate(150%);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We could change the button’s colors,
but I want to reserve this effect for our mouse-over styles:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* inverse colors on mouse-over */
.btn:hover {
  color: #9555af;
  border-color: currentColor;
  background-color: white;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s now add some focus styles.
Users of your websites or web apps can use the keyboard or virtual keyboard (on iOS and Android)
to “focus” and activate form fields, buttons, links and other interactive elements.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For some users it can be a way to speed up slow interactions, like filling a form.&lt;/li&gt;
&lt;li&gt;For others, using a mouse or touch pointer is impossible or difficult.
They rely on using the keyboard, or a specialized device, to visit websites.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In most Web projects I’ve seen,
designers specify the expected mouse-over styles but not focus styles.
What should we do?
A simple solution is to reuse the &lt;code&gt;:hover&lt;/code&gt; style as our &lt;code&gt;:focus&lt;/code&gt; style:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* inverse colors on mouse-over and focus */
.btn:hover,
.btn:focus {
  color: #9555af;
  border-color: currentColor;
  background-color: white;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And once you have this visible focus style (and not before!),
you may want to remove the browser’s default styles for your button:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.btn {
  /* ... */
  /* all browsers: remove the default outline since
      we are rolling our own focus styles */
  outline: none;
}

/* Firefox: removes the inner border shown on focus */
.btn::-moz-focus-inner {
  border: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try it out here;
if you’re on a desktop computer, use the Tab and Shift+Tab keys to navigate between buttons:&lt;/p&gt;
&lt;figure class=&quot;frame&quot;&gt;&lt;iframe class=&quot;autoheight&quot; src=&quot;https://fvsch.com/articles/styling-buttons/step3-states.html&quot; width=&quot;100%&quot; height=&quot;100&quot;&gt;&lt;/iframe&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;sticky-focus&quot;&gt;Dealing with sticky focus styles&lt;/h2&gt;
&lt;p&gt;There is one tricky issue left.
In several browsers, when you click a link or button, two pseudo-classes will apply:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:active&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:focus&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The “active” pseudo-class stops applying as soon as you stop pressing the mouse button or trackpad.
But in some browsers, the &lt;code&gt;:focus&lt;/code&gt; style stays until the user clicks something else on the page.&lt;/p&gt;
&lt;p&gt;In my tests, affected browsers include Chrome (66), Edge (16), and Firefox (60, only for links).
Safari (11.1) seems to be smarter and avoid this issue.&lt;/p&gt;
&lt;p&gt;We can fix this using the new &lt;a href=&quot;https://drafts.csswg.org/selectors/#the-focus-visible-pseudo&quot;&gt;&lt;code&gt;:focus-visible&lt;/code&gt; pseudo-class (draft specification)&lt;/a&gt;.
This feature is still not fully specified, but the idea is that browsers should set the &lt;code&gt;:focus-visible&lt;/code&gt; state only after keyboard or keyboard-like interaction, rather than on clicks.&lt;/p&gt;
&lt;p&gt;Since it’s not yet implemented by browsers, we will have to use a JavaScript implementation, such as &lt;a href=&quot;https://github.com/WICG/focus-visible&quot;&gt;this polyfill&lt;/a&gt;.
It operates on the whole page, and sets a &lt;code&gt;focus-visible&lt;/code&gt; class on elements that received focus &lt;em&gt;when using the keyboard only&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Let’s change our styles to decouple &lt;code&gt;:hover&lt;/code&gt; and &lt;code&gt;:focus&lt;/code&gt; styles:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* inverse colors on hover */
.btn:hover {
  color: #9050AA;
  border-color: currentColor;
  background-color: white;
}

/* make sure we have a visible focus ring */
.btn:focus {
  outline: none;
  box-shadow: 0 0 0 3px rgba(255, 105, 180, 0.5),
    0 0 0 1.5px rgba(255, 105, 180, 0.5);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, after we’ve included the &lt;code&gt;focus-visible.js&lt;/code&gt; script in our page,
it will add a &lt;code&gt;js-focus-visible&lt;/code&gt; class to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element.
We can use this to remove focus styles from focused elements which &lt;em&gt;do not&lt;/em&gt; have the &lt;code&gt;focus-visible&lt;/code&gt; class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* hide focus style if not from keyboard navigation */
.js-focus-visible .btn:focus:not(.focus-visible) {
  box-shadow: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A simpler solution would be to declare focus styles for the &lt;code&gt;focus-visible&lt;/code&gt; class only,
but this would break if the polyfill is not active (e.g. if our JS failed to load).&lt;/p&gt;
&lt;p&gt;This is our final result:&lt;/p&gt;
&lt;figure class=&quot;frame&quot;&gt;&lt;iframe class=&quot;autoheight&quot; src=&quot;https://fvsch.com/articles/styling-buttons/step4-final.html&quot; width=&quot;100%&quot; height=&quot;180&quot;&gt;&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;You can &lt;a href=&quot;https://fvsch.com/articles/styling-buttons/step4-final.html&quot;&gt;look at the final code&lt;/a&gt; to review everything we’ve seen in this tutorial.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:36fa7811-2674-5f64-a90f-62be9a0d8444</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/command-line-tips"/>
    <title>Command line tips</title>
    <published>2018-05-03T00:00:00+02:00</published>
    <updated>2018-05-03T00:00:00+02:00</updated>
    <summary>Semi-random tips I’ve learned that help working with the command line.</summary>
    <content type="html">&lt;p&gt;I’d like to share a few command line things I’ve learned in the past years.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#what&quot;&gt;The command what?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#tldr&quot;&gt;You need help (and tldr is great)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#bash-keyboard&quot;&gt;Bash keyboard shortcuts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#homebrew&quot;&gt;MacOS: install Homebrew and GNU coreutils&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-big&quot;&gt;How big is this directory?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#rmrf&quot;&gt;How to &lt;em&gt;not&lt;/em&gt; delete everything&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;what&quot;&gt;The command what?&lt;/h2&gt;
&lt;p&gt;I’m talking about the command prompt found in macOS when using the built-in “Terminal” application, or on Windows when you install something like &lt;a href=&quot;https://gitforwindows.org/&quot;&gt;Git Bash&lt;/a&gt;.
It’s a text-based interface where you type commands to navigate in folders, create or delete files, and much more.
If you’re curious and want to get started, &lt;a href=&quot;https://tutorial.djangogirls.org/en/intro_to_command_line/&quot;&gt;this tutorial is pretty good for learning from scratch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the next sections, examples will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# This line is a comment
$ command parameter --option&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The dollar character (&lt;code&gt;$&lt;/code&gt;) indicates a command that you can type.
You should not type this character, only what comes after it.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot;&gt;You need help (and &lt;code&gt;tldr&lt;/code&gt; is great)&lt;/h2&gt;
&lt;p&gt;Sometimes you remember the name of a command, but how should you use it?
What arguments and options does it take?&lt;/p&gt;
&lt;p&gt;Most operating systems will come with documentation files called “man pages” (man is for manual).
For example if I want to be reminded how the &lt;code&gt;cd&lt;/code&gt; (change directory) command works, I could type &lt;code&gt;man cd&lt;/code&gt; and press the Enter key, and I would get this:&lt;/p&gt;
&lt;figure class=&quot;full&quot;&gt;&lt;img class=&quot;border&quot; src=&quot;https://fvsch.com/articles/command-line-tips/terminal-man-cd.png&quot; alt=&quot;A very big wall of text&quot;&gt;&lt;/figure&gt;
&lt;p&gt;That’s great, but those doc pages are long often hard to read.
Can I get a summary?&lt;/p&gt;
&lt;p&gt;One option is to use the command’s built-in help, if it exists.
For example you can call &lt;code&gt;mycommand --help&lt;/code&gt;.
But some commands don’t have a help option, some use &lt;code&gt;-help&lt;/code&gt; (single hyphen), some use &lt;code&gt;-h&lt;/code&gt;, some use &lt;code&gt;mycommand help&lt;/code&gt; (no hyphen), it’s a mess!&lt;/p&gt;
&lt;p&gt;My solution is to use the &lt;code&gt;tldr&lt;/code&gt; command.
It’s an &lt;a href=&quot;https://tldr.sh/&quot;&gt;open-source documentation project&lt;/a&gt; for common commands, which shows very short usage examples.
It looks like this:&lt;/p&gt;
&lt;figure class=&quot;full&quot;&gt;&lt;img class=&quot;border&quot; src=&quot;https://fvsch.com/articles/command-line-tips/terminal-tldr-cd.png&quot; alt=&quot;Short usage examples for cd, using tldr cd&quot;&gt;&lt;/figure&gt;
&lt;p&gt;You can &lt;a href=&quot;https://tldr.ostera.io/cd&quot;&gt;use it online&lt;/a&gt;, but it’s quicker to use it straight from the command line.
You will need to have Node.js and &lt;code&gt;npm&lt;/code&gt; installed to use it, though, so it’s only useful if you’re using those tools already.&lt;/p&gt;
&lt;p&gt;Installation looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Install with npm
$ npm install --global tldr
...

# Time to use your new powers!
$ tldr rm&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;bash-keyboard&quot;&gt;Bash keyboard shortcuts&lt;/h2&gt;
&lt;p&gt;When you’re using a Terminal or command prompt on Linux or macOS, you’re probably using the Bash program.
(On Windows, you can install Git Bash to use that instead of cmd.exe.)&lt;/p&gt;
&lt;p&gt;Bash is, among other things, the program that allows you to write commands an see their output.&lt;/p&gt;
&lt;p&gt;One thing really frustrating with Bash and terminals in general is that you can’t just click around to move the input cursor, like you would in a code editor.
If you’re like me, you probably spent a lot of time using the left and right arrows to move the cursor in a line of text.&lt;/p&gt;
&lt;p&gt;So here are a few keyboard shortcuts that can help:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Up&lt;/code&gt;: fill the command prompt with the last command; useful if you need to correct it.
Use “Up” several times to go back in history.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl + Left&lt;/code&gt; and &lt;code&gt;Ctrl + Right&lt;/code&gt; (or &lt;code&gt;Alt + Left|Right&lt;/code&gt; on macOS): move the cursor by a full word.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl + A&lt;/code&gt;: move the cursor to the start of the line.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl + E&lt;/code&gt;: move the cursor to the end of the line.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl + K&lt;/code&gt;: delete all characters to the right.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl + U&lt;/code&gt;: delete all characters to the left.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;homebrew&quot;&gt;MacOS: install Homebrew and GNU coreutils&lt;/h2&gt;
&lt;p&gt;If you’re going to work on the command line often on macOS, it’s probably a good idea to install &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;, which will help you install more command-line tools.&lt;/p&gt;
&lt;p&gt;Once you’ve installed Homebrew, I recommend using GNU Core Utilities, which contains the GNU version of tools like &lt;code&gt;ls&lt;/code&gt; or &lt;code&gt;rm&lt;/code&gt;.
Compared to the similar tools that come with macOS, they’re often a bit more usable.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Install coreutils, then follow the instructions
# to use them instead of macOS’s built-in ones
$ brew install coreutils&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-big&quot;&gt;How big is this directory?&lt;/h2&gt;
&lt;p&gt;I’ve had trouble remembering how to check how big a directory is.
For the past 15 years, I would have to switch to a graphical file explorer to do that.&lt;/p&gt;
&lt;p&gt;Sometimes I would find this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Show the size of everything in current directory
$ du -sh *&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But I would never manage to remember it.
Then it hit me that it sounds a bit like “douche”, so if you need something to help you memorize it, you’re welcome.&lt;/p&gt;
&lt;p&gt;Here’s another trick for listing folders sorted by size.
This only works on Linux, or on macOS &lt;a href=&quot;#homebrew&quot;&gt;if you’re using GNU Core Utilities&lt;/a&gt;:&lt;/p&gt;
&lt;h2 id=&quot;rmrf&quot;&gt;How to &lt;em&gt;not&lt;/em&gt; delete everything&lt;/h2&gt;
&lt;p&gt;When you want to delete a directory which contains a lot of files, you’re probably going to use &lt;code&gt;rm -rf&lt;/code&gt;.
It might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ rm -rf some/path&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But if you mess up the path part, you can end up deleting the wrong folder.
For instance, maybe you wanted to delete a bunch of old log files:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ sudo rm -rf /var/log/some-program/old-logs/*&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But when you types, you pressed Enter by mistake, and the resulting command is now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ sudo rm -rf /&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Shit.&lt;/p&gt;
&lt;p&gt;There’s one way to make this kind of human error less likely: write the &lt;code&gt;-rf&lt;/code&gt; part last:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ sudo rm /var/log/some-program/old-logs/* -rf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you hit Enter by mistake, the command will be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;$ sudo rm /
rm: cannot remove &apos;/&apos;: Is a directory&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Phew.&lt;/p&gt;
&lt;p&gt;This is a good habit to get into:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write &lt;code&gt;rm&lt;/code&gt; then the directory’s path.&lt;/li&gt;
&lt;li&gt;Check the path, does it look okay?&lt;/li&gt;
&lt;li&gt;If it’s okay, write &lt;code&gt;-rf&lt;/code&gt; at the end.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The main issue here is that on Linux this will work perfectly, because the GNU version of &lt;code&gt;rm&lt;/code&gt; can take options at the beginning or at the end.
But on macOS, the BSD version of &lt;code&gt;rm&lt;/code&gt; will tell you that &lt;code&gt;-rf&lt;/code&gt; is not a file.
My workaround for this is &lt;a href=&quot;#homebrew&quot;&gt;installing GNU coreutils with Homebrew&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:6cecca07-37be-548a-b625-3538e8eb9679</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/svg-gradient-fill"/>
    <title>Gradient fills for SVG icons</title>
    <published>2018-03-26T00:00:00+02:00</published>
    <updated>2018-03-26T00:00:00+02:00</updated>
    <summary>Using gradients on SVG icons is not as easy as using linear-gradient in CSS, but it can still be done. Let’s explore a few techniques.</summary>
    <content type="html">&lt;p&gt;In my 2016 article &lt;a href=&quot;https://fvsch.com/svg-icons&quot;&gt;How to work with SVG icons&lt;/a&gt;, I concluded the “Advanced” section with this warning: sorry, gradient fills won’t work.&lt;/p&gt;
&lt;p&gt;I was mostly referring to code like &lt;code&gt;fill: linear-gradient(red, blue)&lt;/code&gt; which does not work because &lt;code&gt;fill&lt;/code&gt; is from SVG which has its own gradient system, and &lt;code&gt;linear-gradient&lt;/code&gt; is from CSS and made mostly for backgrounds. The two don’t mix well.&lt;/p&gt;
&lt;p&gt;I knew we could probably use SVG’s &lt;code&gt;&amp;lt;linearGradient/&amp;gt;&lt;/code&gt; element but hadn’t really tried it. Then a few months later I did try it, and it worked!, but I forgot to blog about it. So, how can we do this thing?&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Putting gradients in your HTML&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;The most reliable technique I found was to add a SVG element in your HTML page (at the start or end of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, for instance). It should define a &lt;code&gt;&amp;lt;linearGradient/&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg style=&quot;width:0;height:0;position:absolute;&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&amp;gt;
  &amp;lt;linearGradient id=&quot;my-cool-gradient&quot; x2=&quot;1&quot; y2=&quot;1&quot;&amp;gt;
    &amp;lt;stop offset=&quot;0%&quot; stop-color=&quot;#447799&quot; /&amp;gt;
    &amp;lt;stop offset=&quot;50%&quot; stop-color=&quot;#224488&quot; /&amp;gt;
    &amp;lt;stop offset=&quot;100%&quot; stop-color=&quot;#112266&quot; /&amp;gt;
  &amp;lt;/linearGradient&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This element should &lt;em&gt;not&lt;/em&gt; be hidden with &lt;code&gt;display:none&lt;/code&gt;, because some browsers will just ignore the gradient if we do that. Making it zero pixels high seems to work.&lt;/p&gt;
&lt;p&gt;You can then use this gradient on your SVG icon:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg class=&quot;icon&quot; fill=&quot;url(#my-cool-gradient) #447799;&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&amp;gt;
  &amp;lt;use xlink:href=&quot;#symbol-id&quot;&amp;gt;&amp;lt;/use&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or in your CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.icon {
  /* gradient and fallback color */
  fill: url(#my-cool-gradient) #447799;
}&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;frame padding&quot; style=&quot;text-align:center&quot;&gt;&lt;svg class=&quot;icon&quot; style=&quot;fill: url(#my-cool-gradient) blue;&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;#icon-leaf&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon&quot; style=&quot;fill: url(#my-cool-gradient) blue;&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;#icon-carpet&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;figcaption&gt;
    These two icons should be using a turquoise-to-dark-blue gradient.&lt;br&gt;
    Credits: &lt;a href=&quot;https://thenounproject.com/search/?q=leaf&amp;amp;i=75382&quot;&gt;Leaf by Gabriele Malaspina&lt;/a&gt; and &lt;a href=&quot;https://thenounproject.com/search/?q=carpet&amp;amp;i=1023407&quot;&gt;Carpet by Ben Davis&lt;/a&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Note that you cannot customize the gradient on a per-icon basis. If you want different gradients, you will need to create different SVG gradient definitions in you HTML.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Changing gradient colors with CSS variables&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;If we want to set our gradient colors from CSS, we could do it using CSS variables. We’re going to write our gradient definitions using CSS custom properties (&lt;code&gt;var(--my-custom-property)&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; style=&quot;width:0;height:0;position:absolute;&quot;&amp;gt;
  &amp;lt;linearGradient id=&quot;gradient-horizontal&quot;&amp;gt;
    &amp;lt;stop offset=&quot;0%&quot; stop-color=&quot;var(--color-stop-1)&quot; /&amp;gt;
    &amp;lt;stop offset=&quot;50%&quot; stop-color=&quot;var(--color-stop-2)&quot; /&amp;gt;
    &amp;lt;stop offset=&quot;100%&quot; stop-color=&quot;var(--color-stop-3)&quot; /&amp;gt;
  &amp;lt;/linearGradient&amp;gt;
  &amp;lt;linearGradient id=&quot;gradient-vertical&quot; x2=&quot;0&quot; y2=&quot;1&quot;&amp;gt;
    &amp;lt;stop offset=&quot;0%&quot; stop-color=&quot;var(--color-stop-1)&quot; /&amp;gt;
    &amp;lt;stop offset=&quot;50%&quot; stop-color=&quot;var(--color-stop-2)&quot; /&amp;gt;
    &amp;lt;stop offset=&quot;100%&quot; stop-color=&quot;var(--color-stop-3)&quot; /&amp;gt;
  &amp;lt;/linearGradient&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can set — and, if need be, change — those colors in our CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;#gradient-horizontal {
  --color-stop-1: #a770ef;
  --color-stop-2: #cf8bf3;
  --color-stop-3: #fdb99b;
}
#gradient-vertical {
  --color-stop-1: #00c3ff;
  --color-stop-2: #77e190;
  --color-stop-3: #ffff1c;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, finally, use them as icon fills:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.icon-hgradient {
  fill: url(#gradient-horizontal) gray;
  /* We could use it as a stroke fill too:
  stroke: url(#gradient-horizontal) gray; */
}
.icon-vgradient {
  fill: url(#gradient-vertical) gray;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s the result (in &lt;a href=&quot;https://caniuse.com/#feat=css-variables&quot;&gt;browser supporting CSS Custom Properties&lt;/a&gt;):&lt;/p&gt;
&lt;figure class=&quot;frame padding&quot; style=&quot;text-align:center&quot;&gt;&lt;svg class=&quot;icon&quot; style=&quot;fill: url(#gradient-vertical) gray;&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;#icon-leaf&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon&quot; style=&quot;fill: url(#gradient-horizontal) gray;&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;#icon-carpet&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;span class=&quot;access-label&quot;&gt;Testing icons with SVG gradient fills and CSS variables&lt;/span&gt;
  &lt;figcaption&gt;The gradient fill is painted for each path of the icon. To avoid color mismatches (like the junction between the leaf and the stem), it might be useful to merge all or most paths in the icon’s source SVG.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;&lt;span&gt;Using gradients in external files&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;This technique works in Firefox, and used to work in Edge (confirmed in Edge 14 and 15, regressed in Edge 16 and Insider Preview). In the following test:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Both icons are fetched from an external sprite: &lt;a href=&quot;https://fvsch.com/articles/svg-gradient-fill/sprite.svg&quot;&gt;sprite.svg&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The first icon uses a gradient from this HTML page, and the second one from &lt;code&gt;sprite.svg&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.icon-sprite-gradient {
  fill: url(sprite.svg#my-warm-gradient) red;
}&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;frame padding&quot; style=&quot;text-align:center&quot;&gt;&lt;svg class=&quot;icon&quot; style=&quot;fill:url(&apos;#my-warm-gradient&apos;) red;&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-gradient-fill/sprite.svg#icon-leaf&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon&quot; style=&quot;fill:url(&apos;https://fvsch.com/articles/svg-gradient-fill/sprite.svg#my-warm-gradient&apos;) red;&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-gradient-fill/sprite.svg#icon-carpet&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;figcaption&gt;Non-supporting browsers (Chrome, Safari, latest Edge…) should show a red fallback fill for the second icon.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;&lt;span&gt;Using gradients in CSS as data URLs&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;What if I told you that you could define your gradients in SVG, but put the SVG in your CSS as data URLs? Okay now I’m just being silly. But it works! At least it works in Firefox :P&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* Notice the `id=&quot;grad&quot;` in the SVG URL and how we’re using it at the end. Note that the `#` in hexadecimal colors must be escaped as `%23`. */
.icon-gradient-url {
  fill: url(&quot;data:image/svg+xml,&amp;lt;svg xmlns=&apos;http://www.w3.org/2000/svg&apos;&amp;gt;&amp;lt;linearGradient id=&apos;grad&apos;&amp;gt;&amp;lt;stop offset=&apos;0%&apos; stop-color=&apos;%23ff00cc&apos;/&amp;gt;&amp;lt;stop offset=&apos;100%&apos; stop-color=&apos;%23333399&apos;/&amp;gt;&amp;lt;/linearGradient&amp;gt;&amp;lt;/svg&amp;gt;#grad&quot;) purple;
}

/* Same SVG, in base64 */
.icon-gradient-base64 {
  fill: url(&quot;data:image/svg+xml;base64,PHN2...zdmc+#grad&quot;) purple;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See how we reference the gradient’s &lt;code&gt;id&lt;/code&gt; with &lt;code&gt;#grad&lt;/code&gt; at the end of the URL? Only Firefox seems to understand this syntax. Eh, too bad.&lt;/p&gt;
&lt;figure class=&quot;frame padding&quot; style=&quot;text-align:center&quot;&gt;&lt;svg class=&quot;icon icon-gradient-url&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-gradient-fill/sprite.svg#icon-leaf&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon icon-gradient-base64&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-gradient-fill/sprite.svg#icon-carpet&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;figcaption&gt;Trying to show a magenta to purple gradient. Non-supporting browsers (Chrome, Safari, Edge…) should show a purple fallback fill.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;&lt;span&gt;Let’s recap&lt;/span&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Yes you can have gradient fills!&lt;/li&gt;
&lt;li&gt;But you need to include SVG gradients in your HTML&lt;/li&gt;
&lt;li&gt;Colors can be hardcoded in the SVG gradient (in HTML), or set in CSS (using CSS variables)&lt;/li&gt;
&lt;li&gt;All icons using a given gradient will use the same colors, you can’t override on a specific color like this:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.icon-gradient-override {
  /* This works, as shown before */
  fill: url(#gradient-horizontal) gray;
  /* But this will do nothing… */
  --color-stop-1: white;
  --color-stop-2: gray;
  --color-stop-3: black;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need a lot of different gradient fills, this technique might not work for you. Creating 10 or 20 different SVG gradients in your HTML is not practical. But for simpler use cases, this should work in all current browsers (including IE11 if we avoid CSS variables).&lt;/p&gt;&lt;!-- SVG gradient for gradient fill --&gt;
&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; style=&quot;width:0;height:0;position:absolute;&quot;&gt;
  &lt;linearGradient id=&quot;my-cool-gradient&quot; x2=&quot;1&quot; y2=&quot;1&quot;&gt;
    &lt;stop offset=&quot;0%&quot; stop-color=&quot;#447799&quot; /&gt;
    &lt;stop offset=&quot;50%&quot; stop-color=&quot;#224488&quot; /&gt;
    &lt;stop offset=&quot;100%&quot; stop-color=&quot;#112266&quot; /&gt;
  &lt;/linearGradient&gt;
  &lt;linearGradient id=&quot;my-warm-gradient&quot; x2=&quot;1&quot; y2=&quot;1&quot;&gt;
    &lt;stop offset=&quot;0%&quot; stop-color=&quot;#CC7744&quot; /&gt;
    &lt;stop offset=&quot;50%&quot; stop-color=&quot;#992244&quot; /&gt;
    &lt;stop offset=&quot;100%&quot; stop-color=&quot;#661133&quot; /&gt;
  &lt;/linearGradient&gt;
  &lt;linearGradient id=&quot;gradient-horizontal&quot;&gt;
    &lt;stop offset=&quot;0%&quot; stop-color=&quot;var(--color-stop-1)&quot; /&gt;
    &lt;stop offset=&quot;50%&quot; stop-color=&quot;var(--color-stop-2)&quot; /&gt;
    &lt;stop offset=&quot;100%&quot; stop-color=&quot;var(--color-stop-3)&quot; /&gt;
  &lt;/linearGradient&gt;
  &lt;linearGradient id=&quot;gradient-vertical&quot; x2=&quot;0&quot; y2=&quot;1&quot;&gt;
    &lt;stop offset=&quot;0%&quot; stop-color=&quot;var(--color-stop-1)&quot; /&gt;
    &lt;stop offset=&quot;50%&quot; stop-color=&quot;var(--color-stop-2)&quot; /&gt;
    &lt;stop offset=&quot;100%&quot; stop-color=&quot;var(--color-stop-3)&quot; /&gt;
  &lt;/linearGradient&gt;
  &lt;symbol id=&quot;icon-carpet&quot; viewBox=&quot;0 0 38 60&quot;&gt;&lt;path d=&quot;M34 18v33.9c0 .3-.1.6-.3.8-.4.3-.8.3-1.2.3H5a1 1 0 0 1-1-1V8c0-.6.4-1 1-1h28c.6 0 1 .4 1 1v10zm3-18a1 1 0 0 0-1 1v2h-2V1a1 1 0 0 0-2 0v2h-2V1a1 1 0 0 0-2 0v2h-2V1a1 1 0 0 0-2 0v2h-2V1a1 1 0 0 0-2 0v2h-2V1a1 1 0 0 0-2 0v2h-2V1a1 1 0 0 0-2 0v2h-2V1a1 1 0 0 0-2 0v2H6V1a1 1 0 0 0-2 0v2H2V1a1 1 0 0 0-2 0v58a1 1 0 0 0 2 0v-2h2v2a1 1 0 0 0 2 0v-2h2v2a1 1 0 0 0 2 0v-2h2v2a1 1 0 0 0 2 0v-2h2v2a1 1 0 0 0 2 0v-2h2v2a1 1 0 0 0 2 0v-2h2v2a1 1 0 0 0 2 0v-2h2v2a1 1 0 0 0 2 0v-2h2v2a1 1 0 0 0 2 0v-2h2v2a1 1 0 0 0 2 0V1c0-.6-.4-1-1-1z&quot;/&gt;&lt;path d=&quot;M8 16c0-.6.4-1 1-1h3v-3c0-.6.4-1 1-1h3V9H6v10h2v-3zM11 28.5l21 21V39h-3a1 1 0 0 1-1-1v-3h-3a1 1 0 0 1-1-1v-3h-3a1 1 0 0 1-1-1v-4c0-.6.4-1 1-1h3v-3c0-.6.4-1 1-1h3v-3c0-.6.4-1 1-1h3V9h-1.6L11 28.5z&quot;/&gt;&lt;path d=&quot;M30 22c0 .6-.4 1-1 1h-3v3c0 .6-.4 1-1 1h-3v2h3c.6 0 1 .4 1 1v3h3c.6 0 1 .4 1 1v3h2V19h-2v3zM27.6 9H18v3c0 .6-.4 1-1 1h-3v3c0 .6-.4 1-1 1h-3v3c0 .6-.4 1-1 1H6v9.6l2.8-2.8L27.6 9zM16 45h-3a1 1 0 0 1-1-1v-3H9a1 1 0 0 1-1-1v-3H6v14h6v-3c0-.6.4-1 1-1h3v-2z&quot;/&gt;&lt;path d=&quot;M6 33.4V35h3c.6 0 1 .4 1 1v3h3c.6 0 1 .4 1 1v3h3c.6 0 1 .4 1 1v4c0 .6-.4 1-1 1h-3v2h16.6L9.5 30 6 33.3z&quot;/&gt;&lt;/symbol&gt;
  &lt;symbol id=&quot;icon-leaf&quot; viewBox=&quot;0 0 100 100&quot;&gt;&lt;path d=&quot;M50.6 30.6l-3.6 3-3.5 3.1-3.4 3.5-1.6 1.7-1.6 1.8c-1 1.3-2.2 2.5-3.1 3.8l-3 3.8c-1.7 2.6-3.5 5.2-5 7.9-1.5 2.6-2.8 5.2-4 7.7l-1.6 3.7-1.4 3.6c-.8 2.2-1.6 4.3-2.1 6.2l-1.4 5-1 4.3-.1.2-.2 1.2a3.4 3.4 0 0 0 6.6 1c3-19.9 14.6-41.1 31.8-58.3l.7-.7a3.4 3.4 0 0 0 .2-4.7l-2.7 2.2z&quot;/&gt;&lt;path d=&quot;M85 6a51.3 51.3 0 0 0-61.4 52.2l.2-.2c1.5-2.7 3.3-5.4 5.2-8l3-4 3.2-3.7 1.6-1.9 1.8-1.8 3.4-3.4 3.6-3.2 3.7-3c2.3-2 4.8-3.7 7.1-5.3 2.3-1.7 4.6-3 6.7-4.4C65.2 18 67.2 17 69 16c1.7-1 3.3-1.7 4.6-2.4l4-2a.9.9 0 0 1 .8 1.6l-4 2c-1.3.7-2.9 1.4-4.6 2.4-1.7 1-3.6 2-5.7 3.4-2 1.3-4.3 2.7-6.5 4.3-2.3 1.6-4.7 3.3-7 5.3l-3.6 3-3.5 3.1-3.4 3.5-1.6 1.7-1.6 1.8c-1 1.3-2.2 2.5-3.1 3.8l-3 3.8c-1.7 2.6-3.5 5.2-5 7.9L24 62.6l.6 3.9A51.3 51.3 0 0 0 85 6z&quot;/&gt;&lt;/symbol&gt;
&lt;/svg&gt;

&lt;!-- Demo styles --&gt;
&lt;style&gt;
.access-label {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
  white-space: nowrap !important;
}
.icon {
  width: 180px;
  height: 180px;
  vertical-align: top;
}
#gradient-horizontal {
  --color-stop-1: #a770ef;
  --color-stop-2: #cf8bf3;
  --color-stop-3: #fdb99b;
}
#gradient-vertical {
  --color-stop-1: #00c3ff;
  --color-stop-2: #77e190;
  --color-stop-3: #ffff1c;
}
.icon-gradient-url {
  fill: url(&quot;data:image/svg+xml,&lt;svg xmlns=&apos;http://www.w3.org/2000/svg&apos;&gt;&lt;linearGradient id=&apos;grad&apos;&gt;&lt;stop offset=&apos;0%&apos; stop-color=&apos;%23ff00cc&apos;/&gt;&lt;stop offset=&apos;100%&apos; stop-color=&apos;%23333399&apos;/&gt;&lt;/linearGradient&gt;&lt;/svg&gt;#grad&quot;) purple;
}
.icon-gradient-base64 {
  fill: url(&quot;data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZCc+PHN0b3Agb2Zmc2V0PScwJScgc3RvcC1jb2xvcj0nI2ZmMDBjYycvPjxzdG9wIG9mZnNldD0nMTAwJScgc3RvcC1jb2xvcj0nIzMzMzM5OScvPjwvbGluZWFyR3JhZGllbnQ+PC9zdmc+#grad&quot;) purple;
}
&lt;/style&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:14584e59-faeb-50fa-b4fb-50c511332598</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/firefox-privacy-settings"/>
    <title>A few wishes for privacy settings in Firefox</title>
    <published>2018-02-07T00:00:00+01:00</published>
    <updated>2018-02-07T00:00:00+01:00</updated>
    <summary>Firefox could do more to limit online tracking. Recently with the Quantum effort, Mozilla rebranded Firefox along three attributes: speed, customization and privacy. Yet the browser’s defaults allow most tracking techniques by default, for the sake of compatibility with existing websites.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;Update: as I was writing this post, Mozilla was already working on privacy features and settings. Firefox’s Tracking Protection was released in 2018 and enabled by default for all users in 2019.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Firefox could do more to limit online tracking. Recently with the Quantum effort, Mozilla rebranded Firefox along three attributes: speed, customization and privacy. Yet the browser’s defaults allow most tracking techniques by default, for the sake of compatibility with existing websites.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;About tracking&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Most web browsers leak a lot of user data by default. For instance:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;They allow third-party services (which a website almost always uses, e.g. Google Analytics, Facebook Connect, and ad providers) to track users’ activity across many websites, using third-party and first-party cookies.&lt;/li&gt;
&lt;li&gt;They allow sites to know where a user is coming from (with the &lt;code&gt;Referer&lt;/code&gt; HTTP header).&lt;/li&gt;
&lt;li&gt;Through JavaScript, they give access to a lot of information on the browser itself, the operating system I’m using, my time zone, how many processor cores I have, and some of my preferences (e.g. whether I allow cookies, which languages I prefer reading). This and other technical information can be used to identify you uniquely across the web, a practice dubbed “fingerprinting”.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’m probably forgetting a few things. Try &lt;a href=&quot;https://panopticlick.eff.org/&quot;&gt;the EFF’s Panopticlick tool&lt;/a&gt; to get an idea of how resistant your browser is to tracking and fingerprinting.&lt;/p&gt;
&lt;p&gt;Most users don’t realize how much random companies they never deal with (such as ad networks) and other companies they do deal with (such as Google and Facebook) manage to know about them.&lt;/p&gt;
&lt;p&gt;I’ve had a few friends and relatives asking how to remove a virus from their browser… where by “a virus” they meant “many sites I visit show me the same ads that seem to know too much about me, so a virus must be spying everything I do and inject ads in my computer”. After explaining a bit, I cleaned their cookies and installed &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/&quot;&gt;uBlock Origin&lt;/a&gt; for them.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Why browsers don’t do more&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Of course all the technical features I mentioned are not provided &lt;em&gt;for&lt;/em&gt; tracking users, they are simply abused that way. But why won’t browsers stop this abuse? Why even load scripts — executable code with access to a bunch of information and features! — from other domains, why enable cookies for third-parties, etc.&lt;/p&gt;
&lt;p&gt;In short: since it was possible before, most sites rely on these loose permissions now, and would break if they were tightened.&lt;/p&gt;
&lt;p&gt;There are still opportunities for tightening these features and preventing at least some of the massive tracking going on, but different browser vendors have different priorities:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Chrome has a good focus on security but is all-in on tracking, because spying on users is Google’s business model.&lt;/li&gt;
&lt;li&gt;Firefox is in a strange position because their brand is about independence and privacy but ultimately the bulk of their money comes from ad-and-tracking companies: Google, Yahoo and perhaps one of their competitors in the future. Also if a website stops working in Firefox then users are likely to blame Firefox for the breakage (and not the website for their invasive tracking techniques), and jump ship to Chrome.&lt;/li&gt;
&lt;li&gt;Upstarts like Brave target a niche of tech-savvy privacy-conscious users, which want an on-by-default ad blocker, so they have some breathing room here. Also they’re based on Chromium so that limits other kinds of breakage (e.g. all the “Chrome only” web apps launching these days, from Google and others), which buys them some goodwill.&lt;/li&gt;
&lt;li&gt;Safari enjoys a monopolistic position on iOS (since other browsers on iOS must use WebKit for rendering pages), and has good usage numbers on macOS, so they can get away with off-by-default third-party cookies and more recent things such as &lt;a href=&quot;https://webkit.org/blog/7675/intelligent-tracking-prevention/&quot;&gt;Intelligent Tracking Protection&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;span&gt;What Mozilla &lt;em&gt;is&lt;/em&gt; doing&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Note: I’m not an employee and not a big contributor to Mozilla projects. The information in this section comes from reading updates on Mozilla blogs and Twitter accounts.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Back to Firefox. While I wish Mozilla would have their Tracking Protection feature on by default, I understand that it can be a hard sell: many Firefox users would not be aware of that feature or what its advantages are, and they would resent the occasional site breakage.&lt;/p&gt;
&lt;p&gt;Which is why Mozilla is experimenting with such features in Private Browsing Mode only: in this mode, users can expect things to maybe break, and are warned about it.&lt;/p&gt;
&lt;figure&gt;&lt;img alt=&quot;Screenshot of Firefox’s Private Browsing new tab page&quot; src=&quot;https://fvsch.com/articles/firefox-privacy-settings/private-browsing-page.png&quot;&gt;&lt;/figure&gt;
&lt;p&gt;In Private Browsing windows, &lt;a href=&quot;https://support.mozilla.org/en-US/kb/tracking-protection&quot;&gt;Tracking Protection&lt;/a&gt; is on by default, and Mozilla is working on &lt;a href=&quot;https://blog.mozilla.org/security/2018/01/31/preventing-data-leaks-by-stripping-path-information-in-http-referrers/&quot;&gt;shortening the Referer HTTP header for third-party domains&lt;/a&gt; so that sites can only know which site you’re coming from and not which specific page (or worse, what private information the URL contained, in some bad cases).&lt;/p&gt;
&lt;p&gt;Mozilla is also researching &lt;a href=&quot;https://blog.mozilla.org/data/2018/01/26/improving-privacy-without-breaking-the-web/&quot;&gt;how often privacy-centric restrictions break websites&lt;/a&gt;; basically they’re trying to figure out what they can do without losing a bunch of users.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mozilla.org/en-US/firefox/mobile/&quot;&gt;Firefox Focus&lt;/a&gt; is another attempt at privacy-by-default, launched as a separate product (and sub-brand).&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Privacy settings should be simpler&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Currently, a more private setup in Firefox requires:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Activating Tracking Protection for all sites in the “Privacy” page of the browser preferences. This setting is also way down in the page.&lt;/li&gt;
&lt;li&gt;Tweaking some settings in the “Privacy” page, some being a bit painful to get right (blocking third-party cookies or restricting cookies to the current section only).&lt;/li&gt;
&lt;li&gt;If you’re feeling even more adventurous, there are a bunch of settings to tweak in &lt;code&gt;about:config&lt;/code&gt;, for HTTP Referer and more.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This should be simpler, and more palatable to most users.&lt;/p&gt;
&lt;p&gt;My wish would be for Mozilla to design a combo of privacy features which users could activate in one click. And because nothing is ever new, &lt;em&gt;in spirit&lt;/em&gt; it would be somewhat close to what Microsoft did with Internet Explorer’s security levels:&lt;/p&gt;
&lt;figure&gt;&lt;img alt=&quot;The security pane of IE7’s Internet Options&quot; src=&quot;ie7-internet-zone-high&quot;&gt;&lt;/figure&gt;
&lt;p&gt;I don’t have fond memories of this setting because, as a developer, it meant that a client’s IE6 or IE7 would sometimes block all scripting. But in retrospect I do find that it had some merits as a preference, if not always in the specific technical choices that were made.&lt;/p&gt;
&lt;p&gt;Two, maybe three levels of protection could be offered:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Default (current behavior, which should not stop Mozilla from upgrading its privacy by blocking third-party cookies and maybe offering something similar to Safari’s Intelligent Tracking Protection…)&lt;/li&gt;
&lt;li&gt;More Private (enables Tracking Protection, disables third-party cookies altogether or limits them to the current session, and maybe adds some other technical restrictions such as truncating Referer and fingerprinting resistance.)&lt;/li&gt;
&lt;li&gt;Private Browsing Only (same as #2, with some added technical restrictions and all browsing is in Private Browsing Mode)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s a very rough idea and maybe these specific profiles don’t make sense. But I think it’s an idea worth pursuing, to offer better privacy defaults and options to all users.&lt;/p&gt;
&lt;p&gt;What’s more, this setting should not be hidden in the “Privacy” settings page, but should be near the top of the “General” settings page or at the top of the “Privacy” page at worst. It should also come with an onboarding campaign to explain the trade-offs of each mode and offer a chance to switch modes right there. Finally, there might be ways to reflect this setting — and maybe offer a chance to switch — on each tab, in the navigation bar or on the New Tab page itself.&lt;/p&gt;
&lt;p&gt;I have no idea if the rambling post can be of any help achieving better privacy by default in Firefox or any browser, but here it is anyway. Thanks for reading.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:c8076f02-e7e3-5e2f-a519-8b86426c00b7</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/css-print-background"/>
    <title>Printing background colors with CSS and SVG</title>
    <published>2018-01-24T00:00:00+01:00</published>
    <updated>2018-01-24T00:00:00+01:00</updated>
    <summary>Web browsers do not print background colors by default. Here’s a trick for printing a flat color, e.g. if you have a white logo or graphic.</summary>
    <content type="html">&lt;p&gt;A client’s site had a white SVG logo on top of a color background.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;header style=&quot;background:black&quot;&amp;gt;
  &amp;lt;img src=&quot;https://fvsch.com/articles/css-print-background/white-logo.svg&quot;
       alt=&quot;The site’s name&quot;&amp;gt;
&amp;lt;/header&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When working on the print styles, we had a problem: browsers would disable background colors and print the logo white on white. And in this case we could not change the logo image or use a &lt;code&gt;filter: invert(100%)&lt;/code&gt; on it.&lt;/p&gt;
&lt;p&gt;We could have used the non-standard &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-print-color-adjust&quot;&gt;&lt;code&gt;-webkit-print-color-adjust&lt;/code&gt;&lt;/a&gt; CSS property, but what about other browsers?&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Faking a background with SVG&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;By default, browsers will print content images. We can inject one in our page and display it as a kind of background, in 3 steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a pseudo-element (&lt;code&gt;::before&lt;/code&gt; or &lt;code&gt;::after&lt;/code&gt;) and an image URL in the &lt;code&gt;content&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;For a solid color, we can make a simple SVG image, which works well as a data-URL.&lt;/li&gt;
&lt;li&gt;Finally, we use absolute positioning to place this pseudo-element in the background.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;header {
  position: relative;
}
header &amp;gt; * {
  position: relative;
  z-index: 2;
}
header::before {
  position: absolute;
  z-index: 1;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  overflow: hidden;
  /*
    In the URL, change the background to your own color.
    Warning: the `#` character must be escaped as %23,
    for example: `background:%23FFFF00` (yellow).
  */
  content: url(&quot;data:image/svg+xml,&amp;lt;svg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 10 10&apos; style=&apos;background:YOUR_COLOR_HERE&apos; /&amp;gt;&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is a fully working example in an iframe. You can test it by using your browser’s Print Preview on this page, or &lt;a href=&quot;https://fvsch.com/articles/css-print-background/example.html&quot;&gt;on the example page directly&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&quot;frame&quot;&gt;&lt;iframe class=&quot;autoheight&quot; src=&quot;https://fvsch.com/articles/css-print-background/example.html&quot; width=&quot;100%&quot; height=&quot;500&quot;&gt;&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;As noted, it’s a nice trick but don’t overuse it: you could cost your users a lot of money in printer ink.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Browser support&lt;/span&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Works on all modern desktop browsers (tested in Edge 14–16, Firefox 58–60, Chrome 61–63, Safari 11)&lt;/li&gt;
&lt;li&gt;Not tested on mobile browsers&lt;/li&gt;
&lt;li&gt;Does &lt;em&gt;not&lt;/em&gt; work in IE11&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;span&gt;Three things to watch out for&lt;/span&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The pseudo-element and the image are two separate entities. While the pseudo-element will cover its parent, the image will take the full width, and its height will depend on its aspect ratio. This means that the image will overflow its container, and we have to hide it with &lt;code&gt;overflow:hidden&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In this example, we use a square image (&lt;code&gt;viewBox=&apos;0 0 10 10&apos;&lt;/code&gt;); if you need a taller image, use a rectangle one (for example &lt;code&gt;viewBox=&apos;0 0 10 50&apos;&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Making sure that your &lt;code&gt;url(…)&lt;/code&gt; is a valid data-URL can be tricky. In practice there is not a lot to escape, but if you are using double quotes to wrap your URL you will need single quotes for the XML attributes, and vice-versa. The &lt;code&gt;#&lt;/code&gt; character identifies a fragment in URLs, so you will need to escape it using percent-encoding (&lt;code&gt;%23&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:db5a6eb8-9c2f-5494-99aa-8f6eca18cbd0</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/public-drafts"/>
    <title>Blogging with public drafts</title>
    <published>2018-01-23T00:00:00+01:00</published>
    <updated>2018-01-23T00:00:00+01:00</updated>
    <summary>I’m a perfectionist and often take too long to hit Publish on an article, or to finish a draft that got out of hand. One solution I found is to just publish the draft.</summary>
    <content type="html">&lt;p&gt;The &lt;a href=&quot;https://fvsch.com/css-locks&quot;&gt;last big article I published&lt;/a&gt; took ten days of research and writing. While I’m happy with the result, and have received positive comments, the prospect of writing another one like this is daunting. It was a year and a half ago.&lt;/p&gt;
&lt;p&gt;Every article I write starts with a small itch to scratch, and I often decide to dedicate a few hours to it at most. And every single time, it takes at least 10 hours of work, and sometimes days. What starts as “I’ll write up this bit of information” ends up as a full guide with context and examples. I run a bunch of Google (or, lately, DuckDuckGo) searches to check if an English or French phrase does exist and means what I think it means (not always). I have a bunch of drafts that I’ve worked on for 5 or 10 hours, and that would need at least as much time to get out of the door.&lt;/p&gt;
&lt;p&gt;It gets hard to just write some words and publish. Doubt creeps in. Does this even make sense? Who will want to read about that anyway? Hasn’t this topic been beaten to death already?&lt;/p&gt;
&lt;p&gt;I’ve wanted to make the whole mental process easier for a long time, so here are a few steps I took already:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Scratched my old, carefully wrought design. Hello black-on-white Georgia, you look like a nice notepad to me.&lt;/li&gt;
&lt;li&gt;Installed an editing interface, so I could work online and not on the command line (which, in practice, meant working on my own computer).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On top of that, I’d like to propose something a bit different for people who want to blog more: &lt;strong&gt;publish the damn draft&lt;/strong&gt;. Finish, edit, rewrite or refine later.&lt;/p&gt;
&lt;p&gt;I understand why print publications need cleaned-up stuff. And if I’m paying for articles, in print or not, I’d like them to be intelligent and edited. But the free blogging we do, who said it had to observe the same standards?&lt;/p&gt;
&lt;p&gt;Talking to other ordinary bloggers (aka people who blog once in a full moon or less), it sounds like we’re holding ourselves to the unrealistic standard of published authors &lt;em&gt;who often work with an editor&lt;/em&gt;. We try to be our own editor, but without any actual editorial review we end up doubting our words and our intent.&lt;/p&gt;
&lt;p&gt;So here’s the trick I’d like to try: publish unfinished drafts. I mean drafts where you haven’t even reached the end of your thoughts or message. Get it out, tweet or signal-boost it maybe, and finish tomorrow or next weekend.&lt;/p&gt;
&lt;p&gt;Just add a banner which says “hey, this is a draft and might not be complete yet; I hope to finish this article in the next days, but do go on reading”.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:3d7fbd46-d9d1-51dd-b906-8ec61444f09e</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/reading-books-again"/>
    <title>Reading books again</title>
    <published>2018-01-19T00:00:00+01:00</published>
    <updated>2018-01-19T00:00:00+01:00</updated>
    <summary>I had pretty much stopped reading books in the past few years, and wanted to change that. So I got an e-reader a year ago, and it worked.</summary>
    <content type="html">&lt;p&gt;I got my first e-reader a year ago, and it’s good.&lt;/p&gt;
&lt;p&gt;I had pretty much stopped reading books at this point, which was a bit different from my previous experiences with reading.&lt;/p&gt;
&lt;p&gt;ONE. As a teen I had a tendency to get bored and I spent a good chunk of those years reading science-fiction novels from the local public libraries (in French translations).&lt;/p&gt;
&lt;p&gt;TWO. In college I was studying both French literature and English as a foreign language, so I made up a rule that all my “for fun” reading time would be in English, because at least I would be improving one of two skills. (While it did help my English skills, I also neglected to build an appreciation of classical and contemporary literature.) Then for a while I lost interest in fiction and was mostly reading essays and articles.&lt;/p&gt;
&lt;p&gt;THREE. For context, I have some anxiety difficulties. Over time, as a result of mismanaged anxiety and changing habits — now including work, social media, and series more often than films — my attention span shortened. My capacity for sustained concentration dwindled. If something felt uncomfortable, I had to step back and switch to a different stimulus.&lt;/p&gt;
&lt;p&gt;This affected my reading habits: I could still read articles (taking several breaks when they were long), but averaged at one and a half book per year (2 or 3 in a good year).&lt;/p&gt;
&lt;p&gt;FOUR. In the summer of 2016, while I was burned out from (mismanaging my anxiety at) work, I tried to remember the last book I had read. (Madeline Miller’s &lt;cite&gt;The Song of Achilles&lt;/cite&gt;, which I had read a year before.) As I made plans to recover, I knew that I wanted to start reading books again. Not that I think there’s something superior in that activity, but I suddenly felt nostalgic for the time when reading was a simple past-time and something I enjoyed consistently.&lt;/p&gt;
&lt;p&gt;Or maybe I was angry and trying to get back some level of control over my fucked up mind, and hoped that reading more books would be both a sign of me getting better and a concentration practice of sorts.&lt;/p&gt;
&lt;p&gt;FIVE. When you want to make a change in your habits, it’s natural to look for a tangible thing to change. Just picking up one of the thirty unread books you own is not particularly exciting. That’s probably why I went back to my long-held desire for a great e-reader.&lt;/p&gt;
&lt;p&gt;Now you must understand that I’ve wanted electronic books since I read a fateful article in 2000 — technically, last century. According to the journalists and scientists in research labs, we would soon have high-resolution, color, flexible e-books. One could only imagine the resulting applications and form-factors!&lt;/p&gt;
&lt;p&gt;The first, second, third, fourth and fifth waves of actually marketed products were mightily disappointing: gray, plastic, bulky, low resolution, and plain ugly. Every other year, I would check the e-reader market and realize that the future was a lie. E-readers were my very own flying car fixation.&lt;/p&gt;
&lt;p&gt;But in late 2016 it seemed like there was finally something decent on the market: slightly bigger screen (8 inches instead of the ubiquitous 6 inch screens), print-level resolution, and not bulky.&lt;/p&gt;
&lt;figure&gt;&lt;img alt=&quot;Kobo Aura One official product image&quot; width=&quot;450&quot; src=&quot;https://fvsch.com/articles/reading-books-again/aura-one.jpg&quot;&gt;&lt;figcaption&gt;The Kobo Aura One. It doesn’t suck.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I still have big dreams of foldable technicolor e-paper, but I had finally found a e-reader that was close to a paperback’s size and resolution, and I decided that it would be enough for now. Had to wait while it was sold out — turns out I’m not the only one who wanted an e-reader that is not tiny.&lt;/p&gt;
&lt;p&gt;As for the device: it’s nice. I won’t bother you with a review, but I like how the user interface is set in Georgia — an unusual and excellent choice — and how the screen and optional back-lighting are easy on the eyes. The bigger, 8-inch format can also work well for some black-and-white graphic novels (those with not-too-dense pages, and most manga).&lt;/p&gt;
&lt;p&gt;SIX. You know how buying new gear — a new camera, instrument, etc. — does not come with magical motivation and dedication? Yeah, so I was a bit worried about that.&lt;/p&gt;
&lt;p&gt;In any case, I was tired of packing 4 books on vacations (and only touching one of those for a week), or hesitating to load my day bag with a heavy volume. Even if I ended up not reading much, packing just one e-reader instead would be a weight off my shoulders.&lt;/p&gt;
&lt;p&gt;I asked a friend for a few book recommendations and she sent me a dozen epubs. I did buy a few books legally too, one without DRM and the rest loaded with the whole DRM shitshow.&lt;/p&gt;
&lt;p&gt;In one case, after buying a digital copy of Philippe Jaccottet’s &lt;cite&gt;À la lumière d’hiver&lt;/cite&gt; (which I had bought in paper format twice before), I realized that the book contained a dozen typos and — the horror — straight apostrophes everywhere! I spent hours finding a way to crack the Adobe DRM, so that I could use my HTML-fu and fix that book (and keep it in 10 years when the Adobe DRM servers are down, too).&lt;/p&gt;
&lt;p&gt;I started reading books again, mostly during my commute, but also some nights and on vacations. I’m reading roughly one book per month, sometimes two. I’m not pressuring myself to read, it’s just convenient and offers and alternative to other pastimes (and since mine tend to happen on a computer, they’re loaded with distractions, so this one often feels like much-needed time off).&lt;/p&gt;
&lt;p&gt;SEVEN. I’ll close this post with a few highlights from 2017:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;cite&gt;If I Was Your Girl&lt;/cite&gt; by Meredith Musso. Beautifully written, and an all too rare occurence: a trans character written by a trans author, in a mostly happy story.&lt;/li&gt;
&lt;li&gt;&lt;cite&gt;Known Associates&lt;/cite&gt;, by thingswithwings. I guess if I wanted to start reading fics then a 600 pages novel full of queerness was a good pick? I liked it a bunch.&lt;/li&gt;
&lt;li&gt;&lt;cite&gt;Uprooted&lt;/cite&gt; by Naomi Novik. It’s a beautiful and charming fantasy novel, with a serious tone and a lot of character.&lt;/li&gt;
&lt;li&gt;And I’m currently finishing Gail Carriger’s &lt;cite&gt;Parasol Protectorate&lt;/cite&gt; series. Not groundbreaking but good fun that made me want to read book after book.&lt;/li&gt;
&lt;li&gt;Finally, if you work on Web or digital stuff, you should read &lt;a href=&quot;https://abookapart.com/products/accessibility-for-everyone&quot;&gt;Laura Kalbag’s &lt;cite&gt;Accessibility for Everyone&lt;/cite&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It looks like I’m mostly reading books written by women these days. The only exception this year was Douglas Coupland’s &lt;cite&gt;Worst Person Ever&lt;/cite&gt; (it was plain gimmicky and boring, and its transphobic twist soured me forever on this author).&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:414fa430-e4c8-5e98-8cae-97a53112c54a</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/remarkdown"/>
    <title>ReMarkdown.css</title>
    <published>2017-12-02T00:00:00+01:00</published>
    <updated>2017-12-02T00:00:00+01:00</updated>
    <summary>Styling HTML elements as if they were plain Markdown text.</summary>
    <content type="html">&lt;p&gt;ReMarkdown — &lt;a href=&quot;https://fvsch.github.io/remarkdown/&quot;&gt;demo pages&lt;/a&gt;, &lt;a href=&quot;https://www.npmjs.com/package/remarkdown.css&quot;&gt;npm package&lt;/a&gt; — is a stylesheet that renders HTML elements as if they were plain &lt;a href=&quot;https://daringfireball.net/projects/markdown/&quot;&gt;Markdown&lt;/a&gt; text.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Project history&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;ReMarkdown was created in 2011 as a CSS experiment, to see if generated content (&lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; pseudo-elements) could be used to create a complete text style. The goal was to write a kind of Markdown-in-reverse, styling standard HTML as if it were plain text with Markdown-like markers.&lt;/p&gt;
&lt;p&gt;A v2 in 2014 reworked most of the styles and made the library modular using Sass, allowing custom builds with stylistic variants turned on or off.&lt;/p&gt;
&lt;p&gt;Finally, this 2017 update improves the modularity further, and provides 2 default builds so that ReMarkdown styles can be applied with either classes or attributes.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Using ReMarkdown&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;ReMarkdown now comes in two flavours: class-based or attribute-based. The default flavour uses classes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/remarkdown.css/dist/remarkdown.css&quot;&amp;gt;
&amp;lt;div class=&quot;remarkdown&quot;&amp;gt;
  &amp;lt;h1&amp;gt;Default styles&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;remarkdown h1-underline hr-center&quot;&amp;gt;
  &amp;lt;h1&amp;gt;With options&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the &lt;code&gt;attr&lt;/code&gt; flavour uses the &lt;code&gt;data-remarkdown&lt;/code&gt; attribute instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/remarkdown.css/dist/remarkdown.attr.css&quot;&amp;gt;
&amp;lt;div data-remarkdown&amp;gt;
  &amp;lt;h1&amp;gt;Default styles&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div data-remarkdown=&quot;h1-underline hr-center&quot;&amp;gt;
  &amp;lt;h1&amp;gt;With options&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both come with a dozen styles enabled by default (which means you only need &lt;code&gt;class=&quot;remarkdown&quot;&lt;/code&gt; or &lt;code&gt;data-remarkdown&lt;/code&gt; on a container element to apply those styles). &lt;a href=&quot;https://fvsch.github.io/remarkdown/styles.html&quot;&gt;You can see a full list of styles on this page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are also two &lt;code&gt;ReMarkdown-zero&lt;/code&gt; builds which have no default styles enabled, letting you pick all the styles you want explicitly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/remarkdown.css/dist/remarkdown-zero.attr.css&quot;&amp;gt;
&amp;lt;div data-remarkdown&amp;gt;
  &amp;lt;h1&amp;gt;Very basic styles, no Markdown markers&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div data-remarkdown=&quot;
  hn-reset hn-hash h1-underline
  hr-star hr-center
  ul-plus ol-decimal
  code-tick pre-tick
  &quot;&amp;gt;
  &amp;lt;h1&amp;gt;With explicit options&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If these choices are too limited for your needs, you can also create a custom build of ReMarkdown using Sass. See &lt;a href=&quot;https://fvsch.github.io/remarkdown/customize.html&quot;&gt;the “Customizing ReMarkdown” page&lt;/a&gt; for instructions.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:9bdbfea9-a1a1-5e7b-b8bf-62aa8e3c710e</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/svg-css-vars"/>
    <title>Using CSS variables in SVG icons</title>
    <published>2017-01-24T00:00:00+01:00</published>
    <updated>2017-01-24T00:00:00+01:00</updated>
    <summary>Pass several colors and styles to a SVG symbol instance, using CSS Custom Properties. This requires preparing SVG icons to accept specific names.</summary>
    <content type="html">&lt;p&gt;When I started looking into SVG some three years ago, I envisioned this language as &lt;em&gt;images separate from the page&lt;/em&gt; (like JPEG and PNG images are, unless you base64 them), that you could &lt;em&gt;style, because it’s a document, right?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Except it doesn’t quite work like that. If you keep your SVG image as a separate document, you can’t modify it from the HTML document that calls it. &lt;a href=&quot;https://discourse.wicg.io/t/customizing-styles-of-a-svg-document-from-the-outside/192&quot;&gt;I asked around more than two years ago&lt;/a&gt;; at the time there was a &lt;a href=&quot;https://www.w3.org/TR/SVGParam/&quot;&gt;draft about SVG Params&lt;/a&gt;, and Tab Atkins suggested I look into CSS Variables (because future improvements to SVG styling-from-a-parent-document would probably use those). But CSS Variables were only implemented in Firefox and I didn’t manage to make the most of it.&lt;/p&gt;
&lt;p&gt;Well, we now have CSS Variables in Firefox, Chrome and Safari, and coming to an Edge near you sometime in 2017. Plus we have collectively figured out that SVG’s &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;use&amp;gt;&lt;/code&gt; elements were a nice way to use SVG icons from the same HTML page &lt;em&gt;or&lt;/em&gt; from an external symbol sprite, so maybe we can put the two together and it’ll just work?!&lt;/p&gt;
&lt;p&gt;Quick demo (&lt;a href=&quot;https://fvsch.com/articles/svg-css-vars/test.html&quot; target=&quot;_blank&quot;&gt;open in new tab&lt;/a&gt;):&lt;/p&gt;
&lt;figure class=&quot;frame&quot;&gt;&lt;iframe class=&quot;autoheight&quot; src=&quot;https://fvsch.com/articles/svg-css-vars/test.html&quot; width=&quot;100%&quot; height=&quot;500&quot;&gt;&lt;/iframe&gt;
&lt;/figure&gt;
&lt;p&gt;In my tests it does work well in Firefox, Chrome and Safari (as long as they &lt;a href=&quot;http://caniuse.com/#feat=css-variables&quot;&gt;support CSS custom properties&lt;/a&gt;). Haven’t tried with Edge’s preview yet.&lt;/p&gt;
&lt;p&gt;The funny thing is I had totally forgot that this would work. I was sure it wouldn’t for some shadowy Shadow DOM reason, and I actually forgot &lt;a href=&quot;https://fvsch.com/svg-icons/#section-advanced&quot;&gt;I had demonstrated it working last year&lt;/a&gt;. Duh.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:7fca2936-1121-5bff-9002-6df37c883636</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/css-locks"/>
    <title>The math of CSS Locks</title>
    <published>2016-09-01T00:00:00+02:00</published>
    <updated>2016-09-01T00:00:00+02:00</updated>
    <summary>CSS Locks are a Responsive Web Design technique that lets you transition smoothly between two property values when the screen gets smaller or bigger.</summary>
    <content type="html">&lt;p&gt;A CSS lock is a Responsive Web Design technique that lets you transition smoothly between two values, depending on the current viewport size, rather than jump straight from one value to the other.&lt;/p&gt;
&lt;p&gt;This concept was first demonstrated in 2015 by Mike Riethmuller in &lt;a href=&quot;https://madebymike.com.au/writing/precise-control-responsive-typography/&quot;&gt;“Precise control over responsive typography”&lt;/a&gt;, and dubbed “CSS locks” by Tim Brown in &lt;a href=&quot;http://blog.typekit.com/2016/08/17/flexible-typography-with-css-locks/&quot;&gt;“Flexible typography with CSS locks”&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I found this technique in Tim’s article and, when I tried wrapping my head around it, and creating variants of it, I had a hard time figuring out &lt;em&gt;what was going on exactly&lt;/em&gt;. I did a lot of back-of-the-envelope calculations, and I thought it would be useful to share a mathematical explanation.&lt;/p&gt;
&lt;p&gt;I’ll describe the technique, its limitations, and the math that make it work. But don’t worry about the math: it’s basically addition and multiplication, and I’ve broken down the steps as much as possible—also there will be nice graphs.&lt;/p&gt;
&lt;nav id=&quot;toc&quot; data-level=&quot;2&quot;&gt;&lt;/nav&gt;
&lt;h2&gt;&lt;span&gt;What’s a CSS lock?&lt;/span&gt;&lt;/h2&gt;
&lt;h3&gt;Viewport-relative sizes&lt;/h3&gt;
&lt;p&gt;On my latest project I had a hero (full-width) banner with a title on top, and only the “desktop” mock-ups with a big font-size. I figured I wanted smaller text for small screens and something in between for medium screens. So why not make the font-size relative to the viewport width?&lt;/p&gt;
&lt;p&gt;Early techniques for that looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 { font-size: 4vw; /* Boom! Done. */ }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This has two downsides:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the text gets really small on smaller screens (12.8 pixels at 320px) and really big on bigger screens (64px at 1600px);&lt;/li&gt;
&lt;li&gt;it doesn’t follow user preferences for text size.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CSS lock techniques aim to fix the first issue. &lt;em&gt;Great&lt;/em&gt; CSS lock techniques will also try to fix the second issue: following user preferences.&lt;/p&gt;
&lt;h3&gt;The CSS lock concept&lt;/h3&gt;
&lt;p&gt;A CSS lock is a specific kind of CSS value calculation where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;there is a minimum value and a maximum value,&lt;/li&gt;
&lt;li&gt;and two breakpoints (usually based on the viewport width),&lt;/li&gt;
&lt;li&gt;and between those breakpoints, the actual value goes linearly from the minimum to the maximum.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/px-fontsize-complete.png&quot; alt=&quot;Graph of a font-size lock&quot;&gt;&lt;figcaption&gt;“Let’s use a 20px font-size below 320px, a 40px font-size above 960px, and a value going from 20px to 40px in between.”&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;On the CSS side, this can look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 { font-size: 1.25rem; }

@media (min-width: 320px) {
  h1 { font-size: /* magic value from 1.25rem to 2.5rem */; }
}

@media (min-width: 960px) {
  h1 { font-size: 2.5rem; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our first challenge will be to actually implement the &lt;em&gt;magic value&lt;/em&gt;. I’m going to spoil the fun a bit and reveal that it will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 {
  font-size: calc(1.25rem + viewport_relative_value);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;viewport_relative_value&lt;/code&gt; can be a single value (e.g. &lt;code&gt;3vw&lt;/code&gt;) or a more complex calculation (also based on the &lt;code&gt;vw&lt;/code&gt; unit or another viewport unit).&lt;/p&gt;
&lt;h3&gt;Limitations&lt;/h3&gt;
&lt;p&gt;Because they’re based on viewport units, CSS locks have important limitations. &lt;strong&gt;They only work for values that are numbers, can use &lt;code&gt;calc()&lt;/code&gt;, and can accept pixel dimensions.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Why pixel values? Because viewport units (&lt;code&gt;vw&lt;/code&gt;, &lt;code&gt;vh&lt;/code&gt;, &lt;code&gt;vmin&lt;/code&gt; and &lt;code&gt;vmax&lt;/code&gt;) always resolve to a pixel value. For instance if the viewport width is 768px, then &lt;code&gt;1vw&lt;/code&gt; resolves to 7.68px.&lt;/p&gt;
&lt;p&gt;(As a side note, there is a small error in Tim Brown’s article, where he writes that a calculation like &lt;code&gt;100vw - 30em&lt;/code&gt; results in a &lt;code&gt;em&lt;/code&gt; value. It doesn’t: the browser will see &lt;code&gt;100vw&lt;/code&gt; as a pixel value, and will substract how many pixels &lt;code&gt;30em&lt;/code&gt; happen to be for that element and that property.)&lt;/p&gt;
&lt;p&gt;Some examples of things that will &lt;em&gt;not&lt;/em&gt; work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a CSS lock for the &lt;code&gt;opacity&lt;/code&gt; property, because &lt;code&gt;opacity: calc(.5+1px)&lt;/code&gt; is an error;&lt;/li&gt;
&lt;li&gt;a CSS lock for most &lt;code&gt;transform&lt;/code&gt; functions (e.g. &lt;code&gt;rotate&lt;/code&gt;: one can’t rotate by a pixel value).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Okay, so this pixel restriction looks fairly limiting, but maybe some brave souls will go ahead and identify all the properties and techniques that &lt;em&gt;can&lt;/em&gt; make use of CSS locks.&lt;/p&gt;
&lt;p&gt;To get the fun started, we’ll look at the &lt;code&gt;font-size&lt;/code&gt; and &lt;code&gt;line-height&lt;/code&gt; properties, and how we can build CSS locks for these with either pixel-based or em-based breakpoints.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;CSS locks with pixel breakpoints&lt;/span&gt;&lt;/h2&gt;
&lt;h3&gt;The demos&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fvsch.com/articles/css-locks/demo1.html&quot;&gt;CSS calc lock for font-size (rem+px, px MQ)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fvsch.com/articles/css-locks/demo2.html&quot;&gt;CSS calc lock for line-height (em+px, px MQ)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fvsch.com/articles/css-locks/demo3.html&quot;&gt;Combined font-size and line-height lock (px-based)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the next sub-sections, we’ll explain how we arrived at the CSS code for each example.&lt;/p&gt;
&lt;h3&gt;Font size as a linear function&lt;/h3&gt;
&lt;p&gt;We want the font-size to grow proportionally between two points: 20px at 320px, and 40px at 960px. On a graph, we can write down our two points and draw a line:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/px-fontsize-linear.png&quot; alt=&quot;Graph of the linear font-size&quot;&gt;&lt;/figure&gt;
&lt;p&gt;What we’re seeing here in red is a simple linear function. We can write it as &lt;code&gt;y = mx + b&lt;/code&gt;, where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;y&lt;/code&gt; is our font size (vertical axis),&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x&lt;/code&gt; is the viewport width, in pixels (horizontal axis),&lt;/li&gt;
&lt;li&gt;&lt;code&gt;m&lt;/code&gt; is the &lt;em&gt;slope&lt;/em&gt; of the function (“how many pixels do we add to the font size for each increase of 1px to the viewport width?”),&lt;/li&gt;
&lt;li&gt;and &lt;code&gt;b&lt;/code&gt; is the font-size before we add any viewport-based value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What we would like to do is figure out the respective values of &lt;code&gt;m&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. They’re the parts of the equation that don’t change.&lt;/p&gt;
&lt;p&gt;Let’s find the value of &lt;code&gt;m&lt;/code&gt; first. All we need is two &lt;code&gt;(x,y)&lt;/code&gt; data points. It’s kind of like working out a speed (distance over time), but here it’s font-size over viewport width:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m = font_size_increase / viewport_increase
m = (y2 - y1) / (x2 - x1)
m = (40 - 20) / (960 - 320)
m = 20 / 640
m = 0.03125&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another way to express this calculation is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The total increase in font-size is 20px (&lt;code&gt;40 - 20&lt;/code&gt;).
The total increase in viewport width is 640px (&lt;code&gt;960 - 320&lt;/code&gt;).
If the viewport width was growing by just 1px, how big would the increase in font-size be? It would be &lt;code&gt;20 / 640 = 0.03125px&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now let’s calculate &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;y = mx + b
b = y - mx
b = y - 0.03125x&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since both known points verify our function, we can use the &lt;code&gt;(x,y)&lt;/code&gt; values from the first or the second point. Let’s take the first one:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b = y1 - 0.03125 × x1
b = 20 - 0.03125 × 320
b = 10&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the record, we could have found this &lt;code&gt;10px&lt;/code&gt; value just by looking at the graph. But we don’t always have a graph ready. :)&lt;/p&gt;
&lt;p&gt;Anyway, our complete linear function is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;y = 0.03125x + 10&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Translating to CSS&lt;/h3&gt;
&lt;p&gt;How do we translate our function to CSS syntax? We know that &lt;code&gt;y&lt;/code&gt; is the font-size, and that if we want to do basic operations in CSS we need &lt;code&gt;calc()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;font-size: calc( 0.03125x + 10px );&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not too bad. Of course it’s pseudo-CSS, because &lt;code&gt;x&lt;/code&gt; is not valid CSS syntax. But in our linear function, &lt;code&gt;x&lt;/code&gt; represents the viewport width, which can be expressed in CSS as &lt;code&gt;100vw&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;font-size: calc( 0.03125 * 100vw + 10px );&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;em&gt;this&lt;/em&gt; is working CSS. If we want a shorter style, we can resolve that multiplication. Since &lt;code&gt;0.03125 × 100 = 3.125&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;font-size: calc( 3.125vw + 10px );&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course we want to restrict this style to viewport widths between 320px and 960px. So let’s add some media queries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 { font-size: 20px; }

@media (min-width: 320px) {
  h1 { font-size: calc( 3.125vw + 10px ); }
}

@media (min-width: 960px) {
  h1 { font-size: 40px; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now our graph looks like the one shown in the introduction.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/px-fontsize-complete.png&quot; alt=&quot;&quot;&gt;&lt;/figure&gt;
&lt;p&gt;That’s nice, although I’m not too happy about those pixel values for a &lt;em&gt;font size&lt;/em&gt; declaration; can we do better?&lt;/p&gt;
&lt;h3&gt;Factoring user preferences in&lt;/h3&gt;
&lt;p&gt;Virtually every web browser lets users ask for smaller or bigger default text. The most common, default value is &lt;code&gt;16px&lt;/code&gt;, but users might change it to anything else (generally to bigger text).&lt;/p&gt;
&lt;p&gt;I’d like to introduce this user preference in our formula, and I’m going to use &lt;code&gt;rem&lt;/code&gt; values for that purpose. Note that similar principles apply with &lt;code&gt;em&lt;/code&gt; or percentage values.&lt;/p&gt;
&lt;p&gt;First thing to do is to make sure that the &lt;em&gt;root font-size&lt;/em&gt; was not overwritten with an absolute value. For instance, if you’re using Bootstrap 3’s CSS, there’s a bit of code like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;html {
  font-size: 10px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Do not ever do this!&lt;/strong&gt; (Thankfully, this will be fixed in Bootstrap 4.) If for any reason you really want to redefine the &lt;em&gt;root em&lt;/em&gt; (&lt;code&gt;1rem&lt;/code&gt;) value, you could use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/*
 * Redefine the rem value while keeping it proportional.
 * Useful values, with default font-size of 16px:
 * • 62.5% -&amp;gt; 1rem = 10px, .1rem  = 1px
 * • 125%  -&amp;gt; 1rem = 20px, .05rem = 1px
 */
html {
  font-size: 62.5%;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That being said, we’re going to leave the root font-size alone, so that by default it will be equal to 16px. Let’s see what happens if we replace our pixel values with &lt;code&gt;rem&lt;/code&gt; values in our font-size lock.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/*
 * With default user settings:
 * • 0.625rem = 10px
 * • 1.25rem  = 20px
 * • 2.5rem   = 40px
 */
h1 { font-size: 1.25rem; }

@media (min-width: 320px) {
  h1 { font-size: calc( 3.125vw + .625rem ); }
}

@media (min-width: 960px) {
  h1 { font-size: 2.5rem; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we try this code with default browser settings, we can see that it behaves like our previous pixel-based code. Great!&lt;/p&gt;
&lt;p&gt;But since we’re doing this to support user &lt;em&gt;changes&lt;/em&gt;, we should check how that works too. Say we have a user who configured their browser to use &lt;code&gt;24px&lt;/code&gt; instead of &lt;code&gt;16px&lt;/code&gt; as the default font-size (a 50% increase); how will the above code react? Let’s graph that:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/px-fontsize-bigger-buggy.png&quot; alt=&quot;Graph showing disconnected lines&quot;&gt;&lt;figcaption&gt;
    Dotted blue line: with a base font-size of 16px.&lt;br&gt;
    Solid red line: with a base font-size of 24px.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;At the 320px breakpoint the font size actually becomes &lt;em&gt;smaller&lt;/em&gt; (jumping from 30px to 25px), and there’s a &lt;em&gt;big&lt;/em&gt; jump at the higher breakpoint (45px to 60px). &lt;em&gt;Woops.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To fix that, we can use the same user-configurable baseline for all 3 sizes. For instance, we could pick a &lt;code&gt;1.25rem&lt;/code&gt; baseline:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 { font-size: 1.25rem; }

@media (min-width: 320px) {
  h1 { font-size: calc( 1.25rem + 3.125vw - 10px ); }
}

@media (min-width: 960px) {
  h1 { font-size: calc( 1.25rem + 20px ); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the &lt;code&gt;3.125vw - 10px&lt;/code&gt; part? It’s our old linear function (of the form &lt;code&gt;mx + b&lt;/code&gt;), but with a different value for &lt;code&gt;b&lt;/code&gt;; let’s call it &lt;code&gt;b′&lt;/code&gt;. In our case, since we know that our baseline value is equivalent to 20px, we can get the &lt;code&gt;b′&lt;/code&gt; value with a simple substraction:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b′ = b - baseline_value
b′ = 10 - 20
b′ = 10&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another strategy is to pick your baseline value &lt;em&gt;before anything else&lt;/em&gt;, then look for the linear function that describes the font-size &lt;em&gt;increase&lt;/em&gt; (I’m calling it &lt;code&gt;y′&lt;/code&gt; to differentiate it from the complete font-size &lt;code&gt;y&lt;/code&gt;). Let’s try that quickly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x1 = 320
x2 = 960

y′1 = 0
y′2 = 20

m = (y′2 - y′1) / (x2 - x1)
m = (20 - 0) / (960 - 320)
m = 20 / 640
m = 0.03125

b′ = y′ - mx
b′ = y′1 - 0.03125 × x1
b′ = 0 - 0.03125 × 320
b′ = -10&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We end up with &lt;code&gt;y′ = 0.03125x - 10&lt;/code&gt;, which looks like this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/px-fontsize-increase.png&quot; alt=&quot;Graph of the y′ = 0.025x - 10 function&quot;&gt;&lt;/figure&gt;
&lt;p&gt;With our base value in &lt;code&gt;rem&lt;/code&gt; and additional sizes in &lt;code&gt;vw&lt;/code&gt; and/or &lt;code&gt;px&lt;/code&gt;, we finally get a fully working &lt;code&gt;font-size&lt;/code&gt; lock. When the user changes their base font-size, the whole thing goes up or down and doesn’t break. Success!&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/px-fontsize-bigger-fixed.png&quot; alt=&quot;Graph showing parallel series of connected lines&quot;&gt;&lt;figcaption&gt;
    Dashed magenta line: the raw font-size increase.&lt;br&gt;
    Dotted blue line: with a base font-size of 16px.&lt;br&gt;
    Solid red line: with a base font-size of 24px.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Of course it’s not &lt;em&gt;exactly&lt;/em&gt; what the user asked for: they wanted 50% bigger fonts, and we gave them a font size that is 50% bigger on smaller viewports, and only 25% bigger on bigger viewports. But it’s a good compromise nonetheless.&lt;/p&gt;
&lt;h3&gt;Making a line-height lock&lt;/h3&gt;
&lt;p&gt;For this part, our scenario is going to be: “We want paragraphs with a 1.4 line-height at 320px and a 1.8 line-height at 960px”.&lt;/p&gt;
&lt;p&gt;Since we’ll be working with a base value &lt;em&gt;plus a dynamic value expressed in pixels&lt;/em&gt;, we need to know how many pixels those 1.4 and 1.8 ratios refer to. Which means we need to know the &lt;code&gt;font-size&lt;/code&gt; of our paragraphs. Let’s say that our paragraphs use the default font-size, so probably 16px. Our data points are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;16 * 1.4 = 22.4&lt;/code&gt; pixels at the lower breakpoint (&lt;code&gt;320px&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;16 * 1.8 = 28.8&lt;/code&gt; pixels at the higher breakpoint (&lt;code&gt;960px&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll also use &lt;code&gt;1.4em = 22.4px&lt;/code&gt; as our baseline. So what we are looking at here is an increase of 6.4px. We can work out our linear formula, as before:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x1 = 320
x2 = 960

y′1 = 0
y′2 = 6.4

m = (y′2 - y′1) / (x2 - x1)
m = (6.4 - 0) / (960 - 320)
m = 6.4 / 640
m = 0.01

b′ = y′ - mx
b′ = y′1 - 0.01 × x1
b′ = 0 - 0.01 × 320
b′ = 3.2

y′ = 0.01x - 3.2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Transposing to CSS, we get:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;line-height: calc( 1.4em + 1vw - 3.2px );&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; our baseline value must be expressed as &lt;code&gt;1.4em&lt;/code&gt; inside of &lt;code&gt;calc()&lt;/code&gt; expressions; the unitless ratio (&lt;code&gt;1.4&lt;/code&gt;) notation won’t work inside &lt;code&gt;calc()&lt;/code&gt; (all browsers), and the percent notation (&lt;code&gt;140%&lt;/code&gt;) works in Chrome and Firefox but not in Safari.&lt;/p&gt;
&lt;p&gt;Then we add media queries, and make sure that &lt;em&gt;all&lt;/em&gt; declarations for the &lt;code&gt;line-height&lt;/code&gt; value use the same baseline (&lt;code&gt;1.4em&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;p { line-height: 1.4em; }

@media (min-width: 320px) {
  p { line-height: calc( 1.4em + 1vw - 3.2px ); }
}

@media (min-width: 960px) {
  p { line-height: calc( 1.4em + 6.4px ); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Reminder:&lt;/strong&gt; for the high value, we can’t just use &lt;code&gt;1.8em&lt;/code&gt;, because we need the part that is added to our base to be expressed in pixels. If we used &lt;code&gt;1.8em&lt;/code&gt; (or &lt;code&gt;1.8&lt;/code&gt; or &lt;code&gt;180%&lt;/code&gt;), the result would be alright for a base font size of 16px but would break when the user changed it.&lt;/p&gt;
&lt;p&gt;We can graph our function, and check that it does work with a different base font-size.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/px-lineheight-complete.png&quot; alt=&quot;Graph showing two set of connected lines.&quot;&gt;&lt;figcaption&gt;
    Dotted blue line: with a base font-size of 16px.&lt;br&gt;
    Solid red line: with a base font-size of 24px.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Finally, since our &lt;code&gt;line-height&lt;/code&gt; formula depends on the element’s own &lt;code&gt;font-size&lt;/code&gt;, if we change that font size we have to change the formula. For instance in &lt;a href=&quot;https://fvsch.com/articles/css-locks/demo2.html&quot;&gt;our line-height demo&lt;/a&gt; there is a paragraph with bigger text, defined like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.big {
  font-size: 1.66em;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This changes our data points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;16 * 1.66 * 1.4 = 37.184&lt;/code&gt; pixels at the lower breakpoint (&lt;code&gt;320px&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;16 * 1.66 * 1.8 = 47.808&lt;/code&gt; pixels at the higher breakpoint (&lt;code&gt;960px&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can run the calculations and get this updated formula: &lt;code&gt;y′ = 0.0166x - 5.312&lt;/code&gt;. Then, combining the previous style and this one in our CSS, we get:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;p { line-height: 1.4em; }
.big { font-size: 1.66em; }

@media (min-width: 320px) {
  p    { line-height: calc( 1.4em + 1vw - 3.2px ); }
  .big { line-height: calc( 1.4em + 1.66vw - 5.312px ); }
}

@media (min-width: 960px) {
  p    { line-height: calc( 1.4em + 6.4px ); }
  .big { line-height: calc( 1.4em + 10.624px ); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another option is to let CSS do the calculations. Since we’re using the same breakpoints and relative line-heights as for standard paragraphs, we just need to add a 1.66 factor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;p { line-height: 1.4em; }
.big { font-size: 1.66em; }

@media (min-width: 320px) {
  p    { line-height: calc( 1.4em + 1vw - 3.2px ); }
  .big { line-height: calc( 1.4em + (1vw - 3.2px) * 1.66 ); }
}
@media (min-width: 960px) {
  p    { line-height: calc( 1.4em + 6.4px ); }
  .big { line-height: calc( 1.4em + 6.4px * 1.66 ); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Combining font-size and line-height locks&lt;/h3&gt;
&lt;p&gt;Okay let’s try to put it all together. Here’s our scenario: we have a fluid column of text with a H1 and a few paragraphs, and we’re going to change the font-size and line-height using the following values:&lt;/p&gt;
&lt;table&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;Element and property&lt;/th&gt;
        &lt;th scope=&quot;col&quot;&gt;Value at 320px&lt;/th&gt;
        &lt;th scope=&quot;col&quot;&gt;Value at 960px&lt;/th&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;H1 font-size&lt;/td&gt;
        &lt;td&gt;24px&lt;/td&gt;
        &lt;td&gt;40px&lt;/td&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;H1 line-height&lt;/td&gt;
        &lt;td&gt;1.33em&lt;/td&gt;
        &lt;td&gt;1.2em&lt;/td&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;P font-size&lt;/td&gt;
        &lt;td&gt;15px&lt;/td&gt;
        &lt;td&gt;18px&lt;/td&gt;
    &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;P line-height&lt;/td&gt;
        &lt;td&gt;1.5em&lt;/td&gt;
        &lt;td&gt;1.66em&lt;/td&gt;
    &lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;You’ll notice that we’re doing two different things with the line heights. As a general rule, when text becomes bigger we should make the line-height tighter, and when a column becomes wider we should make the line-height more loose. But in our scenario, both principles apply at the same time and contradict each other! So we have to make a prioritize one aspect over the other:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For the H1, we feel that the font-size increase will be more dramatic than the column width increase.&lt;/li&gt;
&lt;li&gt;For the paragraphs, we feel that the column width increase will be more dramatic than the subtle font-size increase.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, let’s pick two breakpoints. I’ll go with 320px and 960px again, yay. Let’s start by writing the font-size locks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 { font-size: 1.5rem; }
/* .9375rem = 15px with default settings */
p { font-size: .9375rem; }

@media (min-width: 320px) {
  h1 { font-size: calc( 1.5rem + 2.5vw - 8px ); }
  /* .46875vw - 1.5px results in a value from 0 to 3px */
  p { font-size: calc( .9375rem + .46875vw - 1.5px ); }
}
@media (min-width: 960px) {
  h1 { font-size: calc(1.5rem + 16px); }
  p { font-size: calc( .9375rem + 3px ); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing new here, except for the different values.&lt;/p&gt;
&lt;p&gt;Next, calculating the &lt;code&gt;line-height&lt;/code&gt; locks is going to get a bit harder than what we did last time.&lt;/p&gt;
&lt;p&gt;Let’s begin with our H1 element. We’d like to use a relative baseline value for the &lt;code&gt;line-height&lt;/code&gt;, so we take the lowest value, &lt;code&gt;1.2em&lt;/code&gt;. Since the element’s font size is variable, that &lt;code&gt;1.2em&lt;/code&gt; is going to describe a dynamic and linear value, characterized by two points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;24 × 1.2 = 28.8px&lt;/code&gt; at the lower breakpoint,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;40 × 1.2 = 48px&lt;/code&gt; at the higher breakpoint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also know that at the lower breakpoint, we want the &lt;code&gt;line-height&lt;/code&gt; to be &lt;code&gt;1.33em&lt;/code&gt;, which we can round to just 32px.&lt;/p&gt;
&lt;p&gt;We would like to find a linear function that describes “what we add to the 1.20em baseline”. If we remove this 1.2em baseline from our data points, we have two modified data points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;24 × (1.3333 - 1.2) = 3.2px&lt;/code&gt; at the lower breakpoint,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;40 × (1.2 - 1.2) = 0px&lt;/code&gt; at the higher breakpoint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We should end up with a negative slope. Let’s calculate that.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m = (y′2 - y′1) / (x2 - x1)
m = (0 - 3.2) / (960 - 320)
m = -3.2 / 640
m = -0.005

b′ = y′ - mx
b′ = y′1 - (-0.005 × x1)
b′ = 3.2 + 0.005 × 320
b′ = 4.8

y′ = -0.005x + 4.8&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Transposed to CSS, we get:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 {
  line-height: calc( 1.2em - .5vw + 4.8px );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s look at our function on a graph, and see how it relates to the related font-size function.&lt;/p&gt;
&lt;figure&gt;&lt;img class=&quot;border&quot; width=&quot;540&quot; src=&quot;https://fvsch.com/articles/css-locks/px-combined-h1.png&quot; alt=&quot;Graph with 3 linear functions&quot;&gt;&lt;figcaption&gt;
    Dotted blue line: the line-height decrease.&lt;br&gt;
    Dashed red line: our line-height baseline (120% of the title’s font-size).&lt;br&gt;
    Solid magenta line: the final line-height.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Looking at this graph, we can see that the final line-height (magenta line) is equal to the 120% baseline (red dashes) plus the line-height decrease (blue dots). You can look at &lt;a href=&quot;http://www.graphsketch.com/?eqn1_color=2&amp;amp;eqn1_eqn=(16%20%2B%200.025x)*1.2&amp;amp;eqn2_color=1&amp;amp;eqn2_eqn=-0.005x%20%2B%204.8&amp;amp;eqn3_color=5&amp;amp;eqn3_eqn=(16%20%2B%200.025x)*1.2%20%2B%20(-0.005x%20%2B%204.8)&amp;amp;eqn4_color=6&amp;amp;eqn4_eqn=&amp;amp;eqn5_color=6&amp;amp;eqn5_eqn=&amp;amp;eqn6_color=6&amp;amp;eqn6_eqn=&amp;amp;x_min=-80&amp;amp;x_max=1200&amp;amp;y_min=-10&amp;amp;y_max=65&amp;amp;x_tick=80&amp;amp;y_tick=5&amp;amp;x_label_freq=4&amp;amp;y_label_freq=2&amp;amp;do_grid=0&amp;amp;do_grid=1&amp;amp;bold_labeled_lines=0&amp;amp;line_width=3&amp;amp;image_w=1200&amp;amp;image_h=800&quot;&gt;these equations on GraphSketch.com&lt;/a&gt; and check for yourself.&lt;/p&gt;
&lt;p&gt;For the paragraphs, we’re going to use &lt;code&gt;1.5em&lt;/code&gt; as our baseline. The line-height increase we want is: &lt;code&gt;(1.75 - 1.5) × 18 = 4.5px&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img class=&quot;border&quot; width=&quot;540&quot; src=&quot;https://fvsch.com/articles/css-locks/px-combined-soulver.png&quot; alt=&quot;Screenshot of the Soulver app&quot;&gt;&lt;figcaption&gt;&lt;a href=&quot;http://acqualia.com/soulver/&quot;&gt;My calculator&lt;/a&gt; tells me that the formula is:&lt;br&gt;&lt;code&gt;y′ = 0.00703125x - 2.25&lt;/code&gt;
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;To see the complete CSS code, &lt;a href=&quot;https://fvsch.com/articles/css-locks/demo3.html&quot;&gt;look at the combined font-size plus line-height demo&lt;/a&gt; and its source. While resizing the browser’s window, you should see that the effect is subtle but is definitively working.&lt;/p&gt;
&lt;p&gt;I also recommend that you test this demo after changing your browser’s base font size. Note that in that situation the exact line-height ratios are slightly different, but they still look alright; and there’s no danger of the line-height becoming smaller than the baseline value.&lt;/p&gt;
&lt;h3&gt;Automating the calculations&lt;/h3&gt;
&lt;p&gt;Throughout this part I’ve been doing all the calculations by hand or using a calculator such as Soulver.&lt;/p&gt;
&lt;p&gt;But this process can be tedious and error-prone. Could we automate it, to reduce the chance of human error?&lt;/p&gt;
&lt;p&gt;Our first option would be to do the whole calculation in CSS. This is a variant of the formula we’ve used in our font-size examples, with all the values spelled out:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (min-width: 320px) and (max-width: 959px) {
  h1 {
    font-size: calc(
      /* y1 */
      1.5rem
      /* + m × x */
      + ((40 - 24) / (960 - 320)) * 100vw
      /* - m × x1 */
      - ((40 - 24) / (960 - 320)) * 320px
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s a bit verbose, and can be reduced further to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (min-width: 320px) and (max-width: 959px) {
  h1 {
    font-size: calc( 1.5rem + 16 * (100vw - 320px) / (960 - 320) );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works for a combined font-size and line-height, too, but it might be less intuitive, especially with a negative slope.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (min-width: 320px) and (max-width: 959px) {
  h1 {
    font-size: calc( 1.5rem + 16 * (100vw - 320px) / (960 - 320) );
    /* For a negative slope, we have to invert the breakpoints */
    line-height: calc( 1.2em + 3.2 * (100vw - 960px) / (320 - 960) );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our second option would be to automate these calculations with a Sass mixin or a PostCSS plugin. Sadly I don’t have one to offer right now, but if you’re taking a shot at it let me know and I’ll add it to this article.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;CSS locks with em breakpoints&lt;/span&gt;&lt;/h2&gt;
&lt;h3&gt;The demos, reloaded&lt;/h3&gt;
&lt;p&gt;I’ve taken our first 3 demos and ported them from &lt;code&gt;px&lt;/code&gt;-based breakpoints and value increments to &lt;code&gt;em&lt;/code&gt;-based breakpoints and &lt;code&gt;rem&lt;/code&gt;-based value increments.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fvsch.com/articles/css-locks/demo5.html&quot;&gt;CSS calc lock for font-size (rem+rem, em MQ)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fvsch.com/articles/css-locks/demo6.html&quot;&gt;CSS calc lock for line-height (em+rem, px MQ)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fvsch.com/articles/css-locks/demo7.html&quot;&gt;Combined font-size and line-height lock (em/rem-based)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the next sub-sections we’ll describe how the specific syntax used for these demos works.&lt;/p&gt;
&lt;h3&gt;Don’t use &lt;code&gt;em&lt;/code&gt; media queries with &lt;code&gt;m × 100vw&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Remember the &lt;code&gt;m × 100vw&lt;/code&gt; syntax we used in section II (for instance in code looking like &lt;code&gt;calc(base + 2.5vw)&lt;/code&gt;)? We can’t use that with &lt;code&gt;em&lt;/code&gt;-based media queries.&lt;/p&gt;
&lt;p&gt;This is because in the context of a media query, both the &lt;code&gt;em&lt;/code&gt; and the &lt;code&gt;rem&lt;/code&gt; unit refer to one thing: the User Agent’s base font size. Which is — as we’ve said a few times already in this article — usually 16px, but it might be smaller or bigger depending on two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Browser or OS choices (mostly for special cases like TV browsers, and some e-readers).&lt;/li&gt;
&lt;li&gt;User preference.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means that if we have two breakpoints at &lt;code&gt;20em&lt;/code&gt; and &lt;code&gt;60em&lt;/code&gt;, the actual CSS widths at which they will match are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;320px and 960px for a base font size of 16px,&lt;/li&gt;
&lt;li&gt;480px and 1440px for a base font size of 24px,&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Note that these are &lt;em&gt;CSS pixels&lt;/em&gt;, and not &lt;em&gt;device pixels&lt;/em&gt;. We’re not concerned with device pixels in this article as they don’t impact our calculations.)&lt;/p&gt;
&lt;p&gt;In section II, we had examples looking like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;font-size: calc( 3.125vw + .625rem );&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we take this syntax and replace all breakpoints with &lt;code&gt;em&lt;/code&gt;-based breakpoints, by supposing that &lt;code&gt;1em&lt;/code&gt; in a media query is 16px, we could have code like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 { font-size: 1.25rem; }

/* Don’t do this :((( */
@media (min-width: 20em) {
  h1 { font-size: calc( 1.25rem + 3.125vw - 10px ); }
}

/* Or this. */
@media (min-width: 60em) {
  h1 { font-size: calc( 1.25rem + 20px ); }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would indeed work if the operating system, browser and user never changed the base font size. But if for any reason it was different, mayhem would ensue.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/em-fontsize-px.png&quot; alt=&quot;Graph showing 3 disjointed lines&quot;&gt;&lt;figcaption&gt;
    Dotted blue line: result with a base font size of 16px.&lt;br&gt;
    Solid red line: result with a base font size of 24px.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;What’s happening here? When we change the base font-size, our &lt;code&gt;em&lt;/code&gt;-based breakpoints move to bigger pixel values. Our &lt;code&gt;3.125vw - 10px&lt;/code&gt; value is only true for specific pixel breakpoints, though!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At 320px, &lt;code&gt;3.125vw - 10px&lt;/code&gt; is 0px, as planned.&lt;/li&gt;
&lt;li&gt;At 480px, on the other hand, &lt;code&gt;3.125vw - 10px&lt;/code&gt; is 5px.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Things get worse at the higher breakpoint:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At 960px, &lt;code&gt;3.125vw - 10px&lt;/code&gt; is 20px, is expected.&lt;/li&gt;
&lt;li&gt;At 1440px, &lt;code&gt;3.125vw - 10px&lt;/code&gt; is 35px (15px too big).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we want to use &lt;code&gt;em&lt;/code&gt;-based breakpoints, we’re going to need a different technique.&lt;/p&gt;
&lt;h3&gt;Doing the math again&lt;/h3&gt;
&lt;p&gt;This technique, demonstrated in &lt;a href=&quot;https://madebymike.com.au/writing/precise-control-responsive-typography/&quot;&gt;Mike Riethmuller’s article&lt;/a&gt;, relies on letting CSS do much of the calculation, using two variable parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;100vw&lt;/code&gt;, the viewport width;&lt;/li&gt;
&lt;li&gt;the lower breakpoint, expressed in &lt;code&gt;rem&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The formula we’re going to use is:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;y = m × (x - x1) / (x2 - x1)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;How do we end up with this formula? Let’s retrace our steps a bit. In section II we showed that our font-size or line-height could be described as a linear function:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;y = mx + b&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In CSS, we can work with &lt;code&gt;x&lt;/code&gt; (that’s &lt;code&gt;100vw&lt;/code&gt;). But we can’t resolve &lt;code&gt;m&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; to precise &lt;code&gt;px&lt;/code&gt; or a &lt;code&gt;vw&lt;/code&gt; values, because those are fixed pixel quantities and they would end up mismatched with our &lt;code&gt;em&lt;/code&gt;-based breakpoints if the user ever changed the base font size.&lt;/p&gt;
&lt;p&gt;So what we want is to see if we can replace &lt;code&gt;m&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; with other known values, namely our two data points, &lt;code&gt;(x1,y1)&lt;/code&gt; and &lt;code&gt;(x2,y2)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We already showed how to find &lt;code&gt;b&lt;/code&gt; from one point in the function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b = y - mx
b = y1 - m × x1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Putting the two together:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;y = mx + b
y = mx + y1 - m × x1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ve eliminated &lt;code&gt;b&lt;/code&gt; from the equation, yay!&lt;/p&gt;
&lt;p&gt;Also, in section II we showed that what we really needed was not the complete &lt;code&gt;font-size&lt;/code&gt; or &lt;code&gt;line-height&lt;/code&gt; value, but the &lt;em&gt;dynamic part&lt;/em&gt; that we add to a &lt;em&gt;baseline value&lt;/em&gt;. We called that dynamic part &lt;code&gt;y′&lt;/code&gt;, and we can express it as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;y  = y1 + y′
y′ = y - y1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replacing &lt;code&gt;y&lt;/code&gt; with the equation we found just before:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;y′ = mx + y1 - m × x1 - y1
y′ = mx + y1 - m × x1 - y1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hey look, we can eliminate the &lt;code&gt;+ y1 - y1&lt;/code&gt; parts!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;y′ = m × x - m × x1
y′ = m × (x - x1)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is going really well. Now, can we replace &lt;code&gt;m&lt;/code&gt; with values we actually know? We showed earlier that:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;m = (y2 - y1) / (x2 - x1)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;y′ = (y2 - y1) / (x2 - x1) × (x - x1)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Which we can also write as:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;y′ = max_value_increase × (x - x1) / (x2 - x1)&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Translating to CSS&lt;/h3&gt;
&lt;p&gt;Now that’s a value we can use in CSS. Going back to our 20px-to-40px example, we could write it down as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (min-width: 20em) and (max-width: 60em) {
  h1 {
    /* WARNING: this doesn’t work yet! */
    font-size: calc(
      1.25rem /* baseline value */
      + 20px /* difference between max value and baseline */
      * (100vw - 20rem) /* x - x1 */
      / (60rem - 20rem) /* x2 - x1 */
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code doesn’t work yet. It looks like it &lt;em&gt;might&lt;/em&gt; work, but &lt;code&gt;calc()&lt;/code&gt; in CSS has a number of restrictions when it comes to how you can do multiplications and divisions with the same or with different values.&lt;/p&gt;
&lt;p&gt;Let’s start with the &lt;code&gt;100vw - 20rem&lt;/code&gt; fragment; this part works as is, and will return a pixel value.&lt;/p&gt;
&lt;p&gt;For instance if the base font-size is 16px and the viewport width is 600px, the result will be 280px (&lt;code&gt;600 - 20 × 15&lt;/code&gt;). If the base font-size is 24px and the viewport width is 600px, the result will be 120px (&lt;code&gt;600 - 20 × 24&lt;/code&gt;).&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/em-moving-breakpoint.png&quot; alt=&quot;Representation of the width in pixels of 100vw - 20rem&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Note that is that we’re using the &lt;code&gt;rem&lt;/code&gt; unit to express our breakpoints. Why not &lt;code&gt;em&lt;/code&gt;, you ask? Because in a CSS value, &lt;code&gt;em&lt;/code&gt; will &lt;em&gt;not&lt;/em&gt; refer to the base font-size, but instead it refers to the element’s own font-size (generally) or to its parent font-size (when used in the &lt;code&gt;font-size&lt;/code&gt; property).&lt;/p&gt;
&lt;p&gt;Ideally, we would need a CSS unit that refers to the browser’s base font-size, but this unit doesn’t exist. The closest we have is &lt;code&gt;rem&lt;/code&gt;, and it will only refer to that base font-size &lt;em&gt;if it was left completely unchanged&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That means you must make sure you don’t have code like this in your CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* Bad */
html { font-size: 10px; }

/* Equally bad */
:root { font-size: 16px; }

/* Manageable, but we&apos;ll have to write all
   our breakpoints as e.g. 20rem/1.25,
   40em/1.25, etc. */
:root { font-size: 125%; }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Things get a bit more complicated on the other side of the division.&lt;/p&gt;
&lt;h3&gt;Unitless &lt;code&gt;calc&lt;/code&gt; divisors and factors&lt;/h3&gt;
&lt;p&gt;Ideally, we would like the &lt;code&gt;60rem - 20rem&lt;/code&gt; part to resolve to a pixel width. This would mean that the whole &lt;code&gt;(x - x1) / (x2 - x1)&lt;/code&gt; division would resolve to a value between 0 and 1. Let’s call this value &lt;code&gt;n&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For instance, with a base font-size of 16px and a viewport width width of 600px, we would get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = (x - x1) / (x2 - x1)
n = (600 - 320) / (960 - 320)
n = 280 / 640
n = 0.475&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sadly, it doesn’t quite work like that.&lt;/p&gt;
&lt;p&gt;The main reason is that you can’t use pixels, or any CSS unit, for the divisor of a &lt;code&gt;calc()&lt;/code&gt; division. (The divisor is the part on the right. Don’t worry if you didn’t remember that from school, I had to look it up.) You can only divide by a unitless value. So what are our options here?&lt;/p&gt;
&lt;p&gt;What if we just remove the units in the divisor? What would be the result of &lt;code&gt;calc((100vw - 20rem)/(60 - 20))&lt;/code&gt;?&lt;/p&gt;
&lt;table&gt;&lt;caption&gt;Given a base font-size of 16px&lt;/caption&gt;
  &lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;Viewport width&lt;/th&gt;
    &lt;th scope=&quot;col&quot;&gt;CSS division&lt;/th&gt;
    &lt;th scope=&quot;col&quot;&gt;Result&lt;/th&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;20em (320px)&lt;/td&gt;
    &lt;td&gt;(320px - 16px × 20) / (60 - 20)&lt;/td&gt;
    &lt;td&gt;= 0px&lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;40em (640px)&lt;/td&gt;
    &lt;td&gt;(640px - 16px × 20) / (60 - 20)&lt;/td&gt;
    &lt;td&gt;= 8px&lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;60em (960px)&lt;/td&gt;
    &lt;td&gt;(960px - 16px × 20) / (60 - 20)&lt;/td&gt;
    &lt;td&gt;= 16px&lt;/td&gt;
  &lt;/tr&gt;&lt;/table&gt;
&lt;table&gt;&lt;caption&gt;Given a base font-size of 24px&lt;/caption&gt;
  &lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;Viewport width&lt;/th&gt;
    &lt;th scope=&quot;col&quot;&gt;CSS division&lt;/th&gt;
    &lt;th scope=&quot;col&quot;&gt;Result&lt;/th&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;20em (480px)&lt;/td&gt;
    &lt;td&gt;(480px - 24px × 20) / (60 - 20)&lt;/td&gt;
    &lt;td&gt;= 0px&lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;40em (960px)&lt;/td&gt;
    &lt;td&gt;(960px - 24px × 20) / (60 - 20)&lt;/td&gt;
    &lt;td&gt;= 12px&lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;60em (1440px)&lt;/td&gt;
    &lt;td&gt;(1440px - 24px × 20) / (60 - 20)&lt;/td&gt;
    &lt;td&gt;= 24px&lt;/td&gt;
  &lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;As you’ll notice, when we stay within our breakpoints (&lt;code&gt;20em&lt;/code&gt; to &lt;code&gt;60em&lt;/code&gt;), we get a value going linearly between &lt;code&gt;0rem&lt;/code&gt; and &lt;code&gt;1rem&lt;/code&gt;. We can use that!&lt;/p&gt;
&lt;p&gt;Next up is the &lt;code&gt;20px&lt;/code&gt; factor we had used in our first attempt at making the CSS work. We will need to scratch that.&lt;/p&gt;
&lt;p&gt;Our first attempt tried to achieve this kind of code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;font-size: calc( 1.25rem + 20px * n );&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;n&lt;/code&gt; was meant to be a value between 0 and 1. But because of the syntax restrictions for &lt;code&gt;calc()&lt;/code&gt; divisions, we couldn’t get that 0-to-1 result we wanted.&lt;/p&gt;
&lt;p&gt;What we did manage to get was a pixel value equivalent to &lt;code&gt;0rem&lt;/code&gt; and &lt;code&gt;1rem&lt;/code&gt;; let’s call this value &lt;code&gt;r&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another restriction affects &lt;code&gt;calc()&lt;/code&gt; multiplications. When writing &lt;code&gt;calc(a * b)&lt;/code&gt;, either &lt;code&gt;a&lt;/code&gt; or &lt;code&gt;b&lt;/code&gt; should be a unitless number.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;r&lt;/code&gt; already has a unit (it’s a pixel value), the other factor should be unitless.&lt;/p&gt;
&lt;p&gt;In our example, we want a 20px increase at the higher breakpoint. 20px is &lt;code&gt;1.25rem&lt;/code&gt;, so we will use a &lt;code&gt;1.25&lt;/code&gt; factor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;font-size: calc( 1.25rem + 1.25 * r );&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should work well, but note that the &lt;code&gt;r&lt;/code&gt; value will change depending on the base font-size.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With a 16px base, &lt;code&gt;1.25 * r&lt;/code&gt; will be a value between 0px and 20px.&lt;/li&gt;
&lt;li&gt;With a 24px base, &lt;code&gt;1.25 * r&lt;/code&gt; will be a value between 0px and 30px.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s write the whole CSS lock, with the media queries and the low and high values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 {
  font-size: 1.25rem;
}

@media (min-width: 20em) {
  /* The (100vw - 20rem) / (60 - 20) part
     resolves to 0-1rem, depending on the
     viewport width (between 20em and 60em). */
  h1 {
    font-size: calc( 1.25rem + 1.25 * (100vw - 20rem) / (60 - 20) );
  }
}

@media (min-width: 60em) {
  /* The right part of the addition *must* be a
     rem value. In this example we *could* change
     the whole declaration to font-size:2.5rem,
     but if our basline value was not expressed
     in rem we would have to use calc. */
  h1 {
    font-size: calc( 1.25rem + 1.25 * 1rem );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike the &lt;code&gt;px&lt;/code&gt;-based font-size lock, this time when the user increases the base font-size by 50% everything gets a 50% increase: the baseline value, the variable part and the breakpoints. We get a 30px–60px range, instead of the default 20px–40px range.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://fvsch.com/articles/css-locks/em-fontsize-bigger.png&quot; alt=&quot;Graph showing 2 sets of connected lines&quot;&gt;&lt;figcaption&gt;
    Dotted blue line: result with a base font size of 16px.&lt;br&gt;
    Solid red line: result with a base font size of 24px.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;You can check this behavior in &lt;a href=&quot;https://fvsch.com/articles/css-locks/demo5.html&quot;&gt;our first em-based demo&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Line-height locks with &lt;code&gt;em&lt;/code&gt;/&lt;code&gt;rem&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;In &lt;a href=&quot;https://fvsch.com/articles/css-locks/demo6.html&quot;&gt;our second demo&lt;/a&gt;, we want to change a paragraph’s &lt;code&gt;line-height&lt;/code&gt; from 1.4 to 1.8. We’re using &lt;code&gt;1.4em&lt;/code&gt; as our base value, and for the variable part we use the same formula as in the &lt;code&gt;font-size&lt;/code&gt; example.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;p {
  line-height: 1.4em;
}
@media (min-width: 20em) {
  p {
    line-height: calc( 1.4em + .4 * (100vw - 20rem) / (60 - 20) );
  }
}
@media (min-width: 60em) {
  p {
    line-height: calc( 1.4em + .4 * 1rem );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the variable part of our &lt;code&gt;line-height&lt;/code&gt;, we know that we want a &lt;code&gt;rem&lt;/code&gt; value, because &lt;code&gt;(100vw - 20rem) / (60 - 20)&lt;/code&gt; will result in a pixel value between &lt;code&gt;0rem&lt;/code&gt; and &lt;code&gt;1rem&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since our paragraph’s &lt;code&gt;font-size&lt;/code&gt; stays at &lt;code&gt;1rem&lt;/code&gt;, the &lt;code&gt;0.4em&lt;/code&gt; line-height increase we’re looking for is equivalent to &lt;code&gt;.4rem&lt;/code&gt;. So that’s the value we use in our two &lt;code&gt;calc()&lt;/code&gt; expressions.&lt;/p&gt;
&lt;p&gt;Now let’s take a &lt;code&gt;line-height&lt;/code&gt; example from &lt;a href=&quot;https://fvsch.com/articles/css-locks/demo7.html&quot;&gt;our third demo&lt;/a&gt;. We want the H1’s &lt;code&gt;line-height&lt;/code&gt; to &lt;em&gt;decrease&lt;/em&gt; from 1.33 to 1.2. We also know that its &lt;code&gt;font-size&lt;/code&gt; will change at the same time.&lt;/p&gt;
&lt;p&gt;For that same example in section II, we had determined that the line-height decrease could be expressed by two data points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;24 × (1.3333 - 1.2) = 3.2px&lt;/code&gt; at the lower breakpoint,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;40 × (1.2 - 1.2) = 0px&lt;/code&gt; at the higher breakpoint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we’re using a &lt;code&gt;1.2em&lt;/code&gt; base value, and a variable part from 3.2px to 0px. With a base font size of 16px, 3.2px is &lt;code&gt;0.2rem&lt;/code&gt;, so we’re going to use a &lt;code&gt;.2&lt;/code&gt; factor.&lt;/p&gt;
&lt;p&gt;Finally, since we need the variable part to be zero at the higher breakpoint, we will need to invert the breakpoints in the formula:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h1 {
  line-height: calc( 1.2em + 0.2 * 1rem );
}
@media (min-width: 20em) {
  h1 {
    line-height: calc( 1.2em + 0.2 * (100vw - 60rem) / (20 - 60) );
  }
}
@media (min-width: 60em) {
  h1 {
    line-height: 1.2em;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two things to note here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;.2rem&lt;/code&gt; value is only correct if we &lt;em&gt;also&lt;/em&gt; have a font-size lock going from 24px to 40px. (It’s not shown here but you can see it in the demo source.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Since we’re inverting the breakpoint values, for all viewport widths below &lt;code&gt;60em&lt;/code&gt; and at or above &lt;code&gt;20em&lt;/code&gt;, both sides of the &lt;code&gt;(100vw - 60rem) / (20 - 60)&lt;/code&gt; division will be negative. For instance, at the lower breakpoint and with a base font size of 16px, it’s equivalent to &lt;code&gt;-640px / -40&lt;/code&gt;. And since dividing two negative values resolves to a positive value, we don’t need to change the sign before our &lt;code&gt;0.2&lt;/code&gt; factor.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;span&gt;Compatibility notes&lt;/span&gt;&lt;/h2&gt;
&lt;h3&gt;IE and Edge&lt;/h3&gt;
&lt;p&gt;While developing the examples for this article, I routinely ran tests in Firefox and Chrome, and occasionally on Safari (Desktop). Sadly I must admit that Internet Explorer compatibility was never a target of main, and I fully expect IE&amp;nbsp;9 at least to find issues with complex &lt;code&gt;calc()&lt;/code&gt; expressions.&lt;/p&gt;
&lt;p&gt;I would still like to report on Edge and IE&amp;nbsp;11 compatibility, but have not yet found the time to do so.&lt;/p&gt;
&lt;h3&gt;Line-height base value&lt;/h3&gt;
&lt;p&gt;For &lt;code&gt;line-height: calc(…)&lt;/code&gt; declarations, the base value should always be expressed in &lt;code&gt;em&lt;/code&gt;, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;line-height: calc(1.4em + /* vw-based value */);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With CSS, you can express &lt;code&gt;line-height: 1.4em&lt;/code&gt; in two equivalent ways: &lt;code&gt;1.4&lt;/code&gt; (unitless ratio) or &lt;code&gt;140%&lt;/code&gt;. However, in our &lt;code&gt;calc()&lt;/code&gt; expressions, it turns out that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firefox and Chrome accept &lt;code&gt;1.4em&lt;/code&gt; and &lt;code&gt;140%&lt;/code&gt;, but don’t understand &lt;code&gt;1.4&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Safari only accepts &lt;code&gt;1.4em&lt;/code&gt;, and doesn’t understand the other two.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Safari and window resize&lt;/h3&gt;
&lt;p&gt;In my tests, Safari 10 on macOS didn’t always update the &lt;code&gt;calc()&lt;/code&gt; values when resizing the window or when using the Responsive Design Mode.&lt;/p&gt;
&lt;p&gt;It seems to be an on-and-off problem. Sometimes the &lt;code&gt;calc()&lt;/code&gt; values react to viewport changes instantly, and sometimes they need a full page refresh. If this is related to the exact code used, e.g. to the type of values used in &lt;code&gt;calc()&lt;/code&gt;, I haven’t been able to pinpoint the precise issue.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Conclusion&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;A brief summary of our findings. We’ve shown two CSS lock forms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for properties which can use dimensions,&lt;/li&gt;
&lt;li&gt;with &lt;code&gt;font-size&lt;/code&gt; and &lt;code&gt;line-height&lt;/code&gt; examples,&lt;/li&gt;
&lt;li&gt;and for either pixel-based or &lt;code&gt;em&lt;/code&gt;-based breakpoints.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The type of breakpoint you use is a major condition. In most web projects you will want to use the same breakpoints for, say, a &lt;code&gt;font-size&lt;/code&gt; lock, and for layout changes. Depending on your project or coding style, your breakpoints may use pixel values or &lt;code&gt;em&lt;/code&gt; values. (I tend to prefer pixel-based breakpoints, but both have their merits. As a reminder, &lt;a href=&quot;https://fvsch.com/em-media-queries&quot;&gt;if you’re using &lt;code&gt;em&lt;/code&gt;-based media queries, you should avoid pixel dimensions&lt;/a&gt;  when sizing containers.)&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;em&lt;/code&gt;-based media queries, the root element’s &lt;code&gt;font-size&lt;/code&gt; should &lt;em&gt;not&lt;/em&gt; be overriden, and you can only use one CSS lock form:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (min-width: 20em) and (max-width: 60em) {
  selector {
    property: calc(
      baseline_value +
      multiplier *
      (100vw - 20rem) / (60 - 20)
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;multiplier&lt;/code&gt; is the expected total &lt;em&gt;value increase&lt;/em&gt; in &lt;code&gt;rem&lt;/code&gt;, without the unit. (For example: &lt;code&gt;0.75&lt;/code&gt; for a &lt;code&gt;0.75rem&lt;/code&gt; maximum increase.)&lt;/p&gt;
&lt;p&gt;When using pixel-based media queries, you &lt;em&gt;can&lt;/em&gt; override the root element’s &lt;code&gt;font-size&lt;/code&gt; (though, if you do it, I recommend using a percentage value), and you may use two different CSS lock forms. The first one is similar to the the &lt;code&gt;em/rem&lt;/code&gt; lock, but with pixel values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (min-width: 320px) and (max-width: 960px) {
  selector {
    property: calc(
      baseline_value +
      multiplier *
      (100vw - 320px) / (960 - 320)
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;multiplier&lt;/code&gt; is the expected total &lt;em&gt;value increase&lt;/em&gt; in &lt;code&gt;px&lt;/code&gt;, without the unit. (For example: &lt;code&gt;12&lt;/code&gt; for a &lt;code&gt;12px&lt;/code&gt; maximum increase.)&lt;/p&gt;
&lt;p&gt;The second form doesn’t rely on the browser as much to resolve the equation; instead we calculate everything we can ourselves before feeding the values to the browser:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (min-width: 320px) and (max-width: 960px) {
  selector {
    property: calc(
      baseline_value + 0.25vw - 10px;
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where the &lt;code&gt;0.25vw&lt;/code&gt; and &lt;code&gt;-10px&lt;/code&gt; values are calculated beforehand, perhaps thanks to a Sass or PostCSS mixin.&lt;/p&gt;
&lt;p&gt;This last form might be a bit harder to get right (unless we’re using a good mixin), but it can make style inspection and debugging easier—as it is more obvious what values we’re adding.&lt;/p&gt;</content>
  </entry>
  <entry xml:lang="fr">
    <id>urn:uuid:371964c6-d011-5680-a83b-1ddcfe64269e</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/css-selector-wars"/>
    <title>CSS Selector Wars (KiwiParty 2016)</title>
    <published>2016-07-01T00:00:00+02:00</published>
    <updated>2016-07-01T00:00:00+02:00</updated>
    <summary>Support de présentation de ma conférence à la KiwiParty 2016 à Strasbourg. On y parle de stratégie pour gérer ses styles CSS sans provoquer des conflits dans tous les sens.</summary>
    <content type="html">&lt;figure&gt;&lt;img class=&quot;border&quot; src=&quot;https://fvsch.com/articles/css-selector-wars/cover.svg&quot; width=&quot;600&quot; height=&quot;300&quot; alt=&quot;&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Voici le support de présentation de ma conférence à la KiwiParty 2016 à Strasbourg. On y parle de stratégie pour gérer ses styles CSS sans provoquer des conflits dans tous les sens, avec différentes possibilités:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;les conventions de nommage (BEM, par exemple)&lt;/li&gt;
&lt;li&gt;l’approche en CSS atomiques&lt;/li&gt;
&lt;li&gt;les espaces de nom automatiques, avec CSS Modules&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vous pouvez voir le support complet:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://fvsch.com/articles/css-selector-wars/2016-05-kiwiparty-css-selector-wars.pdf&quot;&gt;En PDF (460 Ko)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.google.com/presentation/d/1LZp7LYihmF23a3gt52SNp0POgyXfgVYaA-eguk5xzxQ/edit?usp=sharing&quot;&gt;Sur Google Slides&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content>
  </entry>
  <entry xml:lang="fr">
    <id>urn:uuid:224a8599-87cd-5b10-a9dc-5787d561f011</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/js-sans-jquery"/>
    <title>La vie sans jQuery</title>
    <published>2016-05-30T00:00:00+02:00</published>
    <updated>2016-05-30T00:00:00+02:00</updated>
    <summary>Pour apprendre à utiliser le DOM, passez-vous de jQuery sur votre prochain projet.</summary>
    <content type="html">&lt;p&gt;Je voudrais discuter de comment et pourquoi sortir du &lt;em&gt;jQuery par défaut&lt;/em&gt; sur les projets web. Pour une fois je vais tenter d’écrire un article en français et court&lt;sup id=&quot;fnref1:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Pour résumer mon opinion: il n’y a pas d’urgence à sortir jQuery de vos projets, et dans certains cas ça peut même être une mauvaise décision. Mais apprendre à faire sans, c’est un bon moyen pour mieux se familiariser avec JavaScript et les fonctionnalités natives des navigateurs, y compris les plus récentes (DOM4).&lt;/p&gt;
&lt;nav id=&quot;toc&quot; data-title=&quot;Table des matières&quot;&gt;&lt;/nav&gt;
&lt;h2&gt;&lt;span&gt;jQuery partout&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;W3Techs estime que &lt;a href=&quot;https://w3techs.com/technologies/details/js-jquery/all/all&quot;&gt;jQuery est utilisé sur 70% des sites web&lt;/a&gt;. C’est gigantesque! En bientôt 10 ans depuis sa première version à l’été 2006, jQuery est devenu incontournable. Chez &lt;a href=&quot;http://www.kaliop.com/&quot;&gt;Kaliop&lt;/a&gt;, comme chez mes précédents employeurs et dans la plupart de mes projets en freelance, la question se pose rarement: on met la dernière version de jQuery — ou un fichier copié du dernier projet — vers le début de l’intégration et voilà.&lt;/p&gt;
&lt;p&gt;La popularité de jQuery est largement méritée:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;c’est un projet mature, stable et activement maintenu;&lt;/li&gt;
&lt;li&gt;avec une API succinte et plutôt intuitive;&lt;/li&gt;
&lt;li&gt;qui résoud aussi quelques problèmes de compatibilité.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le mécanisme de plugins a aussi fortement contribué à son succès.&lt;/p&gt;
&lt;p&gt;Pour autant, jQuery n’est pas magique. Tout ce que fait jQuery, vous pouvez le faire directement en utilisant les APIs natives du navigateur, en particulier le DOM. Quelques exemples pas strictement équivalents mais proches:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Avec jQuery
var header = jQuery(&apos;.header&apos;);
header.on(&apos;click&apos;, function(event){ … });

// DOM natif
var header = document.querySelector(&apos;.header&apos;);
header.addEventListener(&apos;click&apos;, function(event){ … });&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;span&gt;Petite pause vocabulaire&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Dans cet article je parle de JavaScript, de jQuery, de DOM et d’API, alors on va essayer de définir tout ça viteuf, parce que c’est pas pareil.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JavaScript:&lt;/strong&gt; un langage de programmation, qui est &lt;em&gt;le&lt;/em&gt; langage de programmation utilisable dans un navigateur Web&lt;sup id=&quot;fnref1:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. JavaScript a une syntaxe, avec des variables, des fonctions, des objets, etc. Avec cette syntaxe on peut faire deux-trois trucs, mais dès qu’on veut sortir de la boite où tourne le moteur JavaScript, par exemple pour modifier des éléments sur la page ou envoyer une requête HTTP, il faut utiliser des… APIs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;API:&lt;/strong&gt; pour &lt;i lang=en&gt;Application Programming Interface&lt;/i&gt;, ce qui se traduit en français par «ensemble de fonctions pour faire des trucs avec des machins». Dans les navigateurs on trouve plein d’APIs pour faire plein de trucs avec plein de machins (des requêtes HTTP, des connexions en peer-to-peer, modifier des flux audio pour faire un synthétiseur dans ton navigateur, etc.). Et pour utiliser ces APIs on écrit du code en… JavaScript.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DOM:&lt;/strong&gt; le &lt;i lang=en&gt;Document Object Model&lt;/i&gt; c’est un ensemble de spécifications qui décrivent une API (eh oui) qui permet de &lt;em&gt;faire des trucs avec un document HTML ou XML&lt;/em&gt;. Par exemple «lire» le contenu de ce document, écrire dedans, créer des éléments HTML, les injecter dans la page, modifier des attributs, etc. JavaScript tout seul ne peut pas faire ça, il a besoin du DOM. Ah oui, et dans les spécifications du DOM il y a aussi &lt;em&gt;DOM Events&lt;/em&gt;, qui décrit un mécanisme d’évènements («l’utilisateur clique quelque part dans la page, paf ça fait un évènement, tu peux écouter cet évènement et y réagir si tu veux»).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;jQuery:&lt;/strong&gt; c’est un gros script écrit en JavaScript et qui fournit une API (la doc est sur &lt;a href=&quot;http://api.jquery.com/&quot;&gt;api.jquery.com&lt;/a&gt;, d’ailleurs) pour &lt;em&gt;faire des trucs avec un document HTML&lt;/em&gt;. Plus deux ou trois autres fonctionnalités utiles, comme des utilitaires pour faire des requêtes HTTP (vous connaissez peut-être sous le nom de «Ajax», sans lien de parenté avec le héros grec ou la lessive).&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Et un peu d’histoire&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Là comme ça, on dirait que jQuery fait la même chose que le DOM, et donc que ses créateurs se sont emmerdés à réinventer la roue. En fait ça s’est passé un peu différemment.&lt;/p&gt;
&lt;p&gt;À l’époque de la création de jQuery, le DOM avait un nombre de fonctionnalités limitées et était, pour tout dire, assez chiant à utiliser. Il fallait écrire beaucoup de lignes de code pour faire pas grand chose. Sans parler des trucs possibles mais trop difficiles à faire.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;i&gt;Dev:&lt;/i&gt; Salut le DOM, je peux avoir tous les éléments qui ont la classe «machin»?
&lt;i&gt;DOM:&lt;/i&gt; Hmmm c’est compliqué.
&lt;i&gt;Dev:&lt;/i&gt; Mais c’est possible ou pas?
&lt;i&gt;DOM:&lt;/i&gt; Oui mais il faut faire ça d’abord, et puis ça, et enfin ça.
&lt;i&gt;Dev:&lt;/i&gt; Punaise… et sinon pour ajouter un élément juste après celui-ci, je fais comment?
&lt;i&gt;DOM:&lt;/i&gt; Tu veux pas le mettre avant, plutôt?
&lt;i&gt;Dev:&lt;/i&gt; Euh non je voudrais le mettre après.
&lt;i&gt;DOM:&lt;/i&gt; Moi j’ai juste appris à mettre des éléments avant, je sais pas les mettre après. C’est peut-être faisable en trois étapes, faut voir.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pour corser le tout, les spécifications du DOM n’étaient pas complètement implémentées dans les navigateurs, et notamment dans Internet Explorer qui faisait une partie des trucs à sa sauce. Donc il fallait parfois écrire des fonctions qui faisent une chose dans Internet Explorer, une autre dans les autres navigateurs, pour obtenir le même résultat final.&lt;/p&gt;
&lt;p&gt;Les projets comme Prototype, MooTools et jQuery avaient donc pour but de corriger ces différences en proposant une seule manière de faire (la leur), et aussi de faciliter l’utilisation du DOM.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;i&gt;Dev:&lt;/i&gt; jQuery, jpeux avoir les éléments avec la classe «machin»?
&lt;i&gt;jQuery:&lt;/i&gt; Bien sûr, les voilà.
&lt;i&gt;Dev:&lt;/i&gt; Cool. Et pour ajouter un élément juste après celui-ci, ça se fait en 3 étapes c’est ça?
&lt;i&gt;jQuery:&lt;/i&gt; Ouais mais je m’en occupe, tkt.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;span&gt;Mais alors, pourquoi faire sans?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Il y a quand même quelques bonnes raisons de se passer de jQuery, en particulier sur de petits projets qui n’ont pas des dizaines ou centaines de fonctionnalités JavaScript.&lt;/p&gt;
&lt;h3&gt;Parce que ça pèse lourd, mine de rien&lt;/h3&gt;
&lt;p&gt;Je me souviens des premières versions de jQuery, qui faisaient dans les 50 Ko avec minification. Aujourd’hui, jQuery 1.12 fait 97 Ko minifié, et jQuery 2.2 fait 85 Ko (idem pour jQuery 3.0).&lt;/p&gt;
&lt;p&gt;Ça n’est pas excessif vu les fonctionnalités apportées, mais encore faut-il en faire réellement usage. Si c’est pour écrire 100 lignes de code maison à tout casser, ça commence à couter cher. Et comme il s’agit de JavaScript, il faut aussi compter le temps d’analyse et d’exécution de ce code (il n’est pas anodin, en particulier sur des mobiles peu performants).&lt;/p&gt;
&lt;h3&gt;Parce que jQuery bloque l’apprentissage&lt;/h3&gt;
&lt;p&gt;C’est le plus gros problème que je vois avec jQuery. Beaucoup de gens qui &lt;em&gt;ont besoin de faire du JavaScript dans leur métier&lt;/em&gt; ne savent pas coder en JavaScript, et ne connaissent pas le DOM; iels&lt;sup id=&quot;fnref1:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote-ref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; ont appris à faire du jQuery (et pas toujours bien). Quand iels se retrouvent sur un projet sans jQuery, iels sont bloqués.&lt;/p&gt;
&lt;p&gt;Si vous connaissez jQuery mais que vous ne savez pas expliquer la différence entre ces trois variables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var test1 = $(&apos;.test&apos;);
var test2 = $(&apos;.test&apos;)[0];
var test3 = document.querySelector(&apos;.test&apos;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;… alors il vous manque la compréhension de concepts essentiels concernant jQuery, le DOM, et même la syntaxe JavaScript. Ce n’est pas un reproche, mais ça vous limite.&lt;/p&gt;
&lt;p&gt;De plus, jQuery n’est pas une abstraction parfaite au dessus du DOM, qui permettrait de s’en passer totalement. Il y a plusieurs cas de figure où des méthodes de jQuery exposent des éléments du DOM plutôt que des objets jQuery:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;var items = $(&apos;.something li&apos;); // objet jQuery
items.on(&apos;click&apos;, function(event) {
    console.log(this); // objet HTMLLIElement
});
items.each(function(index, element){
    console.log(element); // objet HTMLLIElement
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pour pouvoir continuer à travailler uniquement avec les méthodes de jQuery, il faut donc re-fabriquer un objet jQuery avec ces éléments:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;$(&apos;.something li&apos;).each(function(index, element){
    element = $(element);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et comme les développeurs/ses&lt;sup id=&quot;fnref2:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote-ref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; ne savent souvent pas quelle est la différence exacte entre un objet jQuery et un objet du DOM, iels se retrouvent souvent avec des messages d’erreur du type &lt;q lang=en&gt;TypeError: element.attr is not a function&lt;/q&gt;, et rajoutent des &lt;code&gt;$()&lt;/code&gt; autour de leurs variables à tour de bras, même quand ce n’est pas du tout nécessaire.&lt;/p&gt;
&lt;p&gt;Mais produire du code JavaScript+jQuery mal optimisé, ce n’est pas si grave. Le principal problème à mon sens c’est qu’on en arrive à ne pas savoir faire sans jQuery, à ne jamais avoir appris les bases du DOM ou des bases de JavaScript un peu solides. Avec quelques conséquences:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Si vous avez écrit du code théoriquement réutilisable sur d’autres projets, mais que ces projets n’utilisent pas jQuery… votre code n’est pas réutilisable.&lt;/li&gt;
&lt;li&gt;Si un projet n’utilise pas jQuery, vous n’êtes plus capable d’intervenir dessus.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Petite anecdote: un projet en mode maintenance, datant d’il y a quelques années, utilisait MooTools (un concurrent de jQuery). Un-e développeur/se junior à qui on avait confié la réalisation d’une nouvelle fonctionnalité ne savait pas faire sans jQuery. Iel a donc rajouté jQuery (non minifié, sinon c’est moins drôle) dans le projet, et codé la fonctionnalité JavaScript demandée. Avec pour conséquence: 250 Ko de plus chargé par page, et &lt;strong&gt;toutes les autres fonctionnalités JS du site&lt;/strong&gt; plantées à cause de conflits entre jQuery et MooTools.&lt;/p&gt;
&lt;h3&gt;Parce que le DOM natif s’est quand même bien amélioré&lt;/h3&gt;
&lt;p&gt;D’une part, IE8 puis IE9 ont enfin corrigé les grosses différences d’implémentation des standards (la création de gestionnaires d’évènements avec &lt;code&gt;addEventListener&lt;/code&gt; par exemple).&lt;/p&gt;
&lt;p&gt;D’autre part, voyant que &lt;em&gt;personne n’utilisait le DOM directement car c’est trop relou&lt;/em&gt;, quelques améliorations bienvenues sont apparues dans les spécifications DOM:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;document.querySelector&lt;/code&gt; et &lt;code&gt;document.querySelectorAll&lt;/code&gt; (IE8+) pour sélectionner un ou plusieurs éléments.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;element.insertAdjacentHTML&lt;/code&gt; (qui date de IE4 et a été standardisé ensuite).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;element.classList&lt;/code&gt; (IE10+) qui permet d’ajouter et supprimer des classes facilement.&lt;/li&gt;
&lt;li&gt;Et dans les spécifications DOM récentes (parfois appelées “DOM4”), les méthodes &lt;code&gt;prepend&lt;/code&gt;, &lt;code&gt;append&lt;/code&gt;, &lt;code&gt;before&lt;/code&gt;, &lt;code&gt;after&lt;/code&gt;, &lt;code&gt;replaceWith&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt;; &lt;code&gt;document.query&lt;/code&gt; et &lt;code&gt;document.queryAll&lt;/code&gt; qui améliorent quelques aspects de &lt;code&gt;querySelector[All]&lt;/code&gt;; &lt;code&gt;element.matches&lt;/code&gt;, &lt;code&gt;element.contains&lt;/code&gt;, &lt;code&gt;element.closest&lt;/code&gt; qui ressemblent beaucoup à certaines méthodes de jQuery.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le dernier lot d’améliorations (“DOM4”) est assez récent et pas encore implémenté partout, mais il existe &lt;a href=&quot;https://github.com/WebReflection/dom4/&quot;&gt;des polyfills plutôt légers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ces améliorations rendent envisageables l’utilisation de l’API DOM standard sans passer par un utilitaire un peu lourd comme jQuery (et sans s’arracher les cheveux).&lt;/p&gt;
&lt;h3&gt;Pour manipuler le DOM le plus tôt possible&lt;/h3&gt;
&lt;p&gt;Pour certaines fonctionnalités JS, mieux vaut pouvoir modifier le DOM quasi-instantanément, plutôt qu’attendre le chargement d’un gros fichier externe contenant jQuery, d’autres libs et vos scripts.&lt;/p&gt;
&lt;p&gt;Sur certains projets, nous avons par exemple des petits scripts qui font 1, 2 ou 3 Ko minifiés, et qui sont inclus intégralement juste après les contenus HTML, et &lt;em&gt;avant&lt;/em&gt; l’appel de jQuery ou d’autres scripts.&lt;/p&gt;
&lt;p&gt;Il ne faut pas en abuser, mais ça peut être un bon moyen pour optimiser les performances perçues par l’utilisateur, ou éviter les demandes de clients qui trouvent que tel ou tel contenu affiché intégralement le temps que JS charge c’est innacceptable.&lt;/p&gt;
&lt;h3&gt;Pour essayer!&lt;/h3&gt;
&lt;p&gt;Si vous n’avez jamais codé sans jQuery, ou si vous l’avez fait uniquement au milieu des années 2000, ça vaut le coup d’essayer. Vous apprendrez au passage le fonctionnement du DOM natif, améliorerez sans doute votre compréhension de la syntaxe JavaScript, et comprendrez mieux ce que fait jQuery.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Sans les m… euh sans jQuery&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Bon alors, comment faire pour se passer de jQuery sur un projet? Voici quelques approches intéressantes.&lt;/p&gt;
&lt;h3&gt;Le DOM natif compatible IE10+&lt;/h3&gt;
&lt;p&gt;Si vous pouvez laisser de côté le support d’IE9, pas mal de fonctionnalités DOM natives sont disponibles sans avoir à charger de lib ou de polyfill. Notamment: &lt;code&gt;addEventListener&lt;/code&gt;, &lt;code&gt;querySelector&lt;/code&gt;, et &lt;code&gt;classList&lt;/code&gt;. Avec les fonctionnalités DOM plus classiques, &lt;code&gt;setAttribute&lt;/code&gt; &lt;code&gt;appendChild&lt;/code&gt;, &lt;code&gt;innerHTML&lt;/code&gt;, &lt;code&gt;textContent&lt;/code&gt; et &lt;code&gt;insertAdjacentHTML&lt;/code&gt;, vous avez de quoi manipuler des éléments dans la page sans trop de souci.&lt;/p&gt;
&lt;p&gt;Sur un tout petit projet, je vous recommande vraiment cette approche, qui vous aidera à consolider vos connaissances.&lt;/p&gt;
&lt;h3&gt;DOM4 avec un polyfill&lt;/h3&gt;
&lt;p&gt;Si vos besoins dépassent les choses les plus simples, les méthodes du standard DOM4 permettent de se rapprocher un peu de la facilité d’utilisation de jQuery et consors.&lt;/p&gt;
&lt;p&gt;Deux exemples avec les méthodes DOM4:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Contrairement à querySelectorAll qui retourne une NodeList,
// queryAll retourne un Array, ce qui facilite les boucles
document.queryAll(&apos;nav a&apos;).forEach(function(element) {
  // Faire quelque chose avec chaque lien
})

// element.closest(&apos;a&apos;) retourne l’élément lui-même si c’est
// un lien, ou son plus proche ancêtre qui est un lien. Cela
// permet de faire de la délégation d’évènement à la mano.
document.query(&apos;nav&apos;).addEventListener(&apos;click&apos;, function(ev) {
  var link = ev.target.closest(&apos;a&apos;)
  if (link) {
    // Faire quelque chose avec le lien cliqué
  }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pour utiliser ces méthodes dans tous les navigateurs, on pourra se reposer sur &lt;a href=&quot;https://github.com/WebReflection/dom4&quot;&gt;ce polyfill&lt;/a&gt; (IE9+ et 10 Ko minifiés, pas mal du tout).&lt;/p&gt;
&lt;h3&gt;DOM4 et un peu plus avec Bliss&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://blissfuljs.com/&quot;&gt;Bliss&lt;/a&gt;, créé par Lea Verou, est une alternative à jQuery qui repose sur les méthodes natives du DOM et quelques polyfills pour les navigateurs qui en ont besoin. Ce projet s’inspire parfois de jQuery pour certaines méthodes, mais dès que possible il conseille d’utiliser la syntaxe DOM native. C’est donc un beau projet pour apprendre le DOM moderne.&lt;/p&gt;
&lt;p&gt;De plus lorqu’on consulte &lt;a href=&quot;http://blissfuljs.com/docs.html&quot;&gt;la documentation&lt;/a&gt; on peut cliquer les boutons &lt;q lang=en&gt;Show Implementation&lt;/q&gt; pour voir le code correspondant à une méthode.&lt;/p&gt;
&lt;h3&gt;Les jQuery-like&lt;/h3&gt;
&lt;p&gt;Certaines libs réimplémentent l’API de jQuery en plus léger, en général en visant seulement la compatibilité avec les navigateurs récents. Par exemple &lt;a href=&quot;http://zeptojs.com/&quot;&gt;Zepto.js&lt;/a&gt;, qui est un des projets les plus complets, vise IE10+. La syntaxe proposée est exactement la même que celle de jQuery, car c’est un projet créé pour remplacer jQuery complètement sur mobile. Certains plugins jQuery sont d’ailleurs testés pour fonctionner également avec Zepto.&lt;/p&gt;
&lt;p&gt;Ce n’est pas une approche que je recommande, car elle pose les mêmes problèmes de limitation dans l’apprentissage du JavaScript et du DOM.&lt;/p&gt;
&lt;h3&gt;Une note sur les requêtes HTTP&lt;/h3&gt;
&lt;p&gt;Alors on va arrêter de parler d’«Ajax» parce que ce nom est stupide, et que si vous faites du code pour le Web il est temps d’apprendre à connaitre les bases de HTTP, ce qu’est une requête HTTP et une réponse HTTP. Utilisez l’onglet «Réseau» de vos devtools préférés et inspectez un peu quelques requêtes et réponses pour vous familiariser avec tout ça.&lt;/p&gt;
&lt;p&gt;L’API classique pour faire des requêtes HTTP en JavaScript se nomme &lt;code&gt;XMLHttpRequest&lt;/code&gt;, et vous pouvez en apprendre plus sur &lt;a href=&quot;http://eloquentjavascript.net/17_http.html&quot;&gt;les requêtes HTTP en JavaScript par ici&lt;/a&gt; (en anglais, car je n’ai pas trouvé d’article correct en français).&lt;/p&gt;
&lt;p&gt;jQuery fournit des méthodes pour faire des requêtes HTTP, nommées &lt;code&gt;jQuery.ajax()&lt;/code&gt;, &lt;code&gt;jQuery.get()&lt;/code&gt; et &lt;code&gt;jQuery.getJSON()&lt;/code&gt;. Donc si on se passe de jQuery, il faut soit utiliser &lt;code&gt;XMLHttpRequest&lt;/code&gt; soi-même, soit utiliser un petit utilitaire qui facilite son utilisation.&lt;/p&gt;
&lt;p&gt;Si vous choisissez Bliss ou une autre lib légère concurrente de jQuery, vérifiez ce qu’elle propose pour les requêtes HTTP (Bliss a une méthode &lt;code&gt;Bliss.fetch&lt;/code&gt;, par exemple).&lt;/p&gt;
&lt;p&gt;Autre option intéressante: utiliser le nouveau standard &lt;code&gt;fetch()&lt;/code&gt; (&lt;a href=&quot;http://caniuse.com/#feat=fetch&quot;&gt;Can I Use&lt;/a&gt;), avec un polyfill tel que &lt;a href=&quot;https://github.com/github/fetch&quot;&gt;github/fetch&lt;/a&gt; (IE10+).&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Mini retour d’expérience&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Sur un projet récent, j’ai donc fait uniquement du JavaScript sans jQuery. J’ai utilisé:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WebReflection/dom4&quot;&gt;WebReflection/dom4&lt;/a&gt;, tip top.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/github/fetch&quot;&gt;github/fetch&lt;/a&gt;, ce qui marchait très bien mais que j’ai fini par enlever car j’ai pu me passer des requêtes HTTP en JavaScript.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://lunrjs.com/&quot;&gt;lunr.js&lt;/a&gt; pour de la recherche dans des données JS/JSON.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les fonctionnalités JS à implémenter étaient:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Une navigation mobile à déplier au clic (sur 2 niveaux).&lt;/li&gt;
&lt;li&gt;Une sorte de slider, que j’ai implémenté en ajoutant/supprimant des attributs &lt;code&gt;hidden&lt;/code&gt; (pas d’effet d’animation, on affiche un bloc à la fois et tous les autres sont en &lt;code&gt;display:none&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Une recherche full-text dans un gros fichier JSON. Ça a été la partie la plus compliquée du projet en JavaScript, et sur ce point jQuery n’aurait pas été d’une grande aide. La lib utilisée, lunr.js, est très bien, mais plutôt bas niveau donc il faut un peu de temps pour la comprendre et pas mal de code maison pour l’utiliser.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id=&quot;fn:1&quot;&gt;
&lt;p&gt;Perdu. 😁&amp;#160;&lt;a href=&quot;#fnref1:1&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:2&quot;&gt;
&lt;p&gt;Certains ont bien essayé de mettre d’autres langages de programmation dans le navigateur (Microsoft avec VBSCript, Google avec Dart), mais ça n’a pas pris. Sans parler des plugins qui permettaient de faire du Java (petit succès) ou de l’ActionScript (gros succès) et qui sont passés de mode.&amp;#160;&lt;a href=&quot;#fnref1:2&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn:3&quot;&gt;
&lt;p&gt;Autant que possible, cet article est écrit sans présupposer du genre des personnes même fictives mentionnées. Ainsi «développeur/se» est un raccourci pour «développeur ou développeuse» et le pronom «iels» est un néologisme qui mélange volontairement «ils» et «elles». Je suis consciente que ces mots et graphies ne facilitent pas la lecture pour celleux qui n’en ont pas l’habitude, mais ça compte pour moi donc osef. 😇&amp;#160;&lt;a href=&quot;#fnref1:3&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref2:3&quot; rev=&quot;footnote&quot; class=&quot;footnote-backref&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content>
  </entry>
  <entry xml:lang="en">
    <id>urn:uuid:852cb860-f25b-58fa-93f8-41f219000beb</id>
    <link rel="alternate" type="text/html" href="https://fvsch.com/svg-icons"/>
    <title>How to work with SVG icons</title>
    <published>2016-03-27T00:00:00+01:00</published>
    <updated>2016-03-27T00:00:00+01:00</updated>
    <summary>A complete guide to SVG icons in HTML pages, with the symbol sprites technique.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;March 2018 update: more reliable way to set alternative text, small fixes.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are many ways to use SVG icons in HTML and CSS, and I haven’t tried them all. This is how we do it in our small front-end team at &lt;a href=&quot;https://www.kaliop.com/&quot;&gt;Kaliop&lt;/a&gt;. It works well for our needs, which include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Content and communication websites, often based on big CMSes, rather than full-JS web apps.&lt;/li&gt;
&lt;li&gt;Icons which are often simple, single-color icons (each potentially used in several different colors depending on context and user interactions). Two-color icons and gradient fills are possible too, but require a bit more work.&lt;/li&gt;
&lt;li&gt;IE11 support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article is available &lt;a href=&quot;https://la-cascade.io/comment-travailler-avec-des-icones-en-svg/&quot;&gt;in French&lt;/a&gt; and &lt;a href=&quot;http://zhuanlan.zhihu.com/p/20753791&quot;&gt;in Chinese&lt;/a&gt;.
Many thanks to the translators!&lt;/p&gt;
&lt;nav id=&quot;toc&quot; data-level=&quot;2&quot;&gt;&lt;/nav&gt;
&lt;h2 id=&quot;section-preparing&quot;&gt;Preparing your icons&lt;/h2&gt;
&lt;p&gt;When you get a SVG icon from a designer or from a graphics tool you use (Illustrator, Adobe Assets, Sketch, Inkscape, etc.), it’s tempting to just throw it into your project. Yet I find that reworking it a little bit in your favorite tool to make sure it’s &lt;em&gt;just right&lt;/em&gt; can save you some headaches and improve the result.&lt;/p&gt;
&lt;figure class=&quot;full&quot;&gt;&lt;img src=&quot;https://fvsch.com/articles/svg-icons/example-1.png&quot; alt=&quot;&quot;&gt;&lt;figcaption&gt;Simple icon on an artboard in Illustrator (left) and Sketch (right)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;Work with a new document or artboard&lt;/h3&gt;
&lt;p&gt;Create a new document or new artboard in your favorite tool, and copy-paste your icon in the center. It’s a great way to make sure your icon is clean and doesn’t have a ton of hidden paths lying around.&lt;/p&gt;
&lt;h3&gt;Square is easier&lt;/h3&gt;
&lt;p&gt;Your icon &lt;em&gt;doesn’t have to be square&lt;/em&gt;, but square icons are easier to work with (unless your icon or graphic is really wide or really tall).&lt;/p&gt;
&lt;p&gt;Exact dimensions only matter if you want to micromanage pixel fitting (to get the sharpest possible results on low dpi screens). For example if all your icons can fit on a 15 by 15 pixel grid, and are mostly used with those exact dimensions, go ahead and work with 15×15 artboards or documents. When I’m not sure, I like setting stuff on a 20×20 artboard.&lt;/p&gt;
&lt;h3&gt;Breezy on the sides&lt;/h3&gt;
&lt;p&gt;Leave a little bit of space near the edges, especially for round shapes. Browsers use anti-aliasing when rendering SVG shapes, but sometimes the extra pixels from the anti-aliasing are rendered outside of the &lt;code&gt;viewBox&lt;/code&gt; and they’re cut off.&lt;/p&gt;
&lt;figure class=&quot;full&quot;&gt;&lt;img src=&quot;https://fvsch.com/articles/svg-icons/example-2.png&quot; alt=&quot;Screenshot of an round icon in Illustrator, touching the limits of the artboard&quot;&gt;&lt;figcaption&gt;We didn’t leave any space around the icon, so there’s a risk that it will be rendered with squarish sides. And if the browser doesn’t render the SVG perfectly, it can get worse.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;As a rule of thumb, in a 16px or 20px icon, leave 0.5px or 1px of empty space on each side. Also, remember to export the whole artboard, not the selected paths at the center, or you will lose that white space in the export.&lt;/p&gt;
&lt;h3&gt;Export to SVG&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;In Illustrator I just use “Save As” and pick “SVG” for the format. (It might be better to use “Export as…” and pick SVG for an optimized result.)&lt;/li&gt;
&lt;li&gt;In Sketch you can select an artboard, click “Make Exportable” on the bottom right, and pick “SVG” for the format.&lt;/li&gt;
&lt;li&gt;In Inkscape you can “Save As” and pick “Optimized SVG”.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Learn some SVG&lt;/h3&gt;
&lt;p&gt;You definitely should learn some SVG basics, and be able to read and understand the structure of simple SVG files. Ideally you should know about these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Grouping elements: &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Visual elements: &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;rect&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;line&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Styling attributes: &lt;code&gt;fill&lt;/code&gt;, &lt;code&gt;stroke&lt;/code&gt;, &lt;code&gt;stroke-width&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I tend to &lt;a href=&quot;https://devdocs.io/svg/&quot;&gt;look them up in DevDocs&lt;/a&gt; (which simply offers a nicer view of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG&quot;&gt;MDN SVG docs&lt;/a&gt;) when I want to know more. You don’t have to do that right now, and you can start your SVG adventure by copy-pasting code, but in time it’s helpful to learn enough to understand the code you’re copy-pasting.&lt;/p&gt;
&lt;p&gt;When you export SVG from a design tool, it will often have a little bit or a lot of unnecessary markup, metadata and such. It can also have excessively precise path data (in &lt;a href=&quot;https://css-tricks.com/svg-path-syntax-illustrated-guide/&quot;&gt;the &lt;code&gt;d&lt;/code&gt; attribute&lt;/a&gt;). Try using a tool such as &lt;a href=&quot;https://jakearchibald.github.io/svgomg/&quot;&gt;SVGOMG&lt;/a&gt; and compare the before and after code to see what gets removed or simplified.&lt;/p&gt;
&lt;h3&gt;Remove color data&lt;/h3&gt;
&lt;p&gt;For single-color icons, make sure that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In your source file, the paths are black (&lt;code&gt;#000000&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;In the exported code, there are no &lt;code&gt;fill&lt;/code&gt; attributes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If we have hardcoded fills in the SVG source, we won’t be able to change those colors from our CSS code. So it’s generally best to remove them, at least for single-color icons.&lt;/p&gt;
&lt;p&gt;Illustrator doesn’t output &lt;code&gt;fill&lt;/code&gt; attributes for path that are fully black (&lt;code&gt;#000000&lt;/code&gt;). Sketch does, so you may have to open the exported SVG code and manually remove the &lt;code&gt;fill=&quot;#000000&quot;&lt;/code&gt; attributes.&lt;/p&gt;
&lt;h2 id=&quot;section-sprite&quot;&gt;Making a SVG sprite&lt;/h2&gt;
&lt;p&gt;This part has a lot of code, but it’s actually not complex at all. We want to create a SVG document containing &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt; elements. Each &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt; must have an &lt;code&gt;id&lt;/code&gt; attribute, a &lt;code&gt;viewBox&lt;/code&gt; attribute, and will contain the icon’s &lt;code&gt;&amp;lt;path/&amp;gt;&lt;/code&gt; elements (or other graphical elements).&lt;/p&gt;
&lt;p&gt;I’m calling this SVG document a sprite (in reference to &lt;a href=&quot;https://en.wikipedia.org/wiki/Sprite_%28computer_graphics%29&quot;&gt;sprites in computer games&lt;/a&gt; and CSS), but it may also be called a sprite sheet, or a symbol store.&lt;/p&gt;
&lt;p&gt;Here’s a sprite with just one icon:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot;&amp;gt;
  &amp;lt;symbol id=&quot;cross&quot; viewBox=&quot;0 0 20 20&quot;&amp;gt;
    &amp;lt;path d=&quot;M17.1 5.2l-2.6-2.6-4.6 4.7-4.7-4.7-2.5 2.6 4.7 4.7-4.7 4.7 2.5 2.5 4.7-4.7 4.6 4.7 2.6-2.5-4.7-4.7&quot;/&amp;gt;
  &amp;lt;/symbol&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Adding an icon to our sprite&lt;/h3&gt;
&lt;p&gt;Say that Illustrator gave us this code for the icon show above:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --&amp;gt;
&amp;lt;svg version=&quot;1.1&quot; id=&quot;Layer_1&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; x=&quot;0px&quot; y=&quot;0px&quot;
  viewBox=&quot;0 0 15 15&quot; style=&quot;enable-background:new 0 0 15 15;&quot; xml:space=&quot;preserve&quot;&amp;gt;
  &amp;lt;path id=&quot;ARROW&quot; d=&quot;M7.5,0.5c3.9,0,7,3.1,7,7c0,3.9-3.1,7-7,7c-3.9,0-7-3.1-7-7l0,0C0.5,3.6,3.6,0.5,7.5,0.5 C7.5,0.5,7.5,0.5,7.5,0.5L7.5,0.5L7.5,0.5z M6.1,4.7v5.6l4.2-2.8L6.1,4.7z&quot;/&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can simplify it quite a bit (manually or using &lt;a href=&quot;https://jakearchibald.github.io/svgomg/&quot;&gt;SVGOMG&lt;/a&gt;), only keeping the &lt;code&gt;viewBox&lt;/code&gt; attribute and essential data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 15 15&quot;&amp;gt;
  &amp;lt;path d=&quot;M7.5,0.5c3.9,0,7,3.1,7,7c0,3.9-3.1,7-7,7c-3.9,0-7-3.1-7-7l0,0C0.5,3.6,3.6,0.5,7.5,0.5 C7.5,0.5,7.5,0.5,7.5,0.5L7.5,0.5L7.5,0.5z M6.1,4.7v5.6l4.2-2.8L6.1,4.7z&quot;/&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can copy-paste it in our sprite. We need to transform the &lt;code&gt;&amp;lt;svg viewBox=&quot;…&quot;&amp;gt;&lt;/code&gt; element into a &lt;code&gt;&amp;lt;symbol id=&quot;…&quot; viewBox=&quot;…&quot;&amp;gt;&lt;/code&gt; element and insert it manually into our sprite:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot;&amp;gt;
  &amp;lt;symbol id=&quot;cross&quot; viewBox=&quot;0 0 20 20&quot;&amp;gt;
    &amp;lt;path d=&quot;M17.1 5.2l-2.6-2.6-4.6 4.7-4.7-4.7-2.5 2.6 4.7 4.7-4.7 4.7 2.5 2.5 4.7-4.7 4.6 4.7 2.6-2.5-4.7-4.7&quot;/&amp;gt;
  &amp;lt;/symbol&amp;gt;
  &amp;lt;symbol id=&quot;play&quot; viewBox=&quot;0 0 15 15&quot;&amp;gt;
    &amp;lt;path d=&quot;M7.5,0.5c3.9,0,7,3.1,7,7c0,3.9-3.1,7-7,7c-3.9,0-7-3.1-7-7l0,0C0.5,3.6,3.6,0.5,7.5,0.5 C7.5,0.5,7.5,0.5,7.5,0.5L7.5,0.5L7.5,0.5z M6.1,4.7v5.6l4.2-2.8L6.1,4.7z&quot;/&amp;gt;
  &amp;lt;/symbol&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can do this manually for all your icons, but there are tools that automate the process. We use &lt;a href=&quot;https://github.com/Hiswe/gulp-svg-symbols&quot;&gt;gulp-svg-symbols&lt;/a&gt; (here’s &lt;a href=&quot;https://github.com/kaliop/gulp-assets-builder&quot;&gt;our gulp config&lt;/a&gt;, if you’re curious), but there are several graphical and command-line tools that export SVG symbol sprites, including &lt;a href=&quot;https://icomoon.io/&quot;&gt;Icomoon&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Pro tip: Keep a folder with your source icons&lt;/h3&gt;
&lt;p&gt;If you make your sprite manually, I recommend keeping a folder with individual SVG icons:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;assets/
  icons/
    cross.svg
    play.svg
    search.svg
    ...
public/
  sprite/
    icons.svg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then if you need to rebuild the &lt;code&gt;icons.svg&lt;/code&gt; or change an individual icon, you have the right sources to work with. Be careful to not let your sprite and your source folder get out of sync.&lt;/p&gt;
&lt;p&gt;Of course if you use a build process (with Gulp or Webpack or something else), you can feed it your source folder and let it build the sprite directly.&lt;/p&gt;
&lt;h3&gt;Important: not everything is an icon&lt;/h3&gt;
&lt;p&gt;Just because something is SVG, it doesn’t mean it should go inside your SVG sprite. For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Illustrations that you don’t need to style: keep as a single SVG file, and include it in your page with &lt;code&gt;&amp;lt;img src=&quot;url/to/illustration.svg&quot; alt=&quot;…&quot;&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Illustrations that you want to animate: consider inlining the full SVG code in your HTML page, so you can select and style specific groups or paths or shapes, animate some parts, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a rule of thumb, if it’s a big illustration that you need to show at 100px by 100px or much bigger, or if it contains dozens of elements, it might not be an “icon”. It would probably bloat your sprite’s size, too.&lt;/p&gt;
&lt;h2 id=&quot;section-html&quot;&gt;Adding icons to your pages&lt;/h2&gt;
&lt;p&gt;The bad news is that in order to use our SVG icons, we have to change our HTML code. No CSS backgrounds, no &lt;code&gt;::before&lt;/code&gt; pseudo-elements. The least verbose HTML we can use is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg&amp;gt;
  &amp;lt;use xlink:href=&quot;https://fvsch.com/path/to/icons.svg#play&quot;&amp;gt;&amp;lt;/use&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which can look like this:&lt;/p&gt;
&lt;figure class=&quot;frame padding&quot;&gt;&lt;svg&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#play&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;span class=&quot;access-label&quot;&gt;An unstyled SVG icon&lt;/span&gt;
&lt;/figure&gt;
&lt;p&gt;Now we’re going to make this HTML a bit more verbose, in order to make it easier to style and also more accessible. I’ll show a few common patterns first, and then we’ll explain each technical choice.&lt;/p&gt;
&lt;h3&gt;Pattern A: a purely decorative icon&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&amp;gt;
  &amp;lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/sprite.svg#symbol-id&quot;&amp;gt;&amp;lt;/use&amp;gt;
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Pattern B: link or button with icon and text&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a href=&quot;https://fvsch.com/news&quot;&amp;gt;
  &amp;lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&amp;gt;
    &amp;lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/sprite.svg#newspaper&quot;&amp;gt;&amp;lt;/use&amp;gt;
  &amp;lt;/svg&amp;gt;
  Latest News
&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Pattern C: link or button with icon&lt;/h3&gt;
&lt;p&gt;This is the same as (B), but we only want to show an icon. We should still keep the text around, so that screen reader users can read it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;a href=&quot;https://fvsch.com/news&quot; title=&quot;Latest News&quot;&amp;gt;
  &amp;lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&amp;gt;
    &amp;lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/sprite.svg#newspaper&quot;&amp;gt;&amp;lt;/use&amp;gt;
  &amp;lt;/svg&amp;gt;
  &amp;lt;span class=&quot;access-label&quot;&amp;gt;Latest News&amp;lt;/span&amp;gt;
&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Pattern D: icon and alt text outside of a link or button&lt;/h3&gt;
&lt;p&gt;For example, if we had a table column with icons representing “Yes” and “No”:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;td title=&quot;Yes&quot;&amp;gt;
  &amp;lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&amp;gt;
    &amp;lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/sprite.svg#check&quot;&amp;gt;&amp;lt;/use&amp;gt;
  &amp;lt;/svg&amp;gt;
  &amp;lt;span class=&quot;access-label&quot;&amp;gt;Yes&amp;lt;/span&amp;gt;
&amp;lt;/td&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;What do our different HTML parts do?&lt;/h3&gt;
&lt;table&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot;&gt;HTML pattern&lt;/th&gt;
    &lt;th scope=&quot;col&quot;&gt;What it does&lt;/th&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;a title=&quot;My label&quot;&amp;gt;&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;This is mostly a usability enhancement for some users: when you’re only showing an icon to represent a feature, it can help to show a tooltip on mouseover.&lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;span class=&quot;access-label&quot;&amp;gt;…&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;We’re using a visually hidden &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; to provide accessible text. Other techniques like &lt;code&gt;aria-label&lt;/code&gt; or SVG’s &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; element are not that well supported in browsers and screen readers, so this is the most reliable way. We’ll discuss the exact styles to use for our &lt;code&gt;access-label&lt;/code&gt; class in the next section.&lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;aria-hidden=&quot;true&quot;&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Since we’re already including accessible text outside of the SVG element, we can tell screen readers to ignore it. This prevents issues where some readers might try to read something for the icon, or read the link or button’s text twice.&lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;focusable=&quot;false&quot;&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;This one is for IE and Edge. In those browsers, the SVG element can get keyboard focus. This can result in the surrounding link or button being focused twice, and being read twice.&lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;class=&quot;icon&quot;&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;We could style the SVG element directly, but adding a class and reusing that class everywhere lets us define useful default styles for our SVG icons.&lt;/td&gt;
  &lt;/tr&gt;&lt;/table&gt;
&lt;h3&gt;Using meaningful and localized labels&lt;/h3&gt;
&lt;p&gt;Some articles recommend associating a text label with each SVG file in your icon repository (e.g. by putting a &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; element in each &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt;). This is a mistake, because a visual symbol can have different meanings depending on context.&lt;/p&gt;
&lt;p&gt;A “magnifying glass” icon can mean “Show the search form” in one context, and “Submit my search query” in a second context. Your text labels should then be “Show the search form” and “Submit search query”, not “Search” (too generic) or worse “Magnifying glass” (describes the visuals and not the meaning).&lt;/p&gt;
&lt;p&gt;On multilingual sites, your text labels should also be localized, including all text labels that are hidden by default for sighted users.&lt;/p&gt;
&lt;h3&gt;External and inline sprites&lt;/h3&gt;
&lt;p&gt;Up until now we’ve shown examples for an &lt;em&gt;external&lt;/em&gt; sprite. But some older browsers — namely, IE 9 to IE 11 — only support &lt;em&gt;inline&lt;/em&gt; references for &lt;code&gt;&amp;lt;use xlink:href=&quot;#some-id&quot;/&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This can be polyfilled with some JavaScript (&lt;a href=&quot;https://github.com/jonathantneal/svg4everybody&quot;&gt;svg4everybody&lt;/a&gt;, &lt;a href=&quot;https://github.com/Keyamoon/svgxuse&quot;&gt;svgxuse&lt;/a&gt;). Or you can decide to include your sprite &lt;em&gt;in the HTML code of every page&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;body&amp;gt;
  &amp;lt;!-- Hidden icon data --&amp;gt;
  &amp;lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; style=&quot;display:none&quot;&amp;gt;
    &amp;lt;symbol id=&quot;icon-play&quot;&amp;gt;…&amp;lt;/symbol&amp;gt;
    &amp;lt;symbol id=&quot;icon-cross&quot;&amp;gt;…&amp;lt;/symbol&amp;gt;
    &amp;lt;symbol id=&quot;icon-search&quot;&amp;gt;…&amp;lt;/symbol&amp;gt;
  &amp;lt;/svg&amp;gt;

  &amp;lt;!-- A visible icon --&amp;gt;
  &amp;lt;button&amp;gt;
    &amp;lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&amp;gt;
      &amp;lt;use xlink:href=&quot;#icon-play&quot;&amp;gt;&amp;lt;/use&amp;gt;
    &amp;lt;/svg&amp;gt;
    &amp;lt;span class=&quot;access-label&quot;&amp;gt;Start playback&amp;lt;/span&amp;gt;
  &amp;lt;/button&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each method has its pros and cons.&lt;/p&gt;
&lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;
    &lt;th scope=&quot;col&quot;&gt;Inline sprite&lt;/th&gt;
    &lt;th scope=&quot;col&quot;&gt;External sprite&lt;/th&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;
      Browser support
    &lt;/th&gt;
    &lt;td&gt;
      &lt;p&gt;Native support in IE9+.&lt;/p&gt;
      &lt;p&gt;Older Safari/WebKit &lt;a href=&quot;../symbol-sprite/&quot;&gt;needs the sprite at the beginning of the page&lt;/a&gt; (before any reference).&lt;/p&gt;
    &lt;/td&gt;
    &lt;td&gt;
      &lt;p&gt;Native support in Edge 13+, Safari 9+.&lt;/p&gt;
      &lt;p&gt;IE and older Safari/WebKit need a JS polyfill such as &lt;a href=&quot;https://github.com/jonathantneal/svg4everybody&quot;&gt;svg4everybody&lt;/a&gt; or &lt;a href=&quot;https://github.com/Keyamoon/svgxuse&quot;&gt;svgxuse&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;Loading an external sprite from a different domain doesn’t work for now (even with CORS, see &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=470601&quot;&gt;Chromium bug&lt;/a&gt; for instance). This can be polyfilled with JS using svgxuse.&lt;/p&gt;
    &lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;
      Caching
    &lt;/th&gt;
    &lt;td&gt;
      &lt;p&gt;No caching.&lt;/p&gt;
      &lt;p&gt;The weight of your sprite (5KB, 15KB, 50KB…) is added to every page.&lt;/p&gt;
    &lt;/td&gt;
    &lt;td&gt;
      &lt;p&gt;Sprite is a separate file and can be cached by the browser.&lt;/p&gt;
      &lt;p&gt;Also it does not bloat your server-side HTTP cache.&lt;/p&gt;
    &lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;
      Rendering speed
    &lt;/th&gt;
    &lt;td&gt;
      &lt;p&gt;Icons are rendered instantly.&lt;/p&gt;
      &lt;p&gt;On slow networks, rendering of the page structure and content may be delayed if you have a heavy SVG sprite inserted before your content.&lt;/p&gt;
    &lt;/td&gt;
    &lt;td&gt;
      &lt;p&gt;Icons can be shown a bit late because a) they require a separate HTTP request and b) the browser did not prioritize loading this SVG file (using its &lt;a href=&quot;http://andydavies.me/blog/2013/10/22/how-the-browser-pre-loader-makes-pages-load-faster/&quot;&gt;look-ahead pre-parser&lt;/a&gt;).&lt;/p&gt;
    &lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;Updating&lt;/th&gt;
    &lt;td&gt;
      &lt;p&gt;When you need to update your sprite, you need to make sure it’s added to every single page. This regenerating all HTML pages if you use a static generator, or invalidating all caches.&lt;/p&gt;
    &lt;/td&gt;
    &lt;td&gt;
      &lt;p&gt;Only one public file is changed, so managing updates and server-side caching is a bit easier.&lt;/p&gt;
    &lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;&lt;th scope=&quot;row&quot;&gt;With a CDN&lt;/th&gt;
    &lt;td&gt;
      &lt;p&gt;Not impacted.&lt;/p&gt;
    &lt;/td&gt;
    &lt;td&gt;
      &lt;p&gt;Warning: browsers will not load SVG referenced with &lt;code&gt;xlink:href&lt;/code&gt; on a different domain or subdomain (cross-origin)! If you need to load a cross-origin SVG sprite, you will need to load it with JavaScript and inject it in the page.&lt;/p&gt;
    &lt;/td&gt;
  &lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;I like mixing both methods, building two sprites:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A small one with essential icons (e.g. main icons used in the page header), to be inlined on each page. Target size: 5KB or less.&lt;/li&gt;
&lt;li&gt;A bigger one with all the project’s icons, kept as an external file. Target size: 50KB or less.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On bigger projects, we might add more external sprites when some icons can be grouped together and are only used in one part of the site or for a specific feature.&lt;/p&gt;
&lt;h2 id=&quot;section-styling&quot;&gt;Styling icons with CSS&lt;/h2&gt;
&lt;p&gt;Now that we have SVG icons and a SVG sprite, and that we learned how to add icons to our HTML, let’s look at the CSS side.&lt;/p&gt;
&lt;h3&gt;Starting with base styles&lt;/h3&gt;
&lt;p&gt;Here are the styles for our two utility classes, &lt;code&gt;icon&lt;/code&gt; and &lt;code&gt;access-label&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/**
 * Visually hidden accessible label
 * Using styles that do not hide the text in screen readers
 * We use !important because we should not apply other styles to this hidden alternative text
 */
.access-label {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
  white-space: nowrap !important;
}

/**
 * Default icon style
 */
.icon {
  /* Use the current text color as the icon’s fill color. */
  fill: currentColor;
  /* Inherit the text’s size too. Also allows sizing the icon by changing its font-size. */
  width: 1em;
  height: 1em;
  /* The default vertical-align is `baseline`, which leaves a few pixels of space below the icon. Using `center` prevents this. For icons shown alongside text, you may want to use a more precise value, e.g. `vertical-align: -4px` or `vertical-align: -0.15em`. */
  vertical-align: middle;
  /* Paths and strokes that overflow the viewBox can show in IE11. */
  overflow: hidden;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that code, by default your SVG icons should be small and use the parent’s text color. Like this:&lt;/p&gt;
&lt;figure class=&quot;frame padding&quot;&gt;&lt;div&gt;
    &lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#play&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#cross&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#check&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;span class=&quot;access-label&quot;&gt;First row of icons with default text size and color&lt;/span&gt;
  &lt;/div&gt;
  &lt;div style=&quot;font-size: 500%; color: green; line-height: 1.2;&quot;&gt;
    &lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#play&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#cross&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#check&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;span class=&quot;access-label&quot;&gt;Second row of icons with bigger text size and a green text color&lt;/span&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;
    Icons with our default style. The only change between the top and bottom row is the font-size and color of the container.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If the icon’s shapes do not inherit the parent’s text color (&lt;code&gt;currentColor&lt;/code&gt;), check that you don’t have hardcoded &lt;code&gt;fill&lt;/code&gt; attributes in your icon’s code.&lt;/p&gt;
&lt;h3&gt;Why not style the &lt;code&gt;svg&lt;/code&gt; selector directly?&lt;/h3&gt;
&lt;p&gt;We might have other SVG elements in the page that are not icons. For example, a big SVG map, or an interactive SVG game. Another reason was that older Firefox versions (before Firefox 56) had a bug with the &lt;code&gt;svg&lt;/code&gt; selector (it would apply inside the &lt;code&gt;&amp;lt;use&amp;gt;&lt;/code&gt; element), and targetting a class avoided this issue.&lt;/p&gt;
&lt;p&gt;Feel free to use the &lt;code&gt;svg&lt;/code&gt; selector instead of &lt;code&gt;.icon&lt;/code&gt; if you think that would work for your use case.&lt;/p&gt;
&lt;h3&gt;Overriding the base styles&lt;/h3&gt;
&lt;p&gt;It should be easy to override our base styles in a specific context:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.ShareComponent .icon {
  /* Change the width and height */
  font-size: 40px;
  /* Change the fill color */
  color: purple;
  /* Change the vertical-align if you need more precision
     (sometimes needed for pixel-perfect rendering) */
  vertical-align: -4px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Inherited SVG styles&lt;/h3&gt;
&lt;p&gt;Many SVG style properties are inherited. For instance when we set the &lt;code&gt;fill&lt;/code&gt; CSS property on our containing &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element, it trickles down to our &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt; and other graphic elements.&lt;/p&gt;
&lt;p&gt;We can use this technique for other SVG CSS properties as well. For instance the stroke properties:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.icon-goldstar {
  fill: gold;
  stroke: coral;
  stroke-width: 5%;
  stroke-linejoin: round;
}&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;frame padding&quot;&gt;&lt;div style=&quot;font-size: 800%; line-height: 1;&quot;&gt;
    &lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#star&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon icon-goldstar&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#star&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/div&gt;
  &lt;figcaption&gt;
    Star icon with default and custom styling
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Most of the time we’re not going to change a lot of stuff: only the &lt;code&gt;fill&lt;/code&gt; property for the main color, and sometimes we’ll add or tweak a &lt;code&gt;stroke&lt;/code&gt; (kinda like a border).&lt;/p&gt;
&lt;h3&gt;Two fill colors per icon&lt;/h3&gt;
&lt;p&gt;There’s a fairly simple technique which allows an icon to have two set of paths with two different &lt;code&gt;fill&lt;/code&gt; values (aka two colors).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;symbol id=&quot;check&quot; viewBox=&quot;0 0 20 20&quot;&amp;gt;
  &amp;lt;!-- Will inherit the value of the fill CSS property --&amp;gt;
  &amp;lt;path d=&quot;…&quot; /&amp;gt;
  &amp;lt;!-- Will inherit the value of the color CSS property --&amp;gt;
  &amp;lt;path fill=&quot;currentColor&quot; d=&quot;…&quot; /&amp;gt;
&amp;lt;/symbol&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.icon-bicolor {
  fill: rebeccapurple;
  color: mediumturquoise;
}&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;frame padding&quot;&gt;&lt;div style=&quot;font-size: 800%; line-height: 1;&quot;&gt;
    &lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#check&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon icon-two1&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#check&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon icon-two2&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#check&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/div&gt;
  &lt;figcaption&gt;
    Two color icons
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;Tip: leave some room for strokes&lt;/h3&gt;
&lt;p&gt;Remember when we said to leave some room around your shapes? It’s especially important if you plan to use strokes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.icon-strokespace {
  fill: none;
  stroke: currentColor;
  stroke-width: 5%;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In SVG, strokes are always painted on both sides of the path (inside and outside). If your path touches the limits of the viewport, half the stroke will be cut off.&lt;/p&gt;
&lt;figure class=&quot;frame padding&quot;&gt;&lt;div style=&quot;font-size: 750%; line-height: 1;&quot;&gt;
    &lt;svg class=&quot;icon icon-strokespace icon-strokespace1&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#play-no-space&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon icon-strokespace icon-strokespace2&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#play&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/div&gt;
  &lt;figcaption&gt;
    In this example, the first icon has no reserved blank space on the sides, and the second icon has a small one (0.5px, for a 15px viewport).
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;Tip: use percentages for &lt;code&gt;stroke-width&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Giving the right size to a stroke can be challenging. Look at these two examples where we set &lt;code&gt;stroke-width:1px&lt;/code&gt; on the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element:&lt;/p&gt;
&lt;figure&gt;&lt;div style=&quot;font-size: 800%; line-height: 1; color: hotpink; stroke-width: 1px;&quot;&gt;
    &lt;svg class=&quot;icon icon-stroketest&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#heart1&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon icon-stroketest&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#heart2&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;What’s happening? The &lt;code&gt;stroke-width&lt;/code&gt; property takes a “length” value, but that value is relative to the &lt;em&gt;local coordinates&lt;/em&gt; of your icon. In the examples above:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The first icon has a 20px by 20px &lt;code&gt;viewBox&lt;/code&gt;. So a 1px stroke is 1/20th of the icon’s size, big but not too big.&lt;/li&gt;
&lt;li&gt;The second icon has a 500px by 500px &lt;code&gt;viewBox&lt;/code&gt;. So a 1px stroke is 1/500th of the icon’s size, and that really really small.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If all your icons use a similar &lt;code&gt;viewBox&lt;/code&gt;, that’s not a problem. But if they can differ wildly, using pixel or unitless values (&lt;code&gt;stroke-width:1&lt;/code&gt;) is out. What should we do?&lt;/p&gt;
&lt;p&gt;Percentages can be good. Same example, with &lt;code&gt;stroke-width:5%&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;div style=&quot;font-size: 800%; line-height: 1; color: darkturquoise; stroke-width: 5%;&quot;&gt;
    &lt;svg class=&quot;icon icon-stroketest&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#heart1&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon icon-stroketest&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#heart2&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Now that’s better. (For square icons, &lt;code&gt;stroke-width:N%&lt;/code&gt; will work perfectly, but note that it can behave a bit differently &lt;a href=&quot;#percent-stroke-width&quot;&gt;for large or tall SVG elements&lt;/a&gt;.)&lt;/p&gt;
&lt;h2 id=&quot;section-advanced&quot;&gt;Advanced topics and tricks&lt;/h2&gt;
&lt;p&gt;With the previous sections you should have everything you need to know to use SVG icons. This next section adds some more advanced information, with perhaps fewer real-world applications.&lt;/p&gt;
&lt;h3&gt;Avoiding really big unstyled icons&lt;/h3&gt;
&lt;p&gt;What happens if your main stylesheet fails to load because the user is on a flaky connection, maybe on a train (this happens to me all the time)? The page might still render without styles.&lt;/p&gt;
&lt;p&gt;If you have a decent HTML structure, the page will still be readable, but your icons will end up really big.&lt;/p&gt;
&lt;figure class=&quot;frame padding border&quot;&gt;&lt;img src=&quot;https://fvsch.com/articles/svg-icons/unstyled.png&quot; alt=&quot;Screenshot of unstyled SVG icons&quot;&gt;&lt;figcaption&gt;Recent browsers size the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element at 300 by 150 pixels by default. Other browsers might give it a gigantic 100% width!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I recommend putting this in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your pages:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;style&amp;gt;.icon{width:1em;height:1em}&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Short and sweet.&lt;/p&gt;
&lt;p&gt;Another approach is to always use width and height attributes on your SVG elements. It works, but if you need to resize this icon in CSS it might be a bit harder.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;svg width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&amp;gt;
  …
&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Preloading external sprites&lt;/h3&gt;
&lt;p&gt;In the “Adding icons to your pages” section we said that icons from an external sprite can show up a bit late, partly because of the browser’s pre-load scanner (or look-ahead pre-parser or whatever) not picking up that &lt;code&gt;&amp;lt;use xlink:href=&quot;https://fvsch.com/path/to/icons.svg#something&quot;&amp;gt;&amp;lt;/use&amp;gt;&lt;/code&gt; means there is an important file to load early.&lt;/p&gt;
&lt;p&gt;What can we do about this?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Standard and future solution: add &lt;code&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;https://fvsch.com/path/to/icons.svg&quot; as=&quot;image&quot;&amp;gt;&lt;/code&gt; in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the page (&lt;a href=&quot;https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/&quot;&gt;details on preload&lt;/a&gt;, support coming to a Chrome near you and hopefully in more browsers in the future).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Old school solution: add &lt;code&gt;&amp;lt;img style=&quot;display:none&quot; alt=&quot;&quot; src=&quot;https://fvsch.com/path/to/icons.svg&quot;&amp;gt;&lt;/code&gt; at the start of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I haven’t actually tested those solutions, generally the “inline + external” compromise gives good enough performance that we don’t have to rely on preloading. But it’s worth investigating.&lt;/p&gt;
&lt;h3&gt;Selecting individual shapes or paths&lt;/h3&gt;
&lt;p&gt;We’ve looked at ways to customize fills, strokes etc. for all paths from a &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt;, and 2 or more colors from different paths. But what if we could select specific paths (using classes maybe) directly in the &lt;em&gt;instance&lt;/em&gt; of the &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt;? Is it possible?&lt;/p&gt;
&lt;p&gt;Right now the answer is: yes and no.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you use external sprites, you can’t select individual paths (or other elements) inside a used &lt;code&gt;&amp;lt;symbol&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you inline your sprite, you can select and style elements inside the sprite, but those styles will apply to &lt;em&gt;all instances&lt;/em&gt; of the symbols.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So even with an inlined sprite, you could do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;#my-symbol .style1 {
  /* Styles for one group of paths */
}
#my-symbol .style2 {
  /* Styles for another */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But you can’t do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.MyComponent-button .icon .style1 {
  /* For 1 group of paths for this icon in this context */
}
.MyComponent-button .icon .style2 {
  /* For another group */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the future, there might be a standard way that allows selecting through a Shadow DOM, but that’s not sure at all (there used to be the &lt;code&gt;/deep/&lt;/code&gt; combinator, but it was removed).&lt;/p&gt;
&lt;h3&gt;More than two colors with CSS Custom Properties&lt;/h3&gt;
&lt;p&gt;So we can easily change colors of our SVG icons from CSS, for single-color icons (easy) and two-color icons (takes some preparation). Is there a way we could have multicolor icons with &lt;em&gt;more than two customizable colors&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;We might be able to do that with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables&quot;&gt;CSS Custom Properties&lt;/a&gt; (aka CSS Variables). This requires a lot of preparation on the SVG side:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;symbol id=&quot;iconic-aperture&quot; viewBox=&quot;0 0 128 128&quot;&amp;gt;
  &amp;lt;path fill=&quot;var(--icon-color1)&quot; d=&quot;…&quot; /&amp;gt;
  &amp;lt;path fill=&quot;var(--icon-color2)&quot; d=&quot;…&quot; /&amp;gt;
  &amp;lt;path fill=&quot;var(--icon-color3)&quot; d=&quot;…&quot; /&amp;gt;
  &amp;lt;path fill=&quot;var(--icon-color4)&quot; d=&quot;…&quot; /&amp;gt;
  &amp;lt;path fill=&quot;var(--icon-color5)&quot; d=&quot;…&quot; /&amp;gt;
  &amp;lt;path fill=&quot;var(--icon-color6)&quot; d=&quot;…&quot; /&amp;gt;
&amp;lt;/symbol&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this demo I stole an icon from the excellent &lt;a href=&quot;https://useiconic.com/&quot;&gt;Iconic&lt;/a&gt;, which offers responsive, multicolor SVG icons (powered by CSS and some JS, as I understand). I tried to mimick their own multicolor example for this icon, I hope they don’t mind.&lt;/p&gt;
&lt;figure class=&quot;frame padding&quot;&gt;&lt;span style=&quot;font-size: 800%; line-height: 1;&quot;&gt;
    &lt;svg class=&quot;icon&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#iconic-aperture&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;span style=&quot;font-size: 800%; line-height: 1;&quot;&gt;
    &lt;svg class=&quot;icon icon-vars1&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#iconic-aperture&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;span style=&quot;font-size: 800%; line-height: 1;&quot;&gt;
    &lt;svg class=&quot;icon icon-vars2&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#iconic-aperture&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;figcaption&gt;
    One symbol using 6 different CSS custom properties.&lt;br&gt;
    See in Firefox, Chrome, or Safari 9.1+
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It works fairly well &lt;a href=&quot;https://caniuse.com/#feat=css-variables&quot;&gt;in supported browsers&lt;/a&gt;. There’s only one icon: the first instance doesn’t declare the expected variables, so it falls back to currentColor; the next two instances each declare a set of variable values.&lt;/p&gt;
&lt;h3 id=&quot;percent-stroke-width&quot;&gt;How are percentage &lt;code&gt;stroke-width&lt;/code&gt; computed?&lt;/h3&gt;
&lt;p&gt;What does the percentage correspond to in &lt;code&gt;stroke-width:N%&lt;/code&gt;? Is it the width, the height of the icon? Turns out it’s relative to the diagonal, but with a &lt;a href=&quot;https://svgwg.org/svg2-draft/coords.html#Units&quot;&gt;funky formula (spec)&lt;/a&gt; (diagonal length divided by the square root of 2, which is close to 1.4).&lt;/p&gt;
&lt;p&gt;What does it mean? Well for square icons the result of that formula is the side of the square. So &lt;code&gt;1%&lt;/code&gt; means “one percent of the width or one percent of the height”. Nice and simple.&lt;/p&gt;
&lt;p&gt;For wider or taller icons, though, the result can change a bit:&lt;/p&gt;
&lt;figure class=&quot;frame padding&quot;&gt;&lt;div style=&quot;font-size: 800%; line-height: 1; stroke-width: 5%;&quot;&gt;
    &lt;svg class=&quot;icon icon-stroketest&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot; style=&quot;color:orchid&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#heart1&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;svg class=&quot;icon icon-stroketest&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot; style=&quot;color:mediumpurple;width:2em;&quot;&gt;&lt;use xlink:href=&quot;https://fvsch.com/articles/svg-icons/icons.svg#hearts&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/div&gt;
  &lt;figcaption&gt;
    In the second icon (aspect ratio of 2:1, shown with the same height and twice as large), the &lt;code&gt;stroke-width:5%&lt;/code&gt; gives us a stroke that is roughly: 7.91% of the height and 3.95% of the width.
  &lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;All things considered, I still recommend using percentage values for &lt;code&gt;stroke-width&lt;/code&gt;. If you stick to square or sharish icon, you can use percent values with the understanding that they mean, roughly, “percentage of the width of the icon”.&lt;/p&gt;
&lt;h3&gt;Using gradient fills&lt;/h3&gt;
&lt;p&gt;We can’t use CSS &lt;code&gt;linear-gradient(…)&lt;/code&gt; because this sadly cannot apply to the SVG &lt;code&gt;fill&lt;/code&gt; property. If we want gradient fills, we will need to &lt;a href=&quot;https://fvsch.com/svg-gradient-fill&quot;&gt;use SVG gradients&lt;/a&gt;.&lt;/p&gt;&lt;!-- Demo styles --&gt;
&lt;style&gt;
.access-label {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
  white-space: nowrap !important;
}
.icon {
  width: 1em;
  height: 1em;
  vertical-align: middle;
  fill: currentColor;
  overflow: hidden;
}
.icon-goldstar {
  fill: gold;
  stroke: coral;
  stroke-width: 5%;
  stroke-linejoin: round;
}
.icon-strokespace {
  margin: 5px;
  fill: none;
  stroke: currentColor;
  stroke-width: 5%;
}
.icon-strokespace1 {
  color: rgba(148,0,211,1);
  outline: solid 1px rgba(148,0,211,.5);
  background-color: rgba(148,0,211,.05);
}
.icon-strokespace2 {
  color: deepskyblue;
    color: rgba(0,160,240,1);
    outline: solid 1px rgba(0,160,240,.5);
    background-color: rgba(0,160,240,.05);
}
.icon-stroketest {
  fill: none;
  stroke: currentColor;
}
.icon-two1 {
  fill: firebrick;
  color: darkkhaki;
}
.icon-two2 {
  fill: rebeccapurple;
  color: mediumturquoise;
}
.icon-vars1 {
  --icon-color1: #F69EC3;
  --icon-color2: #F487B5;
  --icon-color3: #F26FA6;
  --icon-color4: #F05898;
  --icon-color5: #EE4089;
  --icon-color6: #EC297B;
}
.icon-vars2 {
  --icon-color1: #80B23C;
  --icon-color2: #2A91A8;
  --icon-color3: #4A46AA;
  --icon-color4: #DA3A35;
  --icon-color5: #F16522;
  --icon-color6: #FCB400;
}
&lt;/style&gt;</content>
  </entry>
</feed>
