<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mayank]]></title><description><![CDATA[Mayank]]></description><link>https://blog.mayank.co</link><generator>RSS for Node</generator><lastBuildDate>Fri, 23 Feb 2024 07:41:58 GMT</lastBuildDate><atom:link href="https://blog.mayank.co/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><atom:link rel="next" href="https://blog.mayank.co/rss.xml?page=1"/><item><title><![CDATA[My CSS Wishlist]]></title><description><![CDATA[<p>Maybe a few weeks late to the party, but better late than never. Today I will talk about the things I wish CSS had.</p><p>CSS has been getting so many awesome features lately, and some of the items on this list are already being worked on, while others should totally be achievable in the near future. But just for fun, I will also include a few stretch goals that might only be possible with some magic wand 🪄.</p><h2 id="heading-arbitrary-mixins">Arbitrary mixins</h2><p>What I mean by this is basically Sass mixins. I want to be able to include any arbitrary set of declarations <em>or</em> rules inside another rule. This arbitrary CSS could also come from a different file.</p><pre><code class="lang-css"><span class="hljs-keyword">@import</span> url(<span class="hljs-string">'./utils.css'</span>) mixins(visually-hidden);<span class="hljs-selector-class">.skip-to-content</span><span class="hljs-selector-pseudo">:not(</span><span class="hljs-selector-pseudo">:focus</span>, <span class="hljs-selector-pseudo">:active)</span> {  @apply visually-hidden;}</code></pre><p>We're kinda getting there with <a target="_blank" href="https://una.im/style-queries/#4-grouping-styles-with-higher-order-variables">style queries</a>, but the problem is that a container cannot query itself so the "trigger" needs to be set on the parent element.</p><p>Edit: The more I think about this, the more I just want classes to be able to <a target="_blank" href="https://sass-lang.com/documentation/at-rules/extend/">extend</a> other classes. It feels simpler and is more "CSS-y".</p><h2 id="heading-visually-hidden">Visually hidden</h2><p>Sure would be nice if I didn't have to define an <a target="_blank" href="https://www.tpgi.com/the-anatomy-of-visually-hidden/">esoteric set of declarations</a> for hiding content visually while keeping it accessible to assistive technologies.</p><pre><code class="lang-css"><span class="hljs-selector-class">.skip-to-content</span><span class="hljs-selector-pseudo">:not(</span><span class="hljs-selector-pseudo">:focus</span>, <span class="hljs-selector-pseudo">:active)</span> {  <span class="hljs-attribute">display</span>: visually-hidden;}</code></pre><h2 id="heading-scoping">Scoping</h2><p>I talk about this one a lot. Scoping is one of the <em>essential</em> features missing from CSS currently. Naturally, this leads to developers (<a target="_blank" href="https://hashnode.com/post/cld7lkp57000309lc3g4t70v1">including me</a>!) coming up with their own hacky ways to manage scoping.</p><p>The good news is that <a target="_blank" href="https://drafts.csswg.org/css-cascade-6/#scoped-styles"><code>@scope</code></a> is being worked on, even if it's a little confusing. The most interesting bit there is that it allows defining a lower boundary (aka <a target="_blank" href="http://www.stubbornella.org/content/2011/10/08/scope-donuts/">CSS donuts</a>).</p><p>🪄 The dream would be to extend <a target="_blank" href="https://web.dev/css-module-scripts/">native CSS module scripts</a> (and constructable stylesheets) to behave more like non-native <a target="_blank" href="https://github.com/css-modules/css-modules">CSS modules</a> so that the classes can be referenced in JavaScript or HTML. One of the challenges here is that this needs to also work at build time (outside the browser) for optimal performance.</p><pre><code class="lang-javascript"><span class="hljs-keyword">import</span> sheet <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles.css'</span> assert { <span class="hljs-attr">type</span>: <span class="hljs-string">'css'</span> };<span class="hljs-keyword">const</span> styles = sheet.scopedClasses;<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> <span class="hljs-comment">/*html*/</span><span class="hljs-string">`  &lt;div class=<span class="hljs-subst">${styles.wrapper}</span>&gt;    &lt;span class=<span class="hljs-subst">${styles.inner}</span>&gt;hi&lt;/span&gt;  &lt;/div&gt;`</span>;</code></pre><h2 id="heading-auto-color-contrast">Auto color contrast</h2><p>I want to be able to dynamically find colors that sufficiently contrast with the background. There are ongoing discussions about a <a target="_blank" href="https://drafts.csswg.org/css-color-6/#colorcontrast"><code>color-contrast()</code></a> function, and some version of it is even implemented in Safari TP.</p><pre><code class="lang-css"><span class="hljs-selector-class">.foo</span> {  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">var</span>(--bg);  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">color-contrast</span>(var(--bg) vs white, black);}</code></pre><p>But as I understand, this feature is currently blocked because the WCAG contrast algorithm is not perfect. Very unfortunate, because this is <em>not</em> something that can be easily achieved at build time - CSS variables only exist at runtime. Can I just have the "imperfect" version in the meantime and switch to a better algorithm when it's available? 🥺</p><h2 id="heading-svg-in-css">SVG-in-CSS</h2><p>We can use svgs from a url in a <code>background-image</code>, but we can't change its fill. To change the fill, we need to do the whole dance of using <a target="_blank" href="https://codepen.io/noahblon/post/coloring-svgs-in-css-background-images#css-masks-1"><code>mask</code> with <code>background-color</code></a>, which is a lot. And it's not perfect, as we don't get granular control over the paths inside the svgs.</p><p>Wouldn't it be nice if we could just use svgs inside CSS and do whatever we want with them, including inline CSS variables? 🪄</p><pre><code class="lang-css"><span class="hljs-selector-class">.icon</span><span class="hljs-selector-attr">[data-icon=sun]</span> {  <span class="hljs-attribute">--_fill</span>: orange;  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">svg</span>(<span class="hljs-string">'&lt;svg fill="var(--_fill)" viewBox="0 0 512 512"&gt;&lt;path d="M256 118a22 ..."/&gt;&lt;/svg&gt;'</span>);}<span class="hljs-selector-class">.icon</span><span class="hljs-selector-attr">[data-icon=moon]</span> {  <span class="hljs-attribute">--_fill</span>: wheat;  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">svg</span>(<span class="hljs-string">'&lt;svg fill="var(--_fill)" viewBox="0 0 20 20"&gt;&lt;path d="M17.39 15.14A7.33..."/&gt;&lt;/svg&gt;'</span>);}</code></pre><h2 id="heading-animateable-display">Animateable display</h2><p>First we got <a target="_blank" href="https://css-tricks.com/animating-css-grid-how-to-examples/">animateable <code>grid</code></a>, so the natural next step is to be able to animate between <code>height: 0</code> / <code>height: auto</code> and also to/from <code>display: none</code>.</p><h2 id="heading-static-variables">Static variables</h2><p>Custom properties are extremely powerful, but they <em>are</em> just properties, which means they have many limitations, such as not being able to use outside style rules.</p><p>What if we could have a less powerful (and therefore more flexible) version of variables, similar to Sass? We'd be able to use them anywhere, including inside strings and media queries. 🪄</p><pre><code class="lang-scss">__mobile: calc(<span class="hljs-number">500</span> / <span class="hljs-number">16</span> * <span class="hljs-number">1rem</span>);<span class="hljs-keyword">@media</span> (width &gt; __mobile) { ... }</code></pre><h2 id="heading-tooltips">Tooltips</h2><p>It shouldn't require a delicate contraption of <a target="_blank" href="https://floating-ui.com/docs/tutorial">precisely authored HTML+CSS+JS</a> to make a damn tooltip. The challenge here is to make it show in the top-layer (which traditionally requires <a target="_blank" href="https://beta.reactjs.org/reference/react-dom/createPortal">portal</a>ing the markup out near the root) and pin it to a reference element (which requires JavaScript to calculate position) and adjust the position when near the edge of the viewport (which requires even more JS).</p><p>Maybe 2023 will be the year of CSS <a target="_blank" href="https://drafts.csswg.org/css-anchor-position-1/">anchor positioning</a> which addresses all of these issues. (edit: maybe 2024)</p><h2 id="heading-double-slash-comments">Double slash comments</h2><p>To end on a lighter note, as a <a target="_blank" href="https://hashnode.com/post/cl4k5wv5p01eldynv7wziefo4">frequent Sass user</a>, I often find myself writing <code>// double slash comments</code> even in vanilla CSS files. I'm sure there is a technical, parsing-related reason why this cannot be made to work, but it certainly would be nice. 😅</p><hr /><h2 id="heading-other-wishlists">Other wishlists</h2><ul><li><p>Chris Coyier: <a target="_blank" href="https://chriscoyier.net/2022/12/21/things-css-could-still-use-heading-into-2023">Things CSS Could Still Use Heading Into 2023</a></p></li><li><p>Ahmad Shadeed: <a target="_blank" href="https://ishadeed.com/article/css-wishlist-2023">My CSS Wishlist</a></p></li><li><p>Eric Meyer: <a target="_blank" href="https://meyerweb.com/eric/thoughts/2023/02/08/css-wish-list-2023/">CSS Wish List 2023</a></p></li><li><p>Manuel Matuzovi: <a target="_blank" href="https://www.matuzo.at/blog/2023/css-wish-list/">My CSS wish list</a></p></li><li><p>Stephanie Eckles: <a target="_blank" href="https://thinkdobecreate.com/articles/css-wishlist-2023/">My CSS Wishlist 2023</a></p></li><li><p>Dave Rupert: <a target="_blank" href="https://daverupert.com/2023/01/css-wishlist-2023/">CSS Wishlist 2023</a></p></li><li><p>Tyler Sticka: <a target="_blank" href="https://cloudfour.com/thinks/tylers-css-wish-list-for-2023/">Tylers CSS Wish List for 2023</a></p></li><li><p>Jim Nielsen: <a target="_blank" href="https://blog.jim-nielsen.com/2023/css-wishlist/">My 2023 CSS Wishlist</a></p></li></ul>]]></description><link>https://blog.mayank.co/my-css-wishlist-2023</link><guid isPermaLink="true">https://blog.mayank.co/my-css-wishlist-2023</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Sun, 19 Feb 2023 16:56:53 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/IS6RwpuEJpY/upload/d929560813440a03633e52a6a65c63b4.jpeg</cover_image></item><item><title><![CDATA[Is CSS-in-JS actually bad?]]></title><description><![CDATA[<p>I have a confession: I used to not like CSS very much in my first few years of being a developer. I would dread opening up CSS files. I would cuss at z-index for causing me so many headaches. I would wish for someone else to write the CSS for me. I would make heavy use of pre-styled component libraries without a second thought.</p><p>Fast forward to today, where just thinking about CSS makes me giddy. I obsess over tiny CSS details, like <a target="_blank" href="https://blog.mayank.co/better-scrolling-through-modern-css#heading-theming-scrollbars">theming scrollbars</a>. I see a well-designed UI out in the wild, and the very first thing I do is pop open dev tools to try and figure out how it was made. I'm unafraid. I <em>enjoy</em> CSS.</p><p>So what changed? Well, I took the time to learn CSS. Oh and also the fact that CSS is now Good and only keeps getting better year after year. Even the smallest of things, like flexbox <code>gap</code>, make CSS feel more ergonomic and approachable.</p><p>Still, most new CSS features don't help with its maintainability, at least until recently... In 2021, we got the much needed <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:where"><code>:where</code></a> pseudo-class, which lets us control the specificity of selectors. In 2022, we got <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers">cascade layers</a>, which let us bypass selector specificity altogether (even for third-party styles!) and directly control the order of large chunks of styles. This is by far my favorite recent addition to CSS.</p><p>But it's only half of the equation.</p><h3 id="heading-scoping">Scoping</h3><p>You see, CSS maintainability is a two-dimensional problem. Layers solve the vertical aspect of it ("order"), but we still need something to manage its horizontality ("reach"). Just like we don't want our global reset styles overriding our component styles, similarly we don't want the styles for one component interfering with other components. If we do this right, we would also know exactly <em>what</em> is being used and <em>where</em>, meaning we can confidently remove dead code (something that has been historically too difficult to do in CSS).</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674264488325/23cb2425-0022-4edf-b3ff-0cb0cedbd1f1.png" alt="A colorful two dimensional grid, where the columns represent components such as Input, Button and Tab, and the rows represent layers such as theme, base, and page." class="image--center mx-auto" /></p><p><em>(Diagram based on</em> <a target="_blank" href="https://css-tricks.com/css-cascade-layers/#scoping-and-name-spacing-styles-nope"><em>Miriam Suzanne's guide to cascade layers</em></a><em>)</em></p><p>There is a spec being developed for <a target="_blank" href="https://www.w3.org/TR/css-cascade-6/#scoped-styles"><code>@scope</code></a> which will help with this across the board. Maybe we'll see it shipped in 2023? But until then, we're on our own.</p><p>Scoping is one of those problems that developers have attempted to solve the most. One of the simpler forms of scoping is achieved by using naming conventions like <a target="_blank" href="https://getbem.com/">BEM</a>. More formally, it is solved through tooling. <a target="_blank" href="https://github.com/css-modules/css-modules">CSS modules</a> (not the <a target="_blank" href="https://css-tricks.com/css-modules-the-native-ones/">native ones</a>) is probably the most popular and robust solution for scoping  it has been implemented by countless tools and has spawned similar variations in many other tools. It worked well then and continues to work well now, despite so many years of innovation both in browsers and in the developer ecosystem.</p><h3 id="heading-colocation">Colocation</h3><p>Having a solution only for scoping can often be enough, but we can do better. We often <em>want</em> better. We want our styles to live close to the markup that they are scoped to. This makes it easier to keep them in sync, change/delete them together, etc. (This is also the whole promise of a utility-first approach, right?)</p><p>This is where BEM, CSS modules, et al start to feel a bit unergonomic. The styles and markup live in different locations  the closest we can get is a CSS file in the same folder. And we still need to manually make the association. There is no Intellisense or "Go to definition" to travel back and forth between the two. It is easy for things to get out of sync. And the bigger the size of the team, the greater the chance of human error.</p><p>Admittedly, web components (specifically shadow DOM) have somewhat solved this problem. But, in my opinion, shadow DOM goes too far with its style encapsulation. I don't want to throw away the entire cascade, I simply want <em>some</em> (but not all) parts of it to be closely tied to specific elements.</p><p>That's where CSS-in-JS comes in. There are very clear DX benefits to libraries like styled-components and emotion*. They provide scoping and colocation on top of the full power of CSS. But remember: CSS was missing some key features back then, and developers <em>really</em> enjoy writing JavaScript. So why not use JavaScript as a pre-processor, post-processor, runtime-processor, everything-processor for <a target="_blank" href="https://styled-components.com/docs/advanced#theming">theming</a>, <a target="_blank" href="https://emotion.sh/docs/media-queries#reusable-media-queries">breakpoints</a>, <a target="_blank" href="https://styled-components.com/docs/basics#adapting-based-on-props">"variants"</a>, <a target="_blank" href="https://styled-components.com/docs/advanced#style-objects">"style objects"</a>, <a target="_blank" href="https://styled-components.com/docs/api#as-polymorphic-prop">"polymorphism"</a>, <a target="_blank" href="https://styled-components.com/docs/basics#motivation:~:text=Automatic%20critical%20CSS">"critical CSS"</a>, <a target="_blank" href="https://styled-components.com/docs/basics#extending-styles">"extending styles"</a>, even <a target="_blank" href="https://emotion.sh/docs/globals">global styles</a>.</p><p>...eh, what? I just wanted scoping and colocation. Why is <a target="_blank" href="https://helloyes.dev/blog/2022/mourning-my-obsolete-skillset/">JavaScript eating everything up?</a> And isn't there a tangible cost to all of this?</p><p><em>*The idea of CSS-in-JS actually <a target="_blank" href="https://blog.vjeux.com/2014/javascript/react-css-in-js-nationjs.html">predates</a> CSS modules (but not BEM). But it was a very different form of CSS-in-JS from what we see today with styled-components and emotion, so I've chosen to skip past that.</em></p><h3 id="heading-performance">Performance</h3><p>Calculating all styles in JavaScript at runtime kills performance (perhaps obviously). Even with <a target="_blank" href="https://www.zachleat.com/web/ssr-overloaded/">SSR</a>, it takes time for the page to become interactive. I have personally seen multi-second page loads on sites that use runtime CSS-in-JS.</p><p>It took a few years but the JavaScript community has started recognizing this issue. You might recall a recent blog post from Sam Magura, one of the maintainers of emotion: <a target="_blank" href="https://dev.to/srmagura/why-were-breaking-up-wiht-css-in-js-4g9b#the-ugly"><em>"Why we're breaking up with CSS-in-JS"</em></a><em>.</em> In that post, Sam goes into a performance deep dive, illustrating the problem with practical measurements and an insider perspective.</p><p>I should (again) briefly mention that web components can kinda fix this, through <a target="_blank" href="https://web.dev/constructable-stylesheets/">constructable stylesheets</a>. Even better, that API can be used outside web components to potentially improve the performance of runtime CSS-in-JS for light DOM (see <a target="_blank" href="https://github.com/emotion-js/emotion/issues/2501">github issue</a>). But we may be past that point, considering that maintainers are "breaking up" with the whole idea, and a large portion of their userbase has moved on to utility classes.</p><p>Besides, even the React core team now <a target="_blank" href="https://github.com/reactwg/react-18/discussions/110#:~:text=Our%20preferred%20solution%20is%20to%20use%20%3Clink%20rel%3D%22stylesheet%22%3E%20for%20statically%20extracted%20styles%20and%20plain%20inline%20styles%20for%20dynamic%20values.%20E.g.%20%3Cdiv%20style%3D%7B%7B...%7D%7D%3E.%20You%20could%20however%20build%20a%20CSS%2Din%2DJS%20library%20that%20extracts%20static%20rules%20into%20external%20files%20using%20a%20compiler.%20That%27s%20what%20we%20use%20at%20Facebook.">recommends</a> static solutions over runtime:</p><blockquote><p>"You could however build a CSS-in-JS library that extracts <em>static</em> rules into external files using a compiler. That's what we use at Facebook."</p></blockquote><h3 id="heading-compile-all-the-things">Compile all the things!</h3><p>It starts to become clear by now that if we want the DX benefits of CSS-in-JS and the UX benefits of static CSS files, then we might need to do things at compile/build time. Pretty much all of the downsides of CSS-in-JS we've seen so far can be traced back to doing things using JavaScript at runtime.</p><p>In the same article where Sam Magura discusses the runtime performance of CSS-in-JS, they also briefly <a target="_blank" href="https://dev.to/srmagura/why-were-breaking-up-wiht-css-in-js-4g9b#a-note-about-compiletime-cssinjs">attempt</a> to speculate on the downsides of compile-time CSS-in-JS libraries (without actually having tried any of those libraries, mind you). In response to that, Mark Dalgleish (co-creator of CSS modules and <a target="_blank" href="https://vanilla-extract.style/">Vanilla Extract</a>) <a target="_blank" href="https://twitter.com/markdalgleish/status/1582908496441331713">called out</a> the falseness of these claims.</p><p>While we're talking about Vanilla Extract, I should mention that I think it is solving a different problem than most CSS-in-JS libraries. Its main focus is to help build type-safe design systems, not websites or applications. Now, one could argue that many applications could benefit from <em>using</em> a design system (one that could be built on top of Vanilla Extract perhaps), and I would generally agree, but that is a different conversation.</p><p>Back to the topic at hand: remember, we want scoping, colocation, and performance. If the goal remains focused on those three points, then this becomes a much narrower, simpler problem to solve. Let me show you my attempt at solving it.</p><p>Since scoping happens at the class level (or more broadly, selector level), we'll use it as the singular point in our API. This has the added benefit of being framework-agnostic. And it's all we really need.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674373540977/def746bd-1263-4f69-9755-51cc53cbf481.png" alt="CSS-in-JS code where a variable named button is assigned a tagged template literal with the tag &quot;css&quot; containing a few css declarations. The button variable is then passed as the class name to a button element in JSX." class="image--center mx-auto" /></p><p>From a DX perspective, the API is quite intuitive too, especially for those coming from other CSS-in-JS libraries (the idea for the <code>css</code> function actually comes directly from <a target="_blank" href="https://emotion.sh/docs/introduction#framework-agnostic">emotion</a>). We also get to use real CSS syntax here, so the authored code feels instantly recognizable to everyone. There is no learning curve, and the styles are copy-pasteable across different projects and codepens.</p><p>So now we just need to replace the whole CSS string with a hashed class name and use it to populate a CSS file. Sounds simple enough. And honestly? It is! Building the original proof of concept only took about 50 lines of code (and a few drinks).</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674414671152/amEA8amwS.gif" alt="Animated gif showing that starting from a TSX file where CSS declarations are written inside a tagged template literal assigned to a variable named &quot;button&quot;. This template string then gets replaced with a hash. Separately, a CSS file is added where this hash is used as the class name for the declarations that were originally in the template string." class="image--center mx-auto" /></p><p>I want to emphasize again that this is all at build time, which enables us to do all kinds of shenanigans with the extracted CSS, without affecting runtime performance. That's how we're able to get <code>&amp;</code> nesting for free (using PostCSS). We can use this idea to go one step further and support Sass too, because <a target="_blank" href="https://blog.mayank.co/the-case-for-using-sass-in-2022">Sass is awesome</a>!</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674370343946/afc0d734-ce4f-4b86-a4a6-efae3e0df4ad.png" alt="Sass code written in a tagged template literal string inside a JSX file. The Sass code showcases the use of variables from &quot;open-props-scss&quot;, nesting, double-slash comments, and mixins." class="image--center mx-auto" /></p><p>Sweet! So we've basically taken verbatim what would have been in a .css or .scss file, and instead put it inside our .jsx/.tsx files with scoped class names that have all the same benefits of JavaScript variables - being importable, tree-shakeable, "Go to definition"-able, etc. It's like inline (S)CSS modules!</p><h3 id="heading-closing-thoughts">Closing thoughts</h3><p>Scoped + colocated + performant styling totally should have been an already-solved problem. There were definitely attempts made: <a target="_blank" href="https://linaria.dev/">Linaria</a> is a static CSS-in-JS library designed to address exactly this; except I have had trouble getting it to work with cascade layers or with Vite SSR (a la Astro). In fact, I have had <a target="_blank" href="https://github.com/withastro/astro/issues/4432">trouble</a> with most CSS-in-JS libraries in Astro.</p><p>I am notably more disappointed in React for not taking this problem seriously. To illustrate this, you need only to look at the competition. Most frameworks that use a non-JSX language for templating  Svelte, Vue, Astro, Angular, WebC  have a mechanism for scoping CSS (if you use one of these non-JSX frameworks, you were probably waiting for me to mention this). There is no reason React, and by extension JSX, can't support scoping when others have done so for ages. React, in its quest to be unprescriptive, has led to years of arguably suboptimal attempts by the community, to solve a problem that the framework is best equipped to solve. The same goes for other JSX frameworks like Preact and Solid, but there is no doubt that once React figures it out (and there are <a target="_blank" href="https://github.com/reactwg/react-18/discussions/108#:~:text=Why%20React%20Specific%20APIs%3F">signs</a> they might), others will soon follow.</p><p>My goal for ecsstatic is to make it the closest thing possible to CSS so that it supports all CSS features (including ones that don't exist yet) but more importantly, so that when the time comes, migrating off of it feels painless.</p><p>If you've made it all the way to the end, have a balloon: 🎈. And maybe check out <a target="_blank" href="https://ecsstatic.dev/">ecsstatic.dev</a>. Thanks for reading.</p>]]></description><link>https://blog.mayank.co/is-css-in-js-actually-bad</link><guid isPermaLink="true">https://blog.mayank.co/is-css-in-js-actually-bad</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Sun, 22 Jan 2023 16:31:57 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1674405025170/94e11790-94c5-4af8-bb66-a0a247a363d4.png</cover_image></item><item><title><![CDATA[Using text symbols in pseudo-elements accessibly]]></title><description><![CDATA[<p>Generated content (the <code>::before</code> and <code>::after</code> pseudo-elements) is quite handy for styling elements without disrupting markup. We can use it for creating things like custom checkboxes without any extra elements.</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/MrRoboto/pen/OJxaGyb">https://codepen.io/MrRoboto/pen/OJxaGyb</a></div><p> </p><p>Usually, we want to use an empty string as the value for the <code>content</code> property. This ensures that the pseudo-element is purely decorative and will not lead to unexpected announcements by assistive technologies.</p><pre><code class="lang-css"><span class="hljs-selector-pseudo">::before</span> {  <span class="hljs-attribute">content</span>: <span class="hljs-string">""</span>;}</code></pre><p>However, we might sometimes get the urge to use unicode characters in the content. A few common examples include:</p><ul><li><p><code>*</code> at the end of labels for required inputs</p></li><li><p><code>#</code> or <code>🔗</code> near clickable headings (fragment links)</p></li><li><p><code></code> to indicate expandable content</p></li><li><p><code></code> or <code></code> between breadcrumbs</p></li></ul><p>If these symbols are used with <code>content</code>, it could cause a confusing experience for users who rely on assistive technologies.</p><pre><code class="lang-css"><span class="hljs-selector-pseudo">::before</span> {  <span class="hljs-comment">/*  don't do this! */</span>  <span class="hljs-attribute">content</span>: <span class="hljs-string">"#"</span>;}</code></pre><p>In the future, we might be able to specify an empty alt text (Adrian Roselli has a <a target="_blank" href="https://adrianroselli.com/2020/10/alternative-text-for-css-generated-content.html">great article</a> on this topic), but support is lacking currently.</p><pre><code class="lang-css"><span class="hljs-selector-pseudo">::before</span> {  <span class="hljs-comment">/* someday */</span>  <span class="hljs-attribute">content</span>: <span class="hljs-string">"#"</span> / <span class="hljs-string">""</span>;}</code></pre><h2 id="heading-so-what-do-we-do">So what do we do?</h2><p>Use svgs! The <a target="_blank" href="https://codepen.io/noahblon/post/coloring-svgs-in-css-background-images">mask technique</a> that I demonstrated above in the custom checkbox demo is a great way to use svgs without leaving CSS.</p><pre><code class="lang-css"><span class="hljs-selector-pseudo">::before</span> {  <span class="hljs-attribute">--svg</span>: <span class="hljs-built_in">url</span>(...); <span class="hljs-comment">/* can be a remote url or a data url */</span>  <span class="hljs-attribute">content</span>: <span class="hljs-string">""</span>;  <span class="hljs-attribute">background-color</span>: currentColor;  <span class="hljs-attribute">-webkit-mask-image</span>: <span class="hljs-built_in">var</span>(--svg);  <span class="hljs-attribute">mask-image</span>: <span class="hljs-built_in">var</span>(--svg);}</code></pre><h2 id="heading-okay-but-text-is-nice">Okay but text is nice</h2><p>Fine! I get it. Good news: I may have just found a way to do this accessibly. Let me show you.</p><p>We will still use the mask technique, but instead of using a real svg icon, we will construct one on the fly using a data url. And we will put our text inside the svg using a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text"><code>&lt;text&gt;</code></a> element. For the purposes of this guide, let's use the <code>*</code> character to put at the end of a required label.</p><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>&gt;</span>  <span class="hljs-tag">&lt;<span class="hljs-name">text</span>&gt;</span>*<span class="hljs-tag">&lt;/<span class="hljs-name">text</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span></code></pre><p>Let's add some attributes for positioning and styling the text. You can adjust these to your liking.</p><ul><li><p>16x16 <code>viewBox</code> to correspond with default font size</p></li><li><p><code>font-family='system-ui'</code> so it adapts to the system font</p></li><li><p><code>font-size='1rem'</code> should be the default but you can adjust for your desired size</p></li><li><p><code>text-anchor='middle'</code> for horizontally centering the text</p></li><li><p><code>x='50%'</code> and <code>y='100%'</code>: I will admit I kept adjusting these randomly until the symbol was close to the center of the box 😅</p></li></ul><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 16 16"</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>&gt;</span>  <span class="hljs-tag">&lt;<span class="hljs-name">text</span> <span class="hljs-attr">font-family</span>=<span class="hljs-string">"system-ui"</span> <span class="hljs-attr">font-size</span>=<span class="hljs-string">"1rem"</span> <span class="hljs-attr">text-anchor</span>=<span class="hljs-string">"middle"</span> <span class="hljs-attr">x</span>=<span class="hljs-string">"50%"</span> <span class="hljs-attr">y</span>=<span class="hljs-string">"100%"</span>&gt;</span>*<span class="hljs-tag">&lt;/<span class="hljs-name">text</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span></code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673044759371/8ded204b-2e5d-4f20-8f25-9d45a8f95c0f.png" alt="A white asterisk symbol at the center of a box on a black background." class="image--center mx-auto" /></p><p>We can now put that in a data url and assign it to a custom property for ease of use. Note that some symbols might need to be <a target="_blank" href="https://gist.github.com/jennyknuth/222825e315d45a738ed9d6e04c7a88d0">replaced</a> (for example, <code>#</code> becomes <code>%23</code>).</p><pre><code class="lang-css"><span class="hljs-selector-tag">--svg</span>: <span class="hljs-selector-tag">url</span>('<span class="hljs-selector-tag">data</span><span class="hljs-selector-pseudo">:image</span>/<span class="hljs-selector-tag">svg</span>+<span class="hljs-selector-tag">xml</span>;<span class="hljs-selector-tag">utf8</span>,&lt;<span class="hljs-selector-tag">svg</span> <span class="hljs-selector-tag">viewBox</span>="0 0 16 16" <span class="hljs-selector-tag">xmlns</span>="<span class="hljs-selector-tag">http</span>://<span class="hljs-selector-tag">www</span><span class="hljs-selector-class">.w3</span><span class="hljs-selector-class">.org</span>/2000/<span class="hljs-selector-tag">svg</span>"&gt;&lt;<span class="hljs-selector-tag">text</span> <span class="hljs-selector-tag">font-family</span>="<span class="hljs-selector-tag">system-ui</span>" <span class="hljs-selector-tag">font-size</span>="1<span class="hljs-selector-tag">rem</span>" <span class="hljs-selector-tag">text-anchor</span>="<span class="hljs-selector-tag">middle</span>" <span class="hljs-selector-tag">x</span>="50%" <span class="hljs-selector-tag">y</span>="100%"&gt;*&lt;/<span class="hljs-selector-tag">text</span>&gt;&lt;/<span class="hljs-selector-tag">svg</span>&gt;');</code></pre><p>Sweet, now we can use it with the mask technique. We'll use the <code>::after</code> element because we want this to appear <em>after</em> the label text. We do need to give it a width and height explicitly (or use <code>position: absolute</code>). And let's also add <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors"><code>forced-colors</code></a> support.</p><pre><code class="lang-css"><span class="hljs-selector-tag">label</span><span class="hljs-selector-pseudo">::after</span> {  <span class="hljs-attribute">content</span>: <span class="hljs-string">""</span>;  <span class="hljs-attribute">-webkit-mask</span>: <span class="hljs-built_in">var</span>(--svg) no-repeat center;  <span class="hljs-attribute">mask</span>: <span class="hljs-built_in">var</span>(--svg) no-repeat center;  <span class="hljs-attribute">background-color</span>: red;  <span class="hljs-attribute">display</span>: inline-block;  <span class="hljs-attribute">width</span>: <span class="hljs-number">1em</span>;  <span class="hljs-attribute">height</span>: <span class="hljs-number">1em</span>;  @media (<span class="hljs-attribute">forced-colors</span>: active) {    background-color: CanvasText;  }}</code></pre><p>And we're done!</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/MrRoboto/pen/XWBNGYL">https://codepen.io/MrRoboto/pen/XWBNGYL</a></div><p> </p><p>Pretty neat, if you ask me. 😄</p>]]></description><link>https://blog.mayank.co/using-text-symbols-in-pseudo-elements-accessibly</link><guid isPermaLink="true">https://blog.mayank.co/using-text-symbols-in-pseudo-elements-accessibly</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Fri, 06 Jan 2023 23:30:03 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1673047755421/340d5249-d18c-472d-b3d1-08d932b80c1c.png</cover_image></item><item><title><![CDATA[Is <dialog> enough?]]></title><description><![CDATA[<p>The native <code>&lt;dialog&gt;</code> element is neat. It has the correct semantics, it appears in the top layer, it keeps track of focus, and it gives you mostly intuitive ways to open/close it. So if you want a modal dialog, you can just drop a wee <code>dialog.showModal()</code> on your page and call it a day, right? Not quite.</p><aside>Note: This article will only cover modal dialogs (<code>dialog.showModal()</code>). Some of these points could still apply to non-modal dialogs opened using <code>dialog.show()</code> but might need some tweaking.</aside><h3 id="heading-initial-focus">Initial focus</h3><p>By default, when the dialog is shown, it will focus the first focusable element inside it. If it's a modal dialog, it will trap focus so that the rest of the page is "inert". When closed, focus will return to the dialog trigger.</p><p>This default behavior works well when the very first thing inside the dialog is a close button.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672680622828/77827d8a-2f4d-4c02-9c91-074b9c7ba2f9.png" alt="screenshot a dialog highlighting the close button which is simply an X icon in the top right corner" class="image--center mx-auto" /></p><p>But when there is a bunch of text before the first focusable element (e.g. a link in a paragraph), it could result in a confusing experience for screen readers as focusing that element would skip all the prior content. It could also be confusing for sighted users if the prior content is so lengthy that it scrolls off screen.</p><p><strong>Solution</strong>: Manually set initial focus on what makes the most sense. This is achievable, without any JavaScript, by specifying <code>tabindex="-1"</code>. This makes the element focusable but not tabbable. For example, we could set <code>tabindex="-1"</code> on the first heading inside the dialog, and it would be automatically focused when the dialog opens.</p><p>If you want to learn more, Scott O'Hara has a <a target="_blank" href="https://www.scottohara.me/blog/2019/03/05/open-dialog.html">good article</a> that goes into more detail on this topic and links to github discussions.</p><h3 id="heading-prevent-page-scroll">Prevent page scroll</h3><p>By default, a modal dialog makes the rest of the page "inert". Ideally, this means the user can't interact with anything outside the page, but I found one scenario that it doesn't fully cover: The user can still scroll the page when the dialog is open.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672683544565/41cb8a58-886e-460f-b55b-04deab28f374.gif" alt="screen recording showing that the page behind the dialog is still scrollable after opening the dialog" class="image--center mx-auto" /></p><p>Thankfully, the solution is quite simple: we can use <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has"><code>:has</code></a> to check if the page has a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:modal"><code>:modal</code></a> dialog and that it is currently <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#attr-open"><code>open</code></a>. If it is, then we set <code>overflow: hidden</code> to disable scrolling.</p><pre><code class="lang-css"><span class="hljs-selector-tag">html</span><span class="hljs-selector-pseudo">:has(dialog</span><span class="hljs-selector-attr">[open]</span><span class="hljs-selector-pseudo">:modal)</span> {  <span class="hljs-attribute">overflow</span>: hidden;}</code></pre><p>This will probably cause layout shift, so let's fix that with <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-gutter"><code>scrollbar-gutter</code></a>.</p><pre><code class="lang-css"><span class="hljs-selector-tag">html</span> {  <span class="hljs-attribute">scrollbar-gutter</span>: stable both-edges;}</code></pre><p>While we're waiting for Firefox to ship <code>:has</code>, we could use JavaScript to set <code>overflow: hidden</code> on the page, or we could treat this as progressive enhancement and leave the default behavior unchanged.</p><p>Something I've also noticed is that when the root scroller is not the <code>&lt;html&gt;</code> but some child element inside <code>&lt;body&gt;</code>, then the page is no longer scrollable, even though the scrollbar is still visible. That makes it even easier for us to treat this as progressive enhancement.</p><pre><code class="lang-css"><span class="hljs-selector-class">.root-scroller</span> {  <span class="hljs-attribute">block-size</span>: <span class="hljs-number">100</span>dvb;  <span class="hljs-attribute">overflow</span>: auto;  <span class="hljs-attribute">scrollbar-gutter</span>: stable both-edges;  &amp;:has(dialog[open]:modal) {    <span class="hljs-attribute">overflow</span>: hidden;  }}</code></pre><p>Here's a codepen you can play around in:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/MrRoboto/pen/MWXdYZG">https://codepen.io/MrRoboto/pen/MWXdYZG</a></div><p> </p><h3 id="heading-light-dismiss">Light dismiss</h3><p>There are three ways to dismiss a dialog:</p><ul><li><p>Pressing the <code>Esc</code> key</p></li><li><p>Submitting a <code>&lt;form&gt;</code> with <code>method=dialog</code></p></li><li><p>Explicitly calling <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/close"><code>.close()</code></a> on the dialog using JavaScript</p></li></ul><p>But we usually also want to close all modal dialogs when clicking the "backdrop" area (this is called "light dismiss" sometimes).</p><p>This is a bit tricky. There is nothing like a "backdropclick" event, so we need to rely on regular <code>click</code> events and call <code>close()</code> in the correct place.</p><p>I've found two ways of doing this. Unfortunately, both of them require wrapping the dialog's contents in a separate element that takes up the entire space inside the dialog. This means there must be no padding on the dialog itself (we even need to undo its default padding). And we need to propagate any explicit sizes (e.g. <code>max-height</code>, <code>width</code>, etc) to the content wrapper.</p><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dialog</span>&gt;</span>  <span class="hljs-tag">&lt;<span class="hljs-name">dialog-contents</span>&gt;</span>    ...  <span class="hljs-tag">&lt;/<span class="hljs-name">dialog-contents</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">dialog</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">  <span class="hljs-selector-tag">dialog</span> {    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;  }</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></code></pre><p>After this, we can go one of two ways.</p><p>We can set up two separate click listeners: one on the <code>&lt;dialog&gt;</code> where we call <code>close</code>, and one on the content wrapper where we call <code>stopPropagation</code>.</p><pre><code class="lang-javascript"><span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'dialog'</span>);<span class="hljs-keyword">const</span> dialogContents = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'dialog-contents'</span>);dialog.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> dialog.close());dialogContents.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e.stopPropagation());</code></pre><p>Alternatively, we can add a single <code>click</code> handler to <code>&lt;dialog&gt;</code> and conditionally call close only if the event target is the dialog itself, and not any nested elements.</p><pre><code class="lang-javascript"><span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'dialog'</span>);dialog.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {  <span class="hljs-keyword">if</span> (e.target === e.currentTarget) { <span class="hljs-comment">// or e.target === dialog</span>    dialog.close();  }});</code></pre><p>Looks weird, but it makes sense when you consider that the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/::backdrop"><code>::backdrop</code></a> is actually part of the <code>&lt;dialog&gt;</code> element.</p><p>And here's a codepen you can play with:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/MrRoboto/pen/qByNKbv">https://codepen.io/MrRoboto/pen/qByNKbv</a></div><p> </p><p><strong>Update</strong>: <a target="_blank" href="https://github.com/jonathantneal">Jonathan Neal</a> has found a way to achieve this without a content wrapper (see <a target="_blank" href="https://codepen.io/jonneal/pen/bGjwddw">codepen</a>). This can be neat for basic use cases, but be careful when using it together with a popover component.</p><h3 id="heading-bonus-setting-display">Bonus: setting <code>display</code></h3><p>You may have encountered this already if you've worked with <code>&lt;dialog&gt;</code> before. When the dialog element has a <code>display</code> property (usually <code>grid</code> or <code>flex</code> for layout purposes), then it will no longer stay hidden when it's closed. That's because we are overriding this rule from the user-agent stylesheet:</p><pre><code class="lang-css"><span class="hljs-selector-tag">dialog</span><span class="hljs-selector-pseudo">:not(</span><span class="hljs-selector-attr">[open]</span>) {  <span class="hljs-attribute">display</span>: none;}</code></pre><p>Normally, fixing this would involve repeating that same rule in our own stylesheet, or setting display only for <code>dialog[open]</code> (thanks <a target="_blank" href="https://kilianvalkhof.com/">Kilian</a>). But since we are already using a content wrapper for light dismiss, this is a non-issue. We can set our <code>display</code> property on the wrapper.</p><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dialog</span>&gt;</span>  <span class="hljs-tag">&lt;<span class="hljs-name">dialog-contents</span>&gt;</span>    ...  <span class="hljs-tag">&lt;/<span class="hljs-name">dialog-contents</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">dialog</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">  <span class="hljs-selector-tag">dialog-contents</span> {    <span class="hljs-attribute">display</span>: grid;  }</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></code></pre><h3 id="heading-bonus-2-close-animation">Bonus 2: close animation</h3><p>Currently, it is straightforward to animate a dialog on entry. Not so much on exit, because of <code>display: none</code> that gets applied by the UA stylesheet. (It will be possible to animate <code>display: none</code> in the future though 👀).</p><p>Now, you could override the UA stylesheet and do all sorts of hacks with <code>visibility</code>/<code>pointer-events</code>, but you don't need to. There is a much simpler and robust solution.</p><p>Instead of only calling <code>dialog.close()</code>, firstly call <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/animate"><code>.animate()</code></a> with your desired keyframes and options. And then when it is <code>finished</code>, commit the styles and call <code>close()</code>. Job done.</p><pre><code class="lang-javascript">dialog .animate([{ <span class="hljs-attr">opacity</span>: <span class="hljs-number">0</span> }], { <span class="hljs-attr">duration</span>: <span class="hljs-number">200</span> }) .finished.then(<span class="hljs-function"><span class="hljs-params">animation</span> =&gt;</span> {   animation.commitStyles();   dialog.close(); });</code></pre>]]></description><link>https://blog.mayank.co/is-dialog-enough</link><guid isPermaLink="true">https://blog.mayank.co/is-dialog-enough</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Mon, 02 Jan 2023 19:34:44 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ce6c75457b91211bc5343ee81aa880a0.jpeg</cover_image></item><item><title><![CDATA[Astro just makes sense]]></title><description><![CDATA[<p>With Astro being days away from a stable 1.0 release, I thought I'd talk about why it's so exciting. I've been playing with Astro for a few months now, using it for every opportunity I get.</p><p>If you've never heard of Astro, it's a new metaframework built on top of Vite. I know! Some of you are probably rolling your eyes at the thought of yet another framework, but hear me out. It just makes sense!</p><h3 id="heading-minimal-api-surface">Minimal API surface</h3><p>Framework churn is a real thing. No one wants to learn a new tool just to see it go away in a year. But with Astro, there is barely anything to learn.</p><p>The markup syntax used in <code>.astro</code> files is literally just HTML with the ability to use JS expressions inside {curlies}. It's like JSX but without the weird quirks. You get to use regular <code>&lt;svg&gt;</code> attributes and <code>&lt;style&gt;</code> tags like normal HTML. No more <code>className</code> or <code>{' '}</code>.</p><p>Besides the markup, all the code that runs <em>before</em> the page generation goes in the "frontmatter" at the top of the file. Any variables declared here will be available in the markup. And because we have access to the full power of JavaScript, we can do all the standard stuff like <code>import</code> and <code>fetch</code> and <code>await</code>.</p><pre><code class="lang-html">---import { format, parseISO } from 'date-fns';const data = await fetch('/my-api').then(r =&gt; r.json());const { title, content, publishDate } = data;const publishDateParsed = typeof publishDate === 'string' &amp;&amp; format(parseISO(publishDate), 'MM/dd/yyyy');---<span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>{publishDateParsed &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">aside</span>&gt;</span>Published on: {publishDateParsed}<span class="hljs-tag">&lt;/<span class="hljs-name">aside</span>&gt;</span>}<span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></code></pre><p>It feels like modern-day PHP, and I mean that in the best way possible. 😄</p><p>There is so much going on in that little snippet:</p><ul><li>ESM imports</li><li>npm packages</li><li>web <code>fetch</code> API</li><li>top level <code>await</code></li><li>typescript!</li></ul><p>All of those pieces feel natural and <em>make sense</em> but if you try to assemble it all yourself, you'll realize why this is such a big deal (Node + ESM + TS is way harder than it should be!). What's more, even though Astro is handling all of that for you, the only code that is specific to Astro is the (ab)use of the frontmatter syntax (<code>---</code>).</p><p>In fact, other than the <code>.astro</code> file syntax, most of the Astro-specific things can be covered by three categories: an <a target="_blank" href="https://docs.astro.build/en/reference/api-reference/#astro-global"><code>Astro</code> global</a> object, a few <a target="_blank" href="https://docs.astro.build/en/reference/directives-reference/#client-directives">template directives</a> for hydration, and <a target="_blank" href="https://docs.astro.build/en/core-concepts/routing/">file-based routing</a>. You won't need to spend hours perusing the Astro docs (which are fantastic btw) because Astro tries to defer everything else to the platform (e.g. web APIs) or to other tools (e.g. Vite or React). There <em>is</em> an astro config file but the <a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/#automatic-integration-setup"><code>astro add</code></a> CLI takes care of most of the things in there.</p><h3 id="heading-bring-your-own-ui-frameworks">Bring your own UI framework(s)</h3><p>When you need more than what is possible inside a <code>.astro</code> template, Astro lets you use components from your framework of choice. This in particular is a breath of fresh air in a world where we see one of two trends: either the author of a UI framework builds their own metaframework to go with it, or the author of a metaframework decides to build it around React.</p><p>Astro inverts this idea and goes even further by letting you mix and match frameworks. Imagine using SolidJS as the default for its very <em>solid</em> primitives, utilizing Svelte for its animation utilities, and leveraging React for its ecosystem. All on the same page. What's more, you can even nest frameworks. 🤯</p><pre><code class="lang-jsx">&lt;div&gt;  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">MySolidComponent</span> <span class="hljs-attr">client:load</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">MyReactComponent</span> <span class="hljs-attr">client:load</span>&gt;</span>      <span class="hljs-tag">&lt;<span class="hljs-name">MySvelteComponent</span> <span class="hljs-attr">client:load</span> /&gt;</span>    <span class="hljs-tag">&lt;/<span class="hljs-name">MyReactComponent</span>&gt;</span>  <span class="hljs-tag">&lt;/<span class="hljs-name">MySolidComponent</span>&gt;</span></span>&lt;/div&gt;</code></pre><p>Again, this <em>just makes sense</em>. Why reinvent a UI framework when so many excellent options already exist and have different strengths?</p><h3 id="heading-client-side-js-is-opt-in">Client-side JS is opt-in</h3><p>Over the course of the last decade, web frameworks have gravitated to a JS-first approach. Astro goes the opposite way by requiring JS to be manually turned on, and it does so quite elegantly, allowing JS to be toggled at a component level rather than page-level.</p><p>Ever since Jason Miller's original post on <a target="_blank" href="https://jasonformat.com/islands-architecture/">Islands Architecture</a>, I've been curious about actually using it in practice. Previously your options were:</p><ul><li>ship all of the JavaScript (no islands) or none of it (all land)</li><li>ditch frameworks and use <code>&lt;script&gt;</code> tags</li><li>roll your own islands on top of something like 11ty</li></ul><p>But Astro is specifically designed around the idea of islands so you get to use components just like you normally would in your favorite framework, and then turn on JS with a template directive. I appreciate the <code>client:idle</code> directive in particular.</p><pre><code class="lang-jsx">&lt;Header /&gt; <span class="hljs-comment">// &lt;-- this will output static HTML</span><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">LoginButton</span> <span class="hljs-attr">client:load</span> /&gt;</span></span> <span class="hljs-comment">// &lt;-- this will load the JS immediately</span><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Carousel</span> <span class="hljs-attr">client:idle</span> /&gt;</span></span> <span class="hljs-comment">// &lt;-- this will load the JS via requestIdleCallback</span></code></pre><p>To me, this approach <em>just makes sense</em>. Not everything on the page needs to be hydrated with tons of JavaScript. Think of all the icons that live rent-free in your JavaScript bundle.</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/_developit/status/1382838799420514317">https://twitter.com/_developit/status/1382838799420514317</a></div><p><em>(I would be remiss not to mention all the work going on over at <a target="_blank" href="https://qwik.builder.io/">Qwik</a>. I'll be giving that a try very soon as well.)</em></p><h3 id="heading-conclusion">Conclusion</h3><p>That's all for today. Astro is refreshingly simple and I've been enjoying using it a lot. One quick note: you may have noticed that I refer to Astro as a "metaframework" rather than a "static site generator". This is on purpose because I think Astro does so much more than what you would get from something like Hugo. I plan to elaborate in another article so stay tuned. (edit: maybe some other day)</p>]]></description><link>https://blog.mayank.co/astro-just-makes-sense</link><guid isPermaLink="true">https://blog.mayank.co/astro-just-makes-sense</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Fri, 05 Aug 2022 20:53:44 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1659732774130/jQUOCuXyB.png</cover_image></item><item><title><![CDATA[The case for using Sass in 2022]]></title><description><![CDATA[<p>Is Sass still relevant? It seems like we need to have this conversation every few months. Most recently, it was Jeremy Keith who suggested in his <a target="_blank" href="https://youtu.be/CdZZcbZG83o?t=2109">CSS Day talk</a> that Sass has become obsolete.</p><blockquote><p>"A great tool becomes so successful it becomes obsolete. They are like R&amp;D for the web platform and become polyfills when they get implemented in browsers"</p><p> <a target="_blank" href="https://twitter.com/adactio">@adactio</a> #cssday  <a target="_blank" href="https://twitter.com/Una">@Una</a>, <a target="_blank" href="https://twitter.com/Una/status/1534810064225280001">June 9, 2022</a></p></blockquote><p>I <em>think</em> his intention there was to imply that Sass has helped the web evolve (which is very much true). But to call it a polyfill is absurd.</p><p>So let's take another look at Sass and some of the things it is still useful for in 2022.</p><h2 id="heading-sass-is-for-static-things">Sass is for static things</h2><p>We can't "run" CSS outside of a browser. This is by design, as the same line of CSS can mean vastly different things in different contexts. But this also means that we need to make the browser do all those things even if they can be done statically.</p><p>The <code>calc</code> function, combined with custom properties, is often used as an example of a feature that makes Sass obsolete.</p><p>Let's say we want to calculate a size derived from other lengths. This is a common requirement for aligning one element (e.g. a header logo) with another (e.g. a sidebar). We might write something like:</p><pre><code class="lang-scss"><span class="hljs-attribute">width</span>: calc(var(--icon-size) + var(--padding) * <span class="hljs-number">2</span> + var(--border-width) * <span class="hljs-number">2</span>);</code></pre><p>This is great if we are using fluid sizes ( la <a target="_blank" href="https://utopia.fyi/blog/painting-with-a-fluid-space-palette">Utopia</a>). <code>calc</code> is the perfect tool when our lengths are in a mixture of different units.</p><p>But what if we already know the sizes beforehand? Then we can probably do the calculation beforehand.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655515840410/qNoyVq-hg.png" alt="illustration showing icon size as 32px, padding as 16px and border width as 2px, for a total of 68px" /></p><p>I'm all for letting the browser do its job, but to do that, it has to actually download the code. And we should generally strive to send less code to the client if we can.</p><p>That's where Sass comes in. If we can statically access those sizes (e.g. either in pixels or in rems), then Sass can generate the final result for us.</p><pre><code class="lang-scss"><span class="hljs-comment">// outputs final value, e.g. width: 68px</span><span class="hljs-attribute">width</span>: <span class="hljs-variable">$icon-size</span> + <span class="hljs-variable">$padding</span> * <span class="hljs-number">2</span> + <span class="hljs-variable">$border-width</span> * <span class="hljs-number">2</span>;</code></pre><p>This was a very small example, but we could easily extrapolate this to our entire codebase. Imagine if we had a custom property for every little thing in every little corner of our website. 😬</p><p>The beauty of Sass is that it "compiles away", which means we can write the most expressive, readable, even verbose, code without worrying about sending any extra bytes to the browser.</p><h2 id="heading-sass-can-work-together-with-modern-css">Sass can work together with modern CSS</h2><p>CSS features are often more powerful than the Sass equivalents. A somewhat obvious example: CSS variables (custom properties) exist at runtime and can change dynamically, whereas Sass variables only exist at build time.</p><p>This static nature of Sass used to be considered a "limitation", but here's the thing: just because CSS is adding features that used to be exclusive to Sass doesn't mean we need to go all in one and stop using the other. Once we realize their differences, we can use them together to get the best of both worlds.</p><p>Let's say we have a set of design tokens. We can define them all as Sass variables at the top level, even if not all of them are consumed yet, because they will be stripped away during compilation.</p><pre><code class="lang-scss"><span class="hljs-variable">$border-radius-1</span>: <span class="hljs-number">2px</span>;<span class="hljs-variable">$border-radius-2</span>: <span class="hljs-number">4px</span>;<span class="hljs-comment">// ...</span><span class="hljs-variable">$background-1</span>: hsl(<span class="hljs-number">0</span> <span class="hljs-number">0%</span> <span class="hljs-number">0%</span>);<span class="hljs-variable">$background-2</span>: hsl(<span class="hljs-number">0</span> <span class="hljs-number">0%</span> <span class="hljs-number">10%</span>);<span class="hljs-comment">// ...</span></code></pre><p>Now if we're building a component that needs to look decent by default but also needs to be customizable, then we can bring in CSS variables.</p><pre><code class="lang-scss"><span class="hljs-attribute">border-radius</span>: var(--button-border-radius, #{<span class="hljs-variable">$border-radius-1</span>});<span class="hljs-attribute">background</span>: var(--button-background, #{<span class="hljs-variable">$background-2</span>});</code></pre><p>This will allow us (or our user) to do the customizations without having to change any of the actual properties. We might even expose a nice API for these custom properties using JavaScript (e.g. React props), drastically simplifying the usage.</p><pre><code class="lang-jsx">&lt;Button borderRadius={<span class="hljs-number">3</span>} background=<span class="hljs-string">'rebeccapurple'</span>&gt;Hi&lt;/Button&gt;</code></pre><h2 id="heading-sass-adds-a-lot-of-nice-features">Sass adds a lot of "nice" features</h2><p>Sass has nesting and double-slash comments. Those alone should be worth the cost of admission in my opinion.</p><p>Even though <a target="_blank" href="https://www.w3.org/TR/css-nesting-1/">nesting is coming to CSS</a>, the Sass version is here today and it has more features than the native CSS nesting will have.</p><p>For instance, we don't <em>ever</em> need the <code>&amp;</code> in Sass, making it feel more intuitive.</p><pre><code class="lang-scss"><span class="hljs-selector-tag">button</span> {  svg { <span class="hljs-comment">// instead of &amp; svg</span>    <span class="hljs-comment">// ...</span>  }}</code></pre><p>And we can nest at-rules inside selectors.</p><pre><code class="lang-scss"><span class="hljs-selector-tag">button</span> {  &amp;:is(:hover,:focus) {    @media (prefers-reduced-motion: no-preference) {      transition: transform <span class="hljs-number">0.2s</span>;      <span class="hljs-attribute">transform</span>: translateY(<span class="hljs-number">4px</span>);    }  }  <span class="hljs-keyword">@layer</span> overrides {    <span class="hljs-keyword">@media</span> (forced-colors: active) {      <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid;    }  }}</code></pre><p>And we can use nesting to generate incremental class names.</p><pre><code class="lang-scss"><span class="hljs-selector-class">.button</span> {  &amp;-active { <span class="hljs-comment">// results in .button-active</span>     <span class="hljs-comment">// ...</span>  }}</code></pre><p>Sass also has functions, including some handy built-in ones. The <a target="_blank" href="https://sass-lang.com/documentation/modules/color">color module</a> is especially useful.</p><pre><code class="lang-scss"><span class="hljs-variable">$accent</span>: hsl(<span class="hljs-number">250</span> <span class="hljs-number">50%</span> <span class="hljs-number">50%</span>);<span class="hljs-variable">$accent-overlay</span>: color.change(<span class="hljs-variable">$accent</span>, <span class="hljs-variable">$alpha</span>: <span class="hljs-number">0.1</span>);<span class="hljs-variable">$accent-darkened</span>: color.scale(<span class="hljs-variable">$accent</span>, <span class="hljs-variable">$lightness</span>: -<span class="hljs-number">30%</span>);</code></pre><p>We can't do any of that in CSS today.</p><h2 id="heading-sass-is-really-good-for-reusing-code">Sass is really good for reusing code</h2><p>With mixins, Sass can help us generate a lot of repetitive CSS without having to manually duplicate the code.</p><p>A very common example is theming, where we need to use the cascade to handle every theme using both a preference query (for OS) and as an override (for custom theme switcher). Imagine duplicating dozens of variables and forgetting to update one of them in the future.</p><p>Sass lets us handle this rather elegantly.</p><pre><code class="lang-scss"><span class="hljs-selector-pseudo">:root</span> {  <span class="hljs-keyword">@include</span> light-theme;  <span class="hljs-keyword">@media</span> (prefers-color-scheme: dark) {    <span class="hljs-keyword">@include</span> dark-theme;  }  &amp;<span class="hljs-selector-attr">[data-theme=light]</span> {    <span class="hljs-keyword">@include</span> light-theme;  }  &amp;<span class="hljs-selector-attr">[data-theme=dark]</span> {    <span class="hljs-keyword">@include</span> dark-theme;  }}<span class="hljs-keyword">@mixin</span> light-theme {  --<span class="hljs-attribute">background</span>: hsl(<span class="hljs-number">0</span> <span class="hljs-number">0%</span> <span class="hljs-number">90%</span>);  --text: hsl(<span class="hljs-number">0</span> <span class="hljs-number">0%</span> <span class="hljs-number">10%</span>);  <span class="hljs-comment">// ...</span>}<span class="hljs-keyword">@mixin</span> dark-theme {  --<span class="hljs-attribute">background</span>: hsl(<span class="hljs-number">0</span> <span class="hljs-number">0%</span> <span class="hljs-number">10%</span>);  --text: hsl(<span class="hljs-number">0</span> <span class="hljs-number">0%</span> <span class="hljs-number">90%</span>);  <span class="hljs-comment">// ...</span>}</code></pre><p>We can even put those theme mixins in separate files and import them granularly, something we can't do with pure CSS yet.</p><p>And of course Sass has loops and conditionals and interpolation, which let us generate "variants" extremely quickly.</p><pre><code class="lang-scss"><span class="hljs-keyword">@each</span> <span class="hljs-variable">$status</span> in positive, negative, warning {  <span class="hljs-selector-class">.component-</span>#{<span class="hljs-variable">$status</span>}    <span class="hljs-attribute">background</span>: var(--bg-#{<span class="hljs-variable">$status</span>});    <span class="hljs-attribute">color</span>: var(--fg-#{<span class="hljs-variable">$status</span>});    <span class="hljs-keyword">@if</span> (<span class="hljs-variable">$status</span> == negative) {      <span class="hljs-attribute">border</span>: <span class="hljs-number">2px</span> solid;    }  }}</code></pre><h2 id="heading-sass-is-super-easy-to-use">Sass is super easy to use</h2><p>Because of how prevalent Sass has been historically, it has amazing support in a large number of environments. In most build systems, using Sass is as simple as installing a package and maybe adding a line of config. <a target="_blank" href="https://vitejs.dev/">Vite</a>, which is one of my preferred build tools, doesn't even require any config.</p><p>This low friction makes it hard to say no to Sass, considering all the value it provides without much of a cost.</p><h2 id="heading-but-what-about-postcss">But what about PostCSS?</h2><p><a target="_blank" href="https://postcss.org/">PostCSS</a> is great for a lot of things, such as autoprefixing, discarding comments, minification, and even <a target="_blank" href="https://preset-env.cssdb.org/">writing tomorrow's CSS today</a>. We can even solve nesting with PostCSS. So why use Sass?</p><p>In my experience, PostCSS can suffice for smaller projects, but Sass really shines at scale where it almost serves like a bundler.</p><p>And despite some of the overlap, I think these tools serve different needs. They also happen to work great together, allowing us to take advantage of the best parts of both.</p><h3 id="heading-lightningcss">LightningCSS?</h3><p><a target="_blank" href="https://github.com/parcel-bundler/lightningcss">LightningCSS</a> looks like a really promising alternative to PostCSS. It requires some manual configuration (unlike PostCSS, which is usually baked into build tools) but it works pretty well. That being said, it's an alternative to PostCSS, not Sass. And of course, that means it works well as a postprocessing step after Sass.</p><h2 id="heading-conclusion">Conclusion</h2><p>Hopefully, this clears any doubts about the relevancy of Sass in 2022 (and beyond). CSS is making great progress at adding a lot of the missing features that originally made Sass so attractive, but I don't think we are at a point where vanilla CSS can replace Sass yet. At best, the role of Sass has changed to help make better use of modern CSS.</p>]]></description><link>https://blog.mayank.co/the-case-for-using-sass-in-2022</link><guid isPermaLink="true">https://blog.mayank.co/the-case-for-using-sass-in-2022</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Sat, 18 Jun 2022 17:37:10 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1655573948125/1BfQtKcof.png</cover_image></item><item><title><![CDATA[Better scrolling through modern CSS]]></title><description><![CDATA[<p>Scrollbars. A very mundane thing that every website and app has. Historically, it has been difficult to consistently customize scrollbar styling on the web. So it is understandable that frontend developers don't pay much attention to them. While it's a good thing that most of us (hopefully) aren't writing <a target="_blank" href="https://github.com/radix-ui/primitives/blob/7ddb053a24b4ffe3c4ca1690e8a2c0bda24847f8/packages/react/scroll-area/src/ScrollArea.tsx">thousands of lines of JavaScript to recreate scrollbars</a>, we should be paying at least a little more attention to them. Let's look at some of the things we can do in 2022 to improve the scrolling experience for our users.</p><h3 id="heading-the-basics">The basics</h3><p>When the content of an element is too big to fit in it, we can use <code>overflow: auto</code> to make it scrollable. There's <a target="_blank" href="https://web.dev/learn/css/overflow/">more to it</a>, but this is all you need to make the default scrollbars show up.</p><p>As for styling the scrollbars, you can use the <code>-webkit</code> prefixed scrollbar properties combined with the standard <code>scrollbar-width</code> and <code>scrollbar-color</code> properties. There have been countless posts on this topic, so I won't bother repeating those parts. I recommend <a target="_blank" href="https://ishadeed.com/article/custom-scrollbars-css/">Ahmad Shadeed's scrollbar guide</a> if you're not already familiar with these properties.</p><p>I do want to mention the <a target="_blank" href="https://github.com/pascalduez/postcss-scrollbar"><code>postcss-scrollbar</code></a> plugin that generates the <code>-webkit</code> prefixed scrollbar properties from the standard ones. Which means you can get cross-browser styles with just this:</p><pre><code class="lang-scss"><span class="hljs-selector-class">.scroll-container</span> {  <span class="hljs-attribute">overflow</span>: auto;  scrollbar-<span class="hljs-attribute">width</span>: thin;  scrollbar-<span class="hljs-attribute">color</span>: hsl(<span class="hljs-number">0</span> <span class="hljs-number">0%</span> <span class="hljs-number">50%</span>);  <span class="hljs-comment">/* postcss-scrollbar will add the -webkit version automatically! */</span>}</code></pre><p>Looks simple enough. Now let's get to the more interesting bits.</p><h3 id="heading-theming-scrollbars">Theming scrollbars</h3><p>If you've decided you still want to rely on the default scrollbars, fair enough. But you need to be careful here if your site supports a dark theme. I see way too many sites implement a dark mode with their scrollbars still stuck in light mode. Here's what the <a target="_blank" href="https://remix.run/docs/en/v1/api/conventions#conventions">remix.run docs</a> look like on Windows in dark mode:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654817315675/SlCATLooX.png" alt="screenshot of remix docs showing a minimal dark theme with ugly white scrollbars" class="image--center mx-auto" /></p><p>This can be fixed using the <code>color-scheme</code> property or meta tag. You should probably be doing this anyway, for non-scrollbar reasons too. Thomas Steiner has a great <a target="_blank" href="https://web.dev/color-scheme/">article on web.dev</a> if you want to learn more. </p><pre><code class="lang-scss"><span class="hljs-selector-tag">html</span> {  <span class="hljs-comment">/* defer to OS preference */</span>  <span class="hljs-attribute">color</span>-scheme: dark light;  <span class="hljs-comment">/* override, assuming the theme toggler sets a data-theme attribute */</span>  &amp;<span class="hljs-selector-attr">[data-theme=light]</span> { <span class="hljs-attribute">color</span>-scheme: light; }  &amp;<span class="hljs-selector-attr">[data-theme=dark]</span> { <span class="hljs-attribute">color</span>-scheme: dark; }}</code></pre><h3 id="heading-the-scrollbar-quirks-of-various-operating-systems">The scrollbar quirks of various operating systems</h3><p>In this section, I will rant about different environments, the expectations, the behaviors, and the problems that come with them, as well as some future speculation. If all you care about is the code, feel free to scroll to the end of this section. 😅</p><h4 id="heading-windows">Windows</h4><p>As I demonstrated in the previous section, Windows has always had some of the ugliest-looking scrollbars of any platform. Windows 10 specifically has <em>the</em> ugliest scrollbars in all browsers. Even if you set the right color scheme, they look too boxy and thick.</p><p>Windows 11 is attempting to modernize the scrollbars by making them thinner and overlay. This can be noticed in the various first-party interfaces (e.g. the Settings app) across the OS. As for the browsers, Firefox 100+ is already shipping it, while Edge/Chrome have it in some form behind a flag.</p><p>The Firefox implementation in particular worries me because the scrollbar is razor thin and disappears unless you're actively interacting with it. To make things worse, there seems to be no way to change this behavior programmatically. Firefox does respect the "Always show scrollbars" setting in Windows 11 but it's off by default and we can't reasonably expect most users to turn it on. I really hope this will be addressed in a future update. For now we can't do anything.</p><p>On Chromium, things are simpler: if you specify even one of those <code>-webkit-scrollbar</code> prefixed properties, you become responsible for providing the entire scrollbar styling. This is exactly what we want, at least until the Windows 11 scrollbars ship in Chromium.</p><h4 id="heading-macos">macOS</h4><p>While macOS scrollbars look less offensive, they come with their fair share of problems.</p><p>The first one is a bit of nitpick but I find it super frustrating that I can't just move my cursor to the right edge of a browser window and use it to scroll. This is because macOS reserves the edges for resizing/moving the window, even if the cursor is still hovering on the scrollbar thumb. This effectively makes the already-thin scrollbar even thinner (albeit only for the viewport scroller).</p><p>The second is a more relatable problem: automatically disappearing scrollbars. They can offer a nice and clean interface... <em>if</em> the user knows that a part of the page is scrollable. In practice, they are very frustrating to deal with. Recently I had an experience where I literally couldn't find the ticket to a conference because it was hidden outside the scrollport and the scrollbar was invisible. In other words, <em>disappearing scrollbars can literally cost you money</em>. To work around this, you could maybe force the height of the scrollport to be such that the last item is half visible. Or you could use some JavaScript to detect scroll position and add a <a target="_blank" href="https://css-tricks.com/scroll-shadows-with-javascript/">scroll shadow</a>.</p><p>Both of these issues can be avoided by specifying custom scrollbar styling, so we should be good here.</p><h4 id="heading-android-ios-and-touchscreens">Android, iOS, and touchscreens</h4><p>Touchscreen devices are interesting. They have the same thin, overlaying, disappearing scrollbars that I framed as a problem in macOS, but the usage is quite different: the user doesn't interact directly with the scrollbar and instead drags their thumb against the scrollport. Also, because the screen is usually smaller, content overflows more often and the user is expected to scroll a lot more to find content. This is also why there are special features designed specifically for this modality, such as (1) momentum-based (inertia) scrolling, and a (2) visual highlight or spring animation when reaching the edge of a scrolling container. Android is even more thoughtful about this and decides to <em>always</em> show the scrollbar when a scrollable element first appears, and then switch to the regular auto-disappearing behavior after the user has scrolled at least once (signalling that they are now aware the area is scrollable).</p><p>All that to say, I quite like the default behavior of these scrollbars and would like to preserve it. This is achievable using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer"><code>pointer</code> media query</a>, which will let us conditionally style scrollbars based on whether the user is on a high-accuracy pointing device (e.g. mouse) or a low-accuracy one (e.g. touchscreen).</p><pre><code class="lang-css"><span class="hljs-keyword">@media</span> (<span class="hljs-attribute">pointer:</span> fine) {  <span class="hljs-selector-class">.scroll-container</span> {    <span class="hljs-comment">/* ...custom scrollbar styles only for desktop */</span>  }}</code></pre><p>I've used this approach on multiple projects and feel pretty good about the results.</p><h3 id="heading-preventing-layout-shift">Preventing layout shift</h3><p>One problem with <code>overflow: auto</code> is that it only shows the scrollbar if the content is actually overflowing, meaning it can cause a layout shift if a scrollbar suddenly appears. Historically, this has been possible to fix using <code>overflow: overlay</code> (in Chrome) to avoid having the scrollbar ever occupy space, and/or using <code>overflow: scroll</code> to always have it occupy space.</p><p>These days we can explicitly tell the browser to reserve space for scrollbars using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-gutter"><code>scrollbar-gutter: stable</code></a>. It's supported in Chrome and Firefox, and we can fallback to <code>overflow: scroll</code> for Safari. If you use an invisible scrollbar track, this fallback should work identically to <code>scrollbar-gutter</code>.</p><pre><code class="lang-scss"><span class="hljs-selector-class">.scroll-container</span> {  <span class="hljs-attribute">overflow</span>: scroll;  <span class="hljs-keyword">@supports</span> (scrollbar-gutter: stable) {    <span class="hljs-attribute">overflow</span>: auto;    scrollbar-gutter: stable;  }}</code></pre><p>Worth noting here is that if you want <code>scrollbar-gutter</code> on the viewport, it's tricky to make it work on the <code>&lt;body&gt;</code> and might be easier to move it either to the <code>:root</code> or to a child.</p><h3 id="heading-more-scrollbar-goodies-from-modern-css">More scrollbar goodies from modern CSS</h3><p>While not directly related to scrollbar styling, there are a few more properties I want to briefly mention that can help improve the scrolling experience. The wealth of tools CSS provides us today never ceases to amaze me.</p><h4 id="heading-scroll-padding">Scroll padding</h4><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-margin"><code>scroll-padding</code></a> allows creating an offset at the edge of the scrollport. This is incredibly useful when you have a sticky header that would otherwise cover up headings scrolled to via in-page jump links or fragment urls.</p><pre><code class="lang-css"><span class="hljs-selector-class">.scroll-container</span> {  <span class="hljs-attribute">scroll-padding-top</span>: <span class="hljs-built_in">var</span>(--header-height);}</code></pre><p>There's also <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-margin"><code>scroll-margin</code></a> which behaves similarly, except it is applied on the individual elements rather than the scroll container.</p><h4 id="heading-scroll-behavior">Scroll behavior</h4><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior"><code>scroll-behavior</code></a> lets you enable smooth scrolling, which is also super neat for in-page jump links. Don't forget to guard it in a reduced motion check for better accessibility!</p><pre><code class="lang-css"><span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-reduced-motion:</span> no-preference) {  <span class="hljs-selector-class">.scroll-container</span> {    <span class="hljs-attribute">scroll-behavior</span>: smooth;  }}</code></pre><h4 id="heading-overscroll-behavior"><strong>Over</strong>scroll behavior</h4><p>Named similarly as the previous one, <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior"><code>overscroll-behavior</code></a> does something quite different: it lets you prevent scroll-chaining in nested scrolling areas. This is especially handy in common nested regions like dialogs and sidebars, where you don't want the underlying page to start scrolling when you reach the end of the current scrolling container.</p><pre><code class="lang-css"><span class="hljs-selector-class">.scroll-container</span> {  <span class="hljs-attribute">overscroll-behavior</span>: contain;}</code></pre><p>From a UX perspective, I think <code>contain</code> should be the default for all scrollable regions, and <code>auto</code> should be used in very few places.</p><h4 id="heading-scroll-snapping">Scroll snapping</h4><p>Introduced a while ago, <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap/Basic_concepts">CSS scroll snapping</a> has gone through numerous improvements and patches. Today it is a fairly reliable way of building scrollable components without a lot of JavaScript.</p><p>This is what it looks like in its most basic form (not that you would ever build a carousel... right?):</p><pre><code class="lang-css"><span class="hljs-selector-class">.scroll-container</span> {  <span class="hljs-attribute">scroll-snap-type</span>: x mandatory;  &amp; &gt; * {    <span class="hljs-attribute">scroll-snap-align</span>: start;  }}</code></pre><p>There are a <em>lot</em> more interesting things you can do with scroll snapping, especially when combined with some of the other properties mentioned above. Did you know you can use it to build a <a target="_blank" href="https://web.dev/building-a-stories-component/">stories component</a> or a <a target="_blank" href="https://ariakit.org/examples/menu-slide">sliding menu</a>?</p><p>I also highly recommend watching Adam Argyle's excellent talk titled <a target="_blank" href="https://www.youtube.com/watch?v=34zcWFLCDIc">"Oh Snap!"</a> to see even more cool things that you can do with scroll snapping.</p><h3 id="heading-conclusion">Conclusion</h3><p>I could rant about scrollbars all day, but that wouldn't be very respectful of your time. So if you've managed to come this far, I just want to say thanks for reading and I hope this inspires you to go forth and build better scrolling experiences. If you have any thoughts/questions, feel free to reach out! 👋</p>]]></description><link>https://blog.mayank.co/better-scrolling-through-modern-css</link><guid isPermaLink="true">https://blog.mayank.co/better-scrolling-through-modern-css</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Sat, 11 Jun 2022 01:14:35 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1654960148833/e5lLNMa9B.png</cover_image></item><item><title><![CDATA[CSS needs an Accent system color]]></title><description><![CDATA[<p>There's a growing movement among designers and developers to create <em>user-adaptive interfaces</em>. If you haven't heard that term before, it's just a fancy way of saying that the interface is personalized to the user and that it will try to <a target="_blank" href="https://www.matuzo.at/blog/writing-even-more-css-with-accessibility-in-mind-user-preferences/">respect the preferences</a> that the user has indicated in their operating system and/or browser.</p><p>A small but familiar example of this is using media queries to change the layout based on screen width. A more modern example is using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme"><code>prefers-color-scheme</code></a> query to set light or dark theme.</p><p>But those are just bits and pieces. For full-blown user-adaptive design, you can look at <a target="_blank" href="https://material.io/blog/announcing-material-you">Material You</a>.</p><blockquote><p>"If done correctly, the user may never know that the interface has even adapted" - <a target="_blank" href="https://twitter.com/argyleink">Adam Argyle</a> on <a target="_blank" href="https://www.youtube.com/watch?v=865olcAfwFg">building user-adaptive interfaces</a>.</p></blockquote><p>Most interfaces that I build these days tend to be adaptive (to the best of my ability). Not only does this result in a better experience for the user, but it's also less mental overhead for me. Instead of picking a font, I can just use <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-family#system-ui"><code>system-ui</code></a>. Instead of finding the perfect button height that works for mobile and desktop, I can just adjust spacing based on the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer"><code>pointer</code></a> media query.</p><p>There's also <a target="_blank" href="https://www.w3.org/TR/css-color-4/#system-color-values">CSS system colors</a> (e.g. <code>Canvas</code>, <code>CanvasText</code>) that can be used to theme parts of the UI without hardcoding a shade of black or white. <a target="_blank" href="https://blog.jim-nielsen.com/2021/css-system-colors/">Jim Nielson wrote</a> about this last year.</p><p>But there is something missing.</p><p>Every modern interface needs some kind of accent color. Without one, your website would look very dull. Unless that <em>is</em> your brand and you know you can pull it off (looking at you, <a target="_blank" href="https://heydonworks.com/">Heydon</a> 👀).</p><p>CSS does have an <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/accent-color"><code>accent-color</code></a> property for changing the color of some native form controls, but you, the designer/developer, are still responsible for picking the actual color (on most platforms anyway). To me, this usually feels like an unnecessary decision that I would rather leave to the user. Many of my UIs fall under <a target="_blank" href="http://dunneandraby.co.uk/content/bydandr/13/0#:~:text=affirmative%20design%3A%20design%20that%20reinforces%20the%20status%20quo.">affirmative design</a>, and I don't need a strong brand identity. I just want the user to feel at home.</p><p>All modern operating systems (except notably iOS) let you pick an accent color, which is then used throughout the OS in different places. macOS even uses it as the default accent-color of native browser controls and text selection. But that only goes so far, as there's no way to use that value to build a custom CSS component.</p><p>Which is why we need... 🥁 <code>Accent</code>. Or <code>AccentText</code> or <code>AccentColor</code>. The name doesn't matter. What's important is that this value is set by the user.</p><p>Combined with <a target="_blank" href="https://www.w3.org/TR/css-color-5/#relative-colors">relative color syntax</a>, this would open up so many possibilities. Imagine extracting a whole palette of colors from the user's preferred hue, and delivering a super polished interface using that. </p><pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {  <span class="hljs-attribute">--accent</span>: Accent; <span class="hljs-comment">/* &lt;-- I want this */</span>  <span class="hljs-attribute">--accent-1</span>: <span class="hljs-built_in">hsl</span>(from Accent h s <span class="hljs-number">20%</span>);  <span class="hljs-attribute">--accent-2</span>: <span class="hljs-built_in">hsl</span>(from Accent h s <span class="hljs-number">40%</span>);  <span class="hljs-attribute">--accent-3</span>: <span class="hljs-built_in">hsl</span>(from Accent h s <span class="hljs-number">60%</span>);  <span class="hljs-attribute">--accent-4</span>: <span class="hljs-built_in">hsl</span>(from Accent h s <span class="hljs-number">80%</span>);}</code></pre><p>All that to say, I just want the sites I use to show me a <em>hint</em> of purple 🥺</p><hr /><p><strong>Update (6/22/2022):</strong> <a target="_blank" href="https://twitter.com/bramus">Bramus</a> has informed me that <a target="_blank" href="https://github.com/w3c/csswg-drafts/commit/8c1fe16402cee71211530ef5283a0372c8740e7d"><code>AccentColor</code> and <code>AccentColorText</code> have been added to the spec</a>! This is amazing news 🥳</p>]]></description><link>https://blog.mayank.co/css-needs-accent-system-color</link><guid isPermaLink="true">https://blog.mayank.co/css-needs-accent-system-color</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Sat, 09 Apr 2022 00:22:54 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1649463704202/D9UX0ul-v.png</cover_image></item><item><title><![CDATA[How I built an accessible version of Wordle]]></title><description><![CDATA[<p>Last month I just had this desire to build my own Wordle clone, since all the <a target="_blank" href="https://www.youtube.com/watch?v=K77xThbu66A">cool kids were doing it</a>. But I wanted to put my own spin on it, so I decided to make it a simpler, prettier and more accessible variant. The result? See for yourself: <a target="_blank" href="https://wordel.app"><strong>wordel.app</strong></a>. If you like it, you can even replace Wordle with it in your daily routune because the word list and answers are synced between them!</p><p>In this post, I will be going over some of the interesting decisions I made when building wordel. Are you excited? Because I am.</p><p>But first...</p><h3 id="heading-the-problem-with-wordle">The problem with Wordle</h3><p><a target="_blank" href="https://www.nytimes.com/games/wordle/index.html">Wordle</a> is delightful at its best, but it is famously inaccessible in pretty much every way. Even the built-in "high contrast mode" has extremely poor color contrast 😬</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647562739896/Kk-omJ3cG.png" alt="1.92 score on the letter background in high contrast mode. Yikes!" /></p><p>The worst part is that the inaccessibility of Wordle extends beyond the player. I'm talking, of course, about the emoji grid that gets dumped on social media. It is painful for screen reader users to go through those tweets full of emojis.</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/ScopicEngineer/status/1478438573913391104">https://twitter.com/ScopicEngineer/status/1478438573913391104</a></div><p><a target="_blank" href="https://slate.com/culture/2022/02/wordle-word-game-results-accessibility-twitter.html">Anna E Cook wrote a wonderful piece</a> going over this in more detail, which I highly recommend reading if you like sharing your results on social media.</p><p>And those are just two of the most obvious problems with Wordle. There's a lot more, but I'd rather talk about what <em>I</em> did than complain about The New York Times not using a fraction of their millions of dollars to make this game more inclusive.</p><h3 id="heading-enter-wordel">Enter wordel</h3><p>Let's get the name out of the way first. It's not a typo. I just like writing <em>wordel</em>. All lower case. Pronounced the same as Wordle (even by screen readers). Don't ask me why.</p><p>Now for the fun parts.</p><h4 id="heading-accessible-right-from-the-beginning">Accessible right from the beginning</h4><p>When building something from scratch, you have the opportunity to consider accessibility at every step of the way. And that's exactly what I did!</p><p><em>Note: I spent a fair bit of time testing all these things through screen readers, keyboard navigation, emulations, etc. While it all worked fine in my testing, I may have still missed something. If you find any issues, I'd love to hear from you so I can fix them</em></p><h4 id="heading-colors">Colors</h4><p>Choosing accessible colors is a relatively easy task. I tried to meet a contrast score of 7:1 (AAA) in most places. The <a target="_blank" href="https://webaim.org/resources/contrastchecker/">WebAim Contrast Checker</a> is a great website for comparing colors and for learning about color contrast in general.</p><p>Additionally, it is important to make sure the app is accessible to folks with color vision deficiencies. So I added small checkmarks next to the correct (green) letters.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647647380648/Kt7Z2BjKI.png" alt="screenshot of letter grid showing checkmarks on correctly guessed letters" /></p><p>Lastly, I should also mention <a target="_blank" href="https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/">Windows forced high contrast mode</a>. The linked article does a great job explaining how to support this mode, but it basically just involves making sure borders are present around elements, and using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/forced-color-adjust"><code>forced-color-adjust: none</code></a> in some places (e.g. the letter backgrounds).</p><h4 id="heading-semantic-html">Semantic HTML</h4><p>Building from scratch, without any component libraries, gives you complete control over your markup. That means I was able to use semantically correct HTML elements in every component, instead of creating a <a target="_blank" href="https://css-tricks.com/twitters-div-soup-and-uglyfied-css-explained/">div soup</a>. Here are a few examples:</p><ul><li>The page layout is split into <code>&lt;header&gt;</code> and <code>&lt;main&gt;</code>.</li><li>The word guess uses a real <code>&lt;input&gt;</code> element that can be typed into using the device keyboard.</li><li>For the guess distribution, I use the <code>&lt;dl&gt;</code>, <code>&lt;dt&gt;</code> and <code>&lt;dd&gt;</code> element, which creates <a target="_blank" href="https://benmyers.dev/blog/on-the-dl/">semantic name-value pairs</a>.</li><li>I make use of the <code>&lt;output&gt;</code> element in a few places (e.g. toast notifications), which automatically announces updates to screen readers.</li><li>I use <code>&lt;button&gt;</code> for a button and <code>&lt;a&gt;</code> for a hyperlink (<a target="_blank" href="https://www.htmhell.dev/26-tasty-buttons/">revolutionary</a>, I know), even if they look exactly the same.</li><li>I use the native <code>&lt;dialog&gt;</code> element which has automatic focus management, speaking of which...</li></ul><h4 id="heading-focus-management">Focus management</h4><p>It is important to make sure focus behaves properly in your app for it to be accessible to keyboard users (which often includes sighted users too). This usually just means having a sensible tab order with <a target="_blank" href="https://mayxnk.hashnode.dev/the-full-power-of-focus-visible">visible focus styles</a>, but sometimes involves manually moving focus around.</p><p>Using semantic elements, like I did, can get you pretty damn far. So it's really impressive (and not in a good way) that the original Wordle has literally no tabbable elements!</p><p>Anyway, there are two places I had to manage focus:</p><ul><li>I mentioned I'm using a real <code>&lt;input&gt;</code> element for the guess, but it is <a target="_blank" href="https://kittygiraudel.com/2021/02/17/hiding-content-responsibly/">visually hidden</a> (and replaced with the letter grid which shows the typed value from the input in big bold letters). Normally you shouldn't visually hide focusable elements, but I think it's fine here because this is the only focusable element on the main page  well, there <em>are</em> multiple of these <code>&lt;input&gt;</code> elements, but only the current guess is active  and the page is not scrollable. So the only thing left is to make sure this input gets focused, which I'm doing manually by calling <code>.focus()</code>.</li><li>I also mentioned that the native <code>&lt;dialog&gt;</code> has automatic focus management, i.e. it automatically moves and traps focus inside the dialog. But my dialog has two screens, so I need to manually move focus when the screen changes.</li></ul><h4 id="heading-emoji-grid">Emoji grid</h4><p>What to do with the infamous emojis-as-results? Inaccessible to screen reader users, but sighted users love them. Well, we can get the best of both worlds.</p><p>I'm showing the emoji grid but disabling text selection on it, and not providing any easy way to copy the emoji text. Instead, I have a button which copies the "alt text" that describes the results of the game. The expectation is that the player will screenshot this emoji grid and share it on social media (or IMs) with the alt text that's already in their clipboard.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647650002413/fl5G_sR7t.png" alt="the result dialog showing emojis mimicking the final state of the game. At the top, there's a note saying &quot;For accessibility reasons, instead of sharing the emojis as plain text, you should take a screenshot of this grid and share it with the provided alt text.&quot; At the bottom there is a button which copies alt text describing the results in plain English" /></p><h3 id="heading-tech-stack">Tech stack</h3><p>Let's talk tech! I wanted this to be lightweight, fast and have a good development experience, which is why I chose the following technologies:</p><ul><li><p><strong>Preact</strong>: I keep hearing how awesome Preact is, so I went with it. I'm super comfortable with React, but it would have been overkill for this little project, so Preact was a great fit. Why not something like Svelte? Well, I didn't want to learn a new framework. 🤷</p></li><li><p><strong>TypeScript</strong>: Many influential people will tell you to stay away from TypeScript for small, fun projects. But the thing is, I really enjoy using TypeScript (usually). It leads to fewer bugs, it offers better developer experience, but above all, it makes me feel... safe. 🥺</p></li><li><p><strong>PostCSS</strong>: These days you can get very far with just vanilla CSS. There is no need for styled-components in a small project like this. I didn't use any component libraries because I didn't want new dependencies, and I was actively avoiding Tailwind because I enjoy writing real CSS syntax. So the only thing left is PostCSS, for nice things like autoprefixer. And with <a target="_blank" href="https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env"><code>postcss-preset-env</code></a>, I even get to use nesting (without Sass!) 😮</p></li><li><p><strong>Vite</strong>: This one's a no-brainer. Vite has an official template for Preact + TypeScript, and it provides a super fast development server with esbuild. Plus, I was able to easily add <code>vite-plugin-pwa</code> to generate a service worker for free! 🥳</p></li><li><p><strong>eslint</strong> + <strong>prettier</strong>: Both of these are are worth mentioning, even though they are part of literally every project I work on. It took me longer than it should have to get eslint working because TS+preact+prettier requires a very specific combination of rules in the eslintconfig. But it's worth it, even if only for two things: enforcing <a target="_blank" href="https://reactjs.org/docs/hooks-rules.html"><em>Rules of Hooks</em></a> and auto formatting on save. 👍</p></li></ul><h3 id="heading-performance">Performance</h3><p>Since the app and the components are so small, it's hard to have any performance issues.</p><p>Worth mentioning though, is the bundle size which is around 60KB, including the huge list of words and all assets. This is all thanks to using Preact and avoiding any other dependencies.</p><p>Just for fun, here's what the network tab looks like on a fresh load:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647569584121/VzhrXcYfw.png" alt="Chrome network tab showing only 5 resources, totaling 60KB and all loading under 100ms" /></p><p>And here it is on subsequent loads, with all the service worker magic:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647651436195/SNUnM8YAJ.png" alt="Chrome network tab showing all resources loading from the service worker cache, within 10ms" /></p><h3 id="heading-polyfill">Polyfill</h3><p>I didn't go over any code snippets anywhere, but I should show you how I'm conditionally loading the polyfill for the <code>&lt;dialog&gt;</code> element only in older browsers.</p><pre><code class="lang-js">(<span class="hljs-keyword">async</span> () =&gt; {  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> HTMLDialogElement !== <span class="hljs-string">'function'</span>) {    <span class="hljs-keyword">const</span> css = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'link'</span>);    css.href = <span class="hljs-string">'https://unpkg.com/dialog-polyfill@0.5.6/dist/dialog-polyfill.css'</span>;    <span class="hljs-built_in">document</span>.head.appendChild(css).rel = <span class="hljs-string">'stylesheet'</span>;    <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'https://unpkg.com/dialog-polyfill@0.5.6/dist/dialog-polyfill.js'</span>);    dialogPolyfill.registerDialog(<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'dialog'</span>));  }})();</code></pre><p>That's a dynamic import from a CDN. <a target="_blank" href="https://caniuse.com/es6-module-dynamic-import">Browser support for dynamic imports</a> is surprisingly good, so I can just throw that in a <code>&lt;script&gt;</code> tag in my <code>index.html</code>. Pretty neat.</p><h3 id="heading-wrapping-up">Wrapping up</h3><p>And there you have it. An accessible, and dare I say <em>beautiful</em>, version of the word game we all know and love. Because there is no reason a word game shouldn't be inclusive.</p><p>You can play wordel at https://wordel.app and find the source code at https://github.com/mayank99/wordel.</p><p>Until next time 👋</p>]]></description><link>https://blog.mayank.co/how-i-built-an-accessible-version-of-wordle</link><guid isPermaLink="true">https://blog.mayank.co/how-i-built-an-accessible-version-of-wordle</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Sat, 19 Mar 2022 01:22:31 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1647652863899/gIsiJkIy7.png</cover_image></item><item><title><![CDATA[Customizing the new MDN redesign]]></title><description><![CDATA[<p>The <a target="_blank" href="https://hacks.mozilla.org/2022/03/a-new-year-a-new-mdn/">redesign for MDN web docs</a> went live yesterday. But what's a redesign if not polarizing? Just look at the comments on their <a target="_blank" href="https://twitter.com/MozDevNet/status/1498660405610266630">twitter</a> and <a target="_blank" href="https://github.com/mdn/yari/issues/5364">github</a>.</p><h2 id="heading-problems">Problems</h2><p>Personally, I mostly like it, even if only for the inclusion of dark mode. From my initial experience, I've found only two things that I would change:</p><ul><li>Currently, the dark mode uses <code>hsl(250deg 13% 9%)</code> as the background color, which creates a bit too much contrast.</li><li>The new browser compatibility section doesn't present enough information at first glance.</li></ul><p>And I'm not the only one who finds both of those things problematic. There have already been github issues created: <a target="_blank" href="https://github.com/mdn/yari/issues/5378">mdn/yari#5378</a> and <a target="_blank" href="https://github.com/mdn/yari/issues/5366">mdn/yari#5366</a>.</p><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible#browser_compatibility"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646266355692/vODUNjGw2.png" alt="image.png" /></a></p><h2 id="heading-solutions">Solutions</h2><p>So while these issues will probably get fixed, we don't need to wait. With the help of browser extensions, we can take matters into our own hands. I'm going to show you what I did, but you can easily extend this approach to do all kinds of fancy stuff.</p><h3 id="heading-css-changing-the-background-color">CSS: Changing the background color</h3><p>I already use the <a target="_blank" href="https://github.com/openstyles/stylus">Stylus</a> extension to inject userstyles into many websites, so this one was extremely simple.</p><p>All I needed to do is slightly change the value of the <code>--background-primary</code> CSS variable.</p><pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {  <span class="hljs-attribute">--background-primary</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-number">250deg</span> <span class="hljs-number">15%</span> <span class="hljs-number">20%</span>);}</code></pre><h3 id="heading-javascript-adding-a-link-to-caniuse">JavaScript: Adding a link to caniuse</h3><p>Like every other frontend developer, I love caniuse.com, so I thought "why not just include a link to caniuse in the MDN page?"</p><p>So I wrote a userscript that I am injecting into MDN pages using <a target="_blank" href="https://www.tampermonkey.net/">Tampermonkey</a>. I will include the code below, but all it does is:</p><ol><li>search the MDN page title on caniuse.com</li><li>get the url to the top search result</li><li>visit the url and parse the browser compatibility percentage</li><li>on the MDN page, add an anchor element that links to the above url and displays the percentage</li></ol><p>Here's what the result looks like:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646266260731/2W8dDwpoE.png" alt="image.png" /></p><p>And here's the code (feel free to modify it as you see fit):</p><pre><code class="lang-js"><span class="hljs-comment">// ==UserScript==</span><span class="hljs-comment">// @name         Add caniuse link to MDN docs</span><span class="hljs-comment">// @namespace    http://tampermonkey.net/</span><span class="hljs-comment">// @version      0.1</span><span class="hljs-comment">// @description  searches the feature name on caniuse and adds the % and link to the BC section</span><span class="hljs-comment">// @author       You</span><span class="hljs-comment">// @match        https://developer.mozilla.org/en-US/docs/Web/*</span><span class="hljs-comment">// @icon         https://www.google.com/s2/favicons?sz=64&amp;domain=mozilla.org</span><span class="hljs-comment">// @grant        GM_xmlhttpRequest</span><span class="hljs-comment">// @grant        window.onurlchange</span><span class="hljs-comment">// @connect      caniuse.com</span><span class="hljs-comment">// ==/UserScript==</span>(<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{<span class="hljs-meta">  'use strict'</span>;  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addCaniuseLink</span>(<span class="hljs-params"></span>) </span>{    <span class="hljs-keyword">const</span> browserCompatHeading = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#browser_compatibility'</span>);    <span class="hljs-keyword">if</span> (!browserCompatHeading) <span class="hljs-keyword">return</span>;    <span class="hljs-keyword">const</span> featureName = <span class="hljs-built_in">window</span>.location.pathname.split(<span class="hljs-string">'/'</span>).at(<span class="hljs-number">-1</span>);    <span class="hljs-keyword">const</span> searchUrl = <span class="hljs-string">`https://caniuse.com/process/query.php?search=<span class="hljs-subst">${<span class="hljs-built_in">encodeURIComponent</span>(featureName)}</span>`</span>;    <span class="hljs-keyword">const</span> caniuseUrl = <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {      GM_xmlhttpRequest({        <span class="hljs-attr">url</span>: searchUrl,        <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,        <span class="hljs-attr">onload</span>: <span class="hljs-function">(<span class="hljs-params">{ response, status, responseType }</span>) =&gt;</span> {          <span class="hljs-keyword">if</span> (status !== <span class="hljs-number">200</span>) reject(<span class="hljs-string">'oopsie'</span>);          resolve(<span class="hljs-string">`https://caniuse.com/<span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.parse(response).featureIds[<span class="hljs-number">0</span>]}</span>`</span>);        },      });    });    <span class="hljs-keyword">const</span> compatPercent = <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {      GM_xmlhttpRequest({        <span class="hljs-attr">url</span>: caniuseUrl,        <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,        <span class="hljs-attr">onload</span>: <span class="hljs-function">(<span class="hljs-params">{ response, status, responseType }</span>) =&gt;</span> {          <span class="hljs-keyword">if</span> (status !== <span class="hljs-number">200</span>) reject(<span class="hljs-string">'oopsie'</span>);          <span class="hljs-keyword">const</span> dom = (<span class="hljs-keyword">new</span> DOMParser()).parseFromString(response, <span class="hljs-string">'text/html'</span>);          resolve(dom.querySelector(<span class="hljs-string">'.support'</span>).textContent);        },      });    });    <span class="hljs-keyword">const</span> caniuseElement = <span class="hljs-string">`      &lt;a        class='external'        href=<span class="hljs-subst">${caniuseUrl}</span>        style='font-size: 1.5rem; font-weight: 300; line-height: 1.5; position: absolute; inset-inline-end: 0; display: inline-flex; align-items: baseline; color: var(--text-secondary);'      &gt;        &lt;span style='font-size: 0.8rem; margin-inline-end: 0.25rem;'&gt;caniuse&lt;/span&gt;        <span class="hljs-subst">${compatPercent}</span>      &lt;/a&gt;    `</span>;    browserCompatHeading.style.position = <span class="hljs-string">'relative'</span>;    browserCompatHeading.insertAdjacentHTML(<span class="hljs-string">'beforeend'</span>, caniuseElement);  }  <span class="hljs-keyword">await</span> addCaniuseLink();  <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'urlchange'</span>, addCaniuseLink);})();</code></pre><h2 id="heading-conclusion">Conclusion</h2><p>And there you have it. Hopefully goes without saying but this approach of modifying websites on your end is very fragile. Your code will probably break in the future, especially if the site gets updated regularly. But I choose to do it anyway just because it's fun. 😅</p>]]></description><link>https://blog.mayank.co/customizing-the-new-mdn-redesign</link><guid isPermaLink="true">https://blog.mayank.co/customizing-the-new-mdn-redesign</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Thu, 03 Mar 2022 00:31:55 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1646267345910/x8CPNyuKw.png</cover_image></item><item><title><![CDATA[Common JavaScript recipes for CSS developers and designers]]></title><description><![CDATA[<p>When building a component or a webpage, you can get pretty far with just HTML and CSS but you'll often find yourself hitting a wall when you want to add <em>interactivity</em> or make things <em>dynamic</em>. Of course this is where JavaScript comes in.</p><p>But maybe it sounds too daunting to learn a whole programming language just to enhance your CSS. Turns out that most use cases can be reduced to just a handful of "recipes". In this post, I will show you some of those common recipes and how they can help you take your UIs to the next level.</p><p>This post is meant to be beginner friendly (including for designers who haven't used JS much), so you may already be familiar with a lot of the content in here. I encourage you to use the table of contents to skip to the parts that interest you the most.</p><details><summary>Prerequisites</summary>We're not going to learn <em>everything</em> about JavaScript, but it helps to start with some extremely basic terminology.<br /><br /> <strong>Variables:</strong> This one is simple if you've used CSS custom properties or Sass variables. A variable is where you store a value and give it a name. Use the <code>let</code> keyword to define a variable (you can also use <code>const</code> if the value doesn't need to change, but please never use the <code>var</code> keyword!). You can assign a value to a variable using <code>=</code>.<br /><br /> <strong>Functions:</strong> If you've used functions or mixins in Sass, it's a similar idea. Functions let you write reusable blocks of code that you can <em>call</em> from multiple places. You can identify them from the <code>function</code> keyword or the <code>=&gt;</code> (arrow) symbol. Functions can optionally accept "arguments" (think of them like <em>input</em>), perform operations on those arguments, and optionally "return" a value (think of it like an <em>output</em>).<br /><br /><strong>Arrays:</strong> Similar to lists in Sass, arrays can contain one or more items (e.g. an array of numbers, or an array of buttons). What's really cool is that arrays provides lots of different ways to access those items and do things with them. For example, if there is an array called <code>numbers</code>, you can access the first number in it using <code>numbers[0]</code>, and you can <em>loop</em> over all numbers using <code>numbers.forEach</code>.<br /><br /><strong>Objects:</strong> An object is a special type of value which consists of <em>properties</em>. Each of these properties can be assigned any kind of value (including functions and arrays), and you can access these properties using the dot notation. For example, if there is an object <code>tea</code> which has a property <code>flavor</code>, you can access it using <code>tea.flavor</code>.<br /><br />This was an extremely quick overview of the terminology you'll need for understanding the recipes in this post. If you want a more detailed write-up, check out Dan Abramov's  <a target="_blank" href="https://overreacted.io/what-is-javascript-made-of/"><em>"What is JavaScript Made Of?"</em></a>.</details><h2 id="heading-basics-ingredients">Basics ("ingredients")</h2><p>We'll start with some very simple recipes (you can even think of them as "ingredients" for the advanced recipes). You can try these in the browser console.</p><h3 id="heading-accessing-dom-elements">Accessing DOM elements</h3><p>DOM elements represent html elements such as <code>&lt;button&gt;</code> and <code>&lt;div&gt;</code>. The webpage itself is represented by <code>document</code>.</p><p>There are many ways to access a DOM element, but my favorite way is to use the built-in <code>querySelector</code> and <code>querySelectorAll</code> functions. Both of these functions accept a CSS selector as the argument, and can be called from <code>document</code> or even from another element. <code>querySelector</code> returns the first instance of the selector that it finds, whereas <code>querySelectorAll</code> returns a list of all instances of that selector.</p><p>In the example below, we find the first <code>&lt;section&gt;</code> element in the page and store it in a variable called <code>firstSection</code>. Then inside that section, we find all <code>&lt;button&gt;</code> elements with the class <code>.text-button</code> and store them in a variable called <code>textButtons</code>. Since this is an array, we can then access the individual buttons from it.</p><pre><code class="lang-js"><span class="hljs-keyword">let</span> firstSection = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'section'</span>);<span class="hljs-keyword">let</span> textButtons = firstSection.querySelectorAll(<span class="hljs-string">'button.text-button'</span>);<span class="hljs-keyword">let</span> firstTextButtonInSection = textButtons[<span class="hljs-number">0</span>];</code></pre><p>There are also a couple special elements which you don't have to query for: <code>document.documentElement</code> will give you the <code>&lt;html&gt;</code> element, and <code>document.body</code> will give you the <code>&lt;body&gt;</code> element.</p><p>You can access the parent of an element (let's say the element is stored in a variable called <code>el</code>) using <code>el.parentElement</code> and you can access its first or second child using <code>el.children[0]</code> and <code>el.children[1]</code> respectively.</p><p>Lastly, you can create an empty DOM element such as <code>&lt;div&gt;&lt;/div&gt;</code> using <code>document.createElement('div')</code>.</p><h3 id="heading-modifying-dom-elements">Modifying DOM elements</h3><p>Now that we have access to the elements, we can make changes to certain aspects of it. For these examples, let's assume we have a DOM element in a variable called <code>el</code>.</p><h4 id="heading-changing-attributes">Changing attributes</h4><p>You can get the current attribute value using <code>getAttribute</code>, assign a new value using <code>setAttribute</code>, toggle a boolean value using <code>toggleAttribute</code>, and remove an attribute using <code>removeAttribute</code>. The following snippet shows how to toggle the value of <code>aria-hidden</code> using a  <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator">ternary operator</a>. We can't use <code>toggleAttribute</code> in this case because  <a target="_blank" href="https://hiddedevries.nl/en/blog/2022-01-12-boolean-attributes-in-html-and-aria-whats-the-difference"><code>aria-hidden</code> is not a boolean attribute</a>.</p><pre><code class="lang-js"><span class="hljs-keyword">let</span> oldVal = el.getAttribute(<span class="hljs-string">'aria-hidden'</span>);<span class="hljs-keyword">let</span> newVal = oldVal === <span class="hljs-string">'true'</span> ? <span class="hljs-string">'false'</span> : <span class="hljs-string">'true'</span>;el.setAttribute(<span class="hljs-string">`aria-hidden`</span>, newVal);</code></pre><p>In many cases, you can actually access the attributes directly using dot notation. For example, you can make a checkbox use the indeterminate state (which is only possible using JavaScript):</p><pre><code class="lang-js"><span class="hljs-keyword">let</span> el = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'[type=checkbox]'</span>);el.indeterminate = <span class="hljs-literal">true</span>;</code></pre><p>However, with this method, the attribute names are camelCase instead of kebab-case.</p><pre><code class="lang-js">el.ariaHidden = <span class="hljs-string">'true'</span>;</code></pre><p>There is also a special way of accessing <code>data-*</code> attributes: using <code>dataset</code>. The following snippet sets <code>data-active="true"</code> on the element.</p><pre><code class="lang-js">el.dataset.active = <span class="hljs-string">'true'</span>;</code></pre><h4 id="heading-changing-styles-and-class-names">Changing styles and class names</h4><p>Possibly the most common thing you would want to do is programmatically change the styles or class names.</p><p>The following snippet is the equivalent of setting <code>style="color: rebeccapurple; background-color: thistle"</code> on an element.</p><pre><code class="lang-js">el.style.color = <span class="hljs-string">'rebeccapurple'</span>;el.style.backgroundColor = <span class="hljs-string">'thistle'</span>;</code></pre><p>This is particularly useful when combined with CSS custom properties which you can manipulate with <code>getPropertyValue</code> and <code>setProperty</code>. The following snippet reads the old value of <code>--x</code>, converts it to a number and increments it, then sends the new value back the <code>--x</code>.</p><pre><code class="lang-js"><span class="hljs-keyword">let</span> oldX = el.style.getPropertyValue(<span class="hljs-string">'--x'</span>);<span class="hljs-keyword">let</span> newX = <span class="hljs-built_in">Number</span>(oldX) + <span class="hljs-number">1</span>;el.style.setProperty(<span class="hljs-string">'--x'</span>, newX);</code></pre><p>For accessing class names, we have two ways: <code>className</code> will give you a single value where all the class names on an element are separated by spaces, and <code>classList</code> will give you a list of those classes. I  prefer <code>classList</code> because it provides a way to easily add/remove/toggle classes.</p><pre><code class="lang-js">el.classList.add(<span class="hljs-string">'new-class'</span>);el.classList.remove(<span class="hljs-string">'old-class'</span>);el.classList.toggle(<span class="hljs-string">'is-active'</span>);el.classList.toggle(<span class="hljs-string">'is-active'</span>, <span class="hljs-literal">true</span>); <span class="hljs-comment">// force this class to be present</span></code></pre><h4 id="heading-changing-inner-content">Changing inner content</h4><p>For accessing the inner text of an element, you can use <code>textContent</code>.</p><pre><code class="lang-js">el.textContent = <span class="hljs-string">'hi'</span>;</code></pre><p>You can add a new element inside another element using <code>appendNode</code>. This is useful if you programmatically created an element using <code>document.createElement</code>.</p><pre><code class="lang-js"><span class="hljs-keyword">let</span> newChildEl = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);el.appendNode(newChildEl);</code></pre><p>You can also replace the entire markup of an element using <code>innerHTML</code> but be careful with this as it can create a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#security_considerations">security risk</a>.</p><pre><code class="lang-js">el.innerHTML = <span class="hljs-string">'&lt;div&gt;hi&lt;/div&gt;'</span>;</code></pre><h2 id="heading-recipes">Recipes</h2><p>Now that we know how to manipulate the DOM, we can start doing more advanced stuff. You could try these recipes in the browser console, but you probably want to use a <code>&lt;script&gt;</code> tag or even a separate <code>.js</code> file. I've also included a few codepen examples showing practical uses for these recipes.</p><h3 id="heading-respond-to-clicks">Respond to clicks</h3><p>Perhaps the most common use case: make things clickable. You may already be doing this today by adding <code>onclick</code> directly in your HTML. But there's a better, more maintainable approach that doesn't require you to manually add <code>onclick</code> to all your buttons.</p><p>Let's first define a function called <code>handleClick</code> that will be called on every click. For now I'm simply logging this action to the browser console, but you can do pretty much anything in here (e.g. change some styles) now that you've learned how to manipulate the DOM.</p><pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleClick</span>(<span class="hljs-params"></span>) </span>{  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Clicked!'</span>);}</code></pre><p>Now let's find our button and add an "event listener" to it. Every DOM element has an <code>addEventListener</code> function which takes two arguments: the name of the event (in this case <code>'click'</code>) and the name of the function (in this case <code>handleClick</code>).</p><pre><code class="lang-js"><span class="hljs-keyword">let</span> primaryButton = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'button.primary-button'</span>);primaryButton.addEventListener(<span class="hljs-string">'click'</span>, handleClick);</code></pre><p>That's it! In fact, you're not limited to just <code>'click'</code> events. You have the ability to add listeners to all kinds of events; check out the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Events">list of event types on MDN</a>.</p><h3 id="heading-toggle-and-store-state">Toggle and store state</h3><p>CSS doesn't offer much in the way of state management, so in the past you might have found yourself reaching for hacks like using an <a target="_blank" href="https://css-tricks.com/the-checkbox-hack/">invisible checkbox</a>. Fortunately, it's quite easy to do using JavaScript.</p><p>One of my favorite places to store state is in a data attribute, which can be easily toggled using JavaScript and also easily styled in CSS.</p><pre><code class="lang-js"><span class="hljs-keyword">let</span> button = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'.toggle-button'</span>);<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">toggleActive</span>(<span class="hljs-params"></span>) </span>{  button.dataset.active = button.dataset.active !== <span class="hljs-string">"true"</span> ? <span class="hljs-string">"true"</span> : <span class="hljs-string">"false"</span>;}button.addEventListener(<span class="hljs-string">'click'</span>, toggleActive);</code></pre><pre><code class="lang-css"><span class="hljs-selector-attr">[data-active=<span class="hljs-string">"true"</span>]</span> {  <span class="hljs-attribute">border</span>: <span class="hljs-number">2px</span> solid;}</code></pre><p>Check out this CodePen where I'm using multiple data attributes and nested functions to create button groups that have independent state yet still share the same code.</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/MrRoboto/pen/BawMqgO">https://codepen.io/MrRoboto/pen/BawMqgO</a></div><h3 id="heading-media-queries">Media queries</h3><p>Media queries are not just a CSS thing. You can access them in JavaScript to build some really powerful UIs. To do that, you can use <code>window.matchMedia</code>. In the following snippet, we check if the user prefers reduced motion, access the boolean result using the <code>matches</code> property, and store the opposite of that result (using <code>!</code>) in a variable called <code>motionOk</code>.</p><pre><code class="lang-js"><span class="hljs-keyword">let</span> motionOk = !<span class="hljs-built_in">window</span>.matchMedia(<span class="hljs-string">'(prefers-reduced-motion: reduce)'</span>).matches;</code></pre><p>We can also add an event listener that will be called every time the media query returns a new value. The following snippet will print <code>true</code> or <code>false</code> in the browser console every time the query runs.</p><pre><code class="lang-js"><span class="hljs-built_in">window</span>.matchMedia(<span class="hljs-string">'(max-width: 500px)'</span>).addEventListener(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {  <span class="hljs-built_in">console</span>.log(e.matches);});</code></pre><p>Here's a practical example showing how to change default state and colors based on user's OS theme.</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/MrRoboto/pen/gOGEwQP">https://codepen.io/MrRoboto/pen/gOGEwQP</a></div><h3 id="heading-window-resizing">Window resizing</h3><p>You have the ability to add a listener on the browser <code>'resize'</code> event, which will be called for each pixel change on the browser's window dimensions. I don't want to go into too much depth here for two reasons:</p><ol><li>You can do a lot with just media queries (and soon container queries).</li><li>This method can be a bit expensive, and it is recommended to use <a target="_blank" href="https://web.dev/resize-observer/"><code>ResizeObserver</code></a> instead, although that is a bit harder to use.</li></ol><p>But it can still be useful in some cases, so here's the syntax.</p><pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doSomethingWithWindowSize</span>(<span class="hljs-params"></span>) </span>{  <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">window</span>.innerHeight, <span class="hljs-built_in">window</span>.innerWidth);}<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'resize'</span>, doSomethingWithWindowSize);</code></pre><p>And here's the syntax for <code>ResizeObserver</code> which lets you observe dimension changes on <em>any</em> element (not just the browser window). Notice how we need to create a resize observer separately, and then manually call <code>observe</code> on the element.</p><pre><code class="lang-js"><span class="hljs-keyword">let</span> el = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'div'</span>);<span class="hljs-keyword">let</span> elResizeObserver = <span class="hljs-keyword">new</span> ResizeObserver(<span class="hljs-function">(<span class="hljs-params">[{ contentRect }]</span>) =&gt;</span> {  <span class="hljs-built_in">console</span>.log(contentRect.height, contentRect.width);});elResizeObserver.observe(el);</code></pre><h3 id="heading-detect-scroll-position">Detect scroll position</h3><p>This is a fun and powerful one, as it opens up quite a few possibilities. Similar to the <code>'resize'</code> event, the browser has a <code>'scroll'</code> event that we can listen to. And we can get the current scroll position using <code>window.scrollX</code> and <code>window.scrollY</code>.</p><pre><code class="lang-js"><span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'scroll'</span>, handleScrollEvent);<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleScrollEvent</span>(<span class="hljs-params"></span>) </span>{  <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">window</span>.scrollX, <span class="hljs-built_in">window</span>.scrollY);}</code></pre><p>You can also read an element's current scroll position by calling <code>getBoundingClientRect()</code> on it, which will give you access to a bunch of information about the size and position of the element. In this case, we care about the <code>top</code> and <code>bottom</code> properties (or <code>left</code> and <code>right</code> for horizontal scrolling). I like to do this in a helper function where I can cleanly destructure those properties. The following function also uses <code>window.innerHeight</code> to return true if either the element's <code>top</code> or <code>bottom</code> are in viewport.</p><pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isElementInViewport</span>(<span class="hljs-params">el</span>) </span>{  <span class="hljs-keyword">const</span> { top, bottom } = el.getBoundingClientRect();  <span class="hljs-keyword">return</span> top &lt;= <span class="hljs-built_in">window</span>.innerHeight &amp;&amp; bottom &gt; <span class="hljs-number">0</span>;}</code></pre><p>Lastly, we can force the page (or even an element) to scroll to a certain position by calling <code>scrollTo</code>.</p><pre><code class="lang-js"><span class="hljs-built_in">window</span>.scrollTo(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>);</code></pre><p>Let's look at a practical example. In this codepen, I'm adding a scroll listener to achieve two things:</p><ul><li>show a "scroll to top" button only after the page is scrolled down a bit</li><li>change the "active" color in the header to match the currently visible color</li></ul><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/MrRoboto/pen/gOGELrg">https://codepen.io/MrRoboto/pen/gOGELrg</a></div><h2 id="heading-conclusion">Conclusion</h2><p>That's all folks! I hope you enjoyed reading this article as much as I enjoyed writing it. These recipes were based on my observations over the last few years. If you feel there is a pattern not covered here that is common enough in your workflow, I'd love to hear about it.</p>]]></description><link>https://blog.mayank.co/javascript-recipes-for-css-developers</link><guid isPermaLink="true">https://blog.mayank.co/javascript-recipes-for-css-developers</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Mon, 17 Jan 2022 04:13:19 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1642392658194/iy3JBNfKF.png</cover_image></item><item><title><![CDATA[Progressively enhancing :focus-visible]]></title><description><![CDATA[<p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible"><code>:focus-visible</code> pseudo-class</a> has been one of the biggest accessibility wins in the recent history of CSS. It took some time but it's finally happening:  <a target="_blank" href="https://bugs.webkit.org/show_bug.cgi?id=234019">Safari has recently unflagged <code>:focus-visible</code></a> and we'll finally be able to use it in 2022! 🎉</p><h2 id="heading-background">Background</h2><p>If you're unfamiliar, <code>:focus-visible</code> allows you to get rid of the annoying focus outline on mouse clicks, while letting you specify the focus styling only for keyboard users (as well as other "tabbing" devices like joysticks)</p><p>2021 was a good year for this pseudo-class:</p><ul><li>Firefox shipped focus-visible.</li><li>Chrome and Firefox both started using it for default focus styles in their User Agent stylesheets.</li><li>And now Safari is on the cusp of shipping it.</li></ul><h2 id="heading-whats-wrong-with-the-popularly-suggested-fallback">What's wrong with the popularly suggested fallback?</h2><p>Maybe you're using the <a target="_blank" href="https://github.com/WICG/focus-visible">polyfill</a> (hopefully not) or maybe you're using <a target="_blank" href="https://www.tpgi.com/focus-visible-and-backwards-compatibility/">Patrick H. Lauke's CSS-only fallback</a> (or even the two in combination).</p><pre><code class="lang-css">  <span class="hljs-selector-pseudo">:focus</span> {    <span class="hljs-comment">/* your custom focus outline */</span>  }  <span class="hljs-selector-pseudo">:focus</span><span class="hljs-selector-pseudo">:not(</span><span class="hljs-selector-pseudo">:focus-visible)</span> {    <span class="hljs-comment">/* undo the focus styling for mouse users */</span>  }</code></pre><p>There are two main problems with this "undo" approach. First, it increases the specificity, which can have unexpected consequences if you rely on the cascade, for say, defining hover styles later. Fortunately you can fix the specificity problem using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:where"><code>:where</code> pseudo-class</a>, which has similar browser support.</p><p>Here's a codepen to demonstrate. Try tabbing through the 3 buttons. Then try clicking and hovering on them. You'll notice that the first button does not respect its <code>:hover</code> and <code>:active</code> rules after it's clicked.</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/MrRoboto/pen/wvrrZve">https://codepen.io/MrRoboto/pen/wvrrZve</a></div><p>The second problem is with code complexity. Undoing focus styling is not trivial, especially if you've taken the extra care to make your component look beautiful in its various states. This problem gets compounded with every new state that you add to your component. Even a seemingly innocuous component like a button can have tons of extra states  active, active + focused, disabled, <a target="_blank" href="https://css-tricks.com/making-disabled-buttons-more-inclusive/">disabled + focused</a>, and so on.</p><h2 id="heading-using-focus-visible-directly">Using <code>:focus-visible</code> directly</h2><p>Since this is still a fairly new feature, you might feel like it's not ready to start using just yet. However, there is good news if you know these two bits of trivia:</p><ol><li><p>Safari already uses <code>:focus-visible</code>-ish logic for buttons and anchor links. That's right, buttons and anchors are <em>not</em> click-focusable in Safari, so regular <code>:focus</code> styles will only be visible to keyboard users.</p></li><li><p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/@supports#testing_for_the_support_of_a_selector"><code>@supports</code> rule can test for a selector</a>. It is fairly new, but has <a target="_blank" href="https://caniuse.com/mdn-css_at-rules_supports_selector">decent browser support</a> (compare stats with your user base as always).</p></li></ol><p>This means you can write code like this:</p><pre><code class="lang-css"><span class="hljs-selector-tag">button</span><span class="hljs-selector-pseudo">:focus-visible</span> {  <span class="hljs-attribute">outline</span>: <span class="hljs-number">2px</span> solid rebeccapurple;}<span class="hljs-keyword">@supports</span> <span class="hljs-keyword">not</span> selector(:focus-visible) {  <span class="hljs-selector-tag">button</span><span class="hljs-selector-pseudo">:focus</span> {    <span class="hljs-attribute">outline</span>: <span class="hljs-number">2px</span> solid rebeccapurple;  }}</code></pre><p>Buttons and anchors make up most of the interactable web, so using this trick will get you most of the way there. For text inputs, there is no difference between <code>:focus</code> and <code>:focus-visible</code> behavior, so that's a non-issue. What remains is the custom interactable elements defined using <code>tabindex=0</code>. Those will still continue to behave as they have, until Safari users can get their hands on <code>:focus-visible</code>.</p><p>In older browser versions where neither <code>:focus-visible</code> or <code>@supports selector</code> is supported, the default focus ring should show instead. Now that's progressive enhancement without any compromises.</p><h3 id="heading-sass-mixin">Sass mixin</h3><p>Since we are duplicating the focus outline styles, there is an opportunity for creating a "helper" of some sort. We can define a handy Sass mixin, utilizing a <a target="_blank" href="https://sass-lang.com/documentation/at-rules/mixin#content-blocks"><code>@content</code></a> block to defer the actual styles to the user of this mixin.</p><pre><code class="lang-scss"><span class="hljs-keyword">@mixin</span> focus-visible {  &amp;<span class="hljs-selector-pseudo">:focus</span>-visible {    <span class="hljs-keyword">@content</span>;  }  <span class="hljs-keyword">@supports</span> <span class="hljs-keyword">not</span> selector(:focus-visible) {    &amp;<span class="hljs-selector-pseudo">:focus</span> {      <span class="hljs-keyword">@content</span>;    }  }}</code></pre><p>Now we can use it throughout our codebase without duplicating any focus styles.</p><pre><code class="lang-scss"><span class="hljs-selector-tag">button</span> {  <span class="hljs-keyword">@include</span> focus-visible {    <span class="hljs-attribute">outline</span>: <span class="hljs-number">2px</span> solid rebeccapurple;  }}</code></pre><p>Sweet!</p><h2 id="heading-what-about-focus-within">What about <code>:focus-within</code>?</h2><p>The last piece of the puzzle is <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within"><code>:focus-within</code></a>. While a little niche, you may have used it before. It's especially handy for giving forms an extra bit of polish. Unfortunately, there is no <code>:focus-visible-within</code>. However, with the <a target="_blank" href="https://caniuse.com/css-has"><code>:has()</code> pseudo-class</a> (which is already available in Safari now!), this will be a trivial matter as you'll be able to just use <code>:has(:focus-visible)</code>.</p><p><em>Update</em>: Safari 15.4 is now shipping :focus-visible! 🥳</p>]]></description><link>https://blog.mayank.co/focus-visible</link><guid isPermaLink="true">https://blog.mayank.co/focus-visible</guid><dc:creator><![CDATA[Mayank]]></dc:creator><pubDate>Fri, 24 Dec 2021 00:46:35 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1640307009941/LvaT_414o.png</cover_image></item></channel></rss>