<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nico Prat</title>
    <description>The latest articles on DEV Community by Nico Prat (@nicooprat).</description>
    <link>https://dev.to/nicooprat</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F377562%2F4cb20872-0fbd-442f-9279-2f3f17507fe1.jpg</url>
      <title>DEV Community: Nico Prat</title>
      <link>https://dev.to/nicooprat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nicooprat"/>
    <language>en</language>
    <item>
      <title>Smooth gradient animation using @property</title>
      <dc:creator>Nico Prat</dc:creator>
      <pubDate>Mon, 09 Mar 2026 15:34:44 +0000</pubDate>
      <link>https://dev.to/nicooprat/smooth-gradient-animation-using-property-3ndh</link>
      <guid>https://dev.to/nicooprat/smooth-gradient-animation-using-property-3ndh</guid>
      <description>&lt;p&gt;I recently stumbled upon a small CSS challenge: we needed a pulsing ring of color, something subtle but noticeable enough to catch the user's eye. At first it sounded like an easy task, but after trying a few different approaches it turned out to be trickier than expected, at least if you want the animation to feel perfectly smooth.&lt;/p&gt;

&lt;p&gt;Eventually it became a good opportunity to finally experiment with the new CSS custom property registration feature, so here’s a short post showing what worked and what didn’t.&lt;/p&gt;

&lt;p&gt;TL;DR: here's the working solution using radial gradient background and @property 👇&lt;/p&gt;

&lt;h2&gt;
  
  
  The theory
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;@property&lt;/code&gt; syntax is an improvement over the basic custom properties we already know, like &lt;code&gt;--size: 20px&lt;/code&gt;. The declaration syntax looks like this (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties" rel="noopener noreferrer"&gt;MDN docs&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--ratio&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'&amp;lt;percentage&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* allows the browser to interpolate values */&lt;/span&gt;
  &lt;span class="py"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The custom property can then be set and used as usual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;
&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ratio&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can animate it as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;--ratio&lt;/span&gt; &lt;span class="err"&gt;100%;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example it doesn’t provide anything that couldn’t be done otherwise, but its real power comes from allowing animation of only &lt;em&gt;parts&lt;/em&gt; of a value. For instance, &lt;code&gt;background-color&lt;/code&gt; is animatable, but &lt;code&gt;background-image&lt;/code&gt; is not. At least, not without using custom properties as we’ll see in a moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practice
&lt;/h2&gt;

&lt;p&gt;So the goal is to animate a ring that grows from its center. Here are the constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it should be &lt;strong&gt;smooth&lt;/strong&gt; (meaning nicely anti-aliased)&lt;/li&gt;
&lt;li&gt;it should have a &lt;strong&gt;consistent thickness&lt;/strong&gt; throughout the animation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's try a few different techniques...&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ Outline offset
&lt;/h3&gt;

&lt;p&gt;For the first attempt, I used &lt;code&gt;outline-offset&lt;/code&gt;. It’s very simple, but the issue is that the value is discrete (not continuous), meaning it animates pixel by pixel instead of subpixel. This is less noticeable on high-DPI displays, though I slowed down the animation here to make the issue more visible. The result doesn’t feel quite as smooth as we’d like.&lt;/p&gt;

&lt;p&gt;

&lt;iframe height="600" src="https://codepen.io/nicooprat/embed/ZYpzVaQ?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Here's a GIF if you can't see the CodePen in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzq27bkcxe3lz6i7aoxcw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzq27bkcxe3lz6i7aoxcw.gif" alt="Animating outline offset"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ Scale
&lt;/h3&gt;

&lt;p&gt;Then I tried using &lt;code&gt;scale&lt;/code&gt;. Still very simple, but the issue is that the thickness grows as well. Yes, I tried to cheat by reducing the &lt;code&gt;border-width&lt;/code&gt; value during the animation, but like &lt;code&gt;outline-offset&lt;/code&gt;, it’s also discrete, so the width jumps during the animation. What's more, Safari seems to use a rasterised version of the element and the result is very blurry.&lt;/p&gt;

&lt;p&gt;

&lt;iframe height="600" src="https://codepen.io/nicooprat/embed/qEaWLXj?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Here's a GIF if you can't see the CodePen in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihid9jdwd3zjghjvow2p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihid9jdwd3zjghjvow2p.gif" alt="Animating scale"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Radial gradient
&lt;/h3&gt;

&lt;p&gt;Finally, I tried using a gradient background, and this time everything is smooth! The idea is not to use a real gradient, but to use its API to draw a solid line that moves.&lt;/p&gt;

&lt;p&gt;The downside is that the implementation becomes slightly more complex because it requires an additional element on top of it that is larger than the original one in order to draw the gradient inside it (backgrounds can’t overflow their element).&lt;/p&gt;

&lt;p&gt;

&lt;iframe height="600" src="https://codepen.io/nicooprat/embed/azmoQed?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Here's a GIF if you can't see the CodePen in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xk008hxxmz8urjf8htm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xk008hxxmz8urjf8htm.gif" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the code, the trick is to animate the gradient &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/gradient/radial-gradient#linear-color-stop" rel="noopener noreferrer"&gt;color stops&lt;/a&gt; values through the &lt;code&gt;--pulse-size&lt;/code&gt; custom property. Once the correct calculations are in place, it becomes easy to reuse and adapt the code using variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.pulse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* original value */&lt;/span&gt;
  &lt;span class="py"&gt;--pulse-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ratio&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="c"&gt;/* drawing the circle */&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c"&gt;/* shape and positioning */&lt;/span&gt;
    &lt;span class="n"&gt;closest-side&lt;/span&gt; &lt;span class="nb"&gt;circle&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c"&gt;/* starting point */&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c"&gt;/* stop drawing transparent */&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--pulse-size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--thickness&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="c"&gt;/* start drawing colored circle */&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--pulse-size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--thickness&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="c"&gt;/* stop drawing the colored circle */&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--pulse-size&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c"&gt;/* start drawing transparent again */&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--pulse-size&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c"&gt;/* ending */&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c"&gt;/* animating the circle */&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulse&lt;/span&gt; &lt;span class="m"&gt;2s&lt;/span&gt; &lt;span class="m"&gt;0.35s&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt; &lt;span class="n"&gt;ease-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In CodePen you'll see some &lt;code&gt;- 1px&lt;/code&gt;, there're here to prevent Safari from drawing aliasing artefacts.&lt;/p&gt;

&lt;p&gt;Now we can simply animate the custom property instead of the whole background gradient (which wouldn’t work anyway):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;pulse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--pulse-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original value for &lt;code&gt;--pulse-size&lt;/code&gt; can't be in the &lt;code&gt;@keyframes&lt;/code&gt; definition as it seems to fail in Firefox at the time of writing, so I set it in the element style directly. Frontend wouldn't be fun without some weird browser quirks here and there, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So the solution was to use a technique that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep the size of the element itself: no scale&lt;/li&gt;
&lt;li&gt;use continues values to prevent "jumps" in the animation: no border or outline&lt;/li&gt;
&lt;li&gt;doesn't suffer from weird browser issues (like scale in Safari)&lt;/li&gt;
&lt;li&gt;keep the document flow intact to get a fluid animation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would you have come up with another solution?&lt;/p&gt;

</description>
      <category>css</category>
    </item>
    <item>
      <title>How to write reviewer-friendly pull requests</title>
      <dc:creator>Nico Prat</dc:creator>
      <pubDate>Fri, 17 Oct 2025 07:54:37 +0000</pubDate>
      <link>https://dev.to/365talents/how-to-write-reviewer-friendly-pull-requests-16aj</link>
      <guid>https://dev.to/365talents/how-to-write-reviewer-friendly-pull-requests-16aj</guid>
      <description>&lt;p&gt;Reviewing someone else's work is often challenging and time-consuming. It can easily lead to procrastination, frustration, or even tension between developers. Yet, it's an essential part of our daily workflow and deserves careful attention.&lt;/p&gt;

&lt;p&gt;Below are a few unordered tips we've gathered over time to make reviewing pull requests easier (from the author's perspective) to help prevent issues before they arise.&lt;/p&gt;

&lt;p&gt;We use GitHub, so some examples are specific to it, but the same ideas apply to most alternatives.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Write small pull requests
&lt;/h2&gt;

&lt;p&gt;This is the most common (and probably the most important) advice. To reinforce it, we &lt;a href="https://github.com/BedrockStreaming/pr-size-labeler" rel="noopener noreferrer"&gt;automatically add t-shirt-sized labels&lt;/a&gt; to our pull requests. This encourages authors to keep them short and reassures reviewers that the review won't take too long.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Always prioritize readability
&lt;/h2&gt;

&lt;p&gt;This applies not only to the codebase itself but also to the files diff. Don't hesitate to add code comments that provide context (eg. data format examples) even if they seem redundant with TypeScript types or equivalents. Anything that helps someone understand the code outside of an IDE is worth including.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Create a simple template
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository" rel="noopener noreferrer"&gt;GitHub pull request templates&lt;/a&gt; to make authors' work easier and ensure consistency across pull requests. A short template with three or four sections (and maybe a few checkboxes) is often enough to remind authors of key steps in the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Explain &lt;em&gt;why&lt;/em&gt; it's done in the description
&lt;/h2&gt;

&lt;p&gt;Include any relevant specifications, documentation, or conversations related to the pull request. This not only helps the reviewer but can also help yourself realize if something has been overlooked (which happens more often than we'd like to admit). Plus, even if the reasoning seems obvious now, it will help future developers understand the decision (especially years down the line). Obviously, make sure to explain how the feature is supposed to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Explain &lt;em&gt;how&lt;/em&gt; it's done in the files diff
&lt;/h2&gt;

&lt;p&gt;Before requesting a review, review your own diff. You might catch accidental commits, hard-to-read code, or even mistakes. It's frustrating for reviewers to find easily avoidable errors. Also, force yourself to add helpful comments in the diff: I personally often find that they actually belong to the code itself and add a commit for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Help the reviewer with tips
&lt;/h2&gt;

&lt;p&gt;I often add "guided tour" comments in the diff, like suggesting to "review with whitespaces ignored" or to "review commit by commit". These small hints can make the process much easier. You can also mark some code lines that are open to discussion or feedback. Encourage the reviewer to be part of the collaboration, not just a gatekeeper.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Make a unique commit for renaming or moving files
&lt;/h2&gt;

&lt;p&gt;Whenever you rename or move files, make it a separate commit from content edits. (Git and GitHub sometimes detect this automatically, but not always.) This allows reviewers to skip the rename commit and focus on the real changes, instead of a massive "deleted and added" diff. This practice made our TypeScript migration much smoother for instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Rebase commits
&lt;/h2&gt;

&lt;p&gt;Coding is rarely linear, mistakes are made, sometimes fixed a few days afterwards. Before opening a pull request, consider taking some time to rebase commits to make the history more readable. Don't try to make it perfect, but at least regroup commits that belongs together so it makes more sense when reviewed.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Suggest a pair review if necessary
&lt;/h2&gt;

&lt;p&gt;For complex pull requests or ones that require intricate setup, a pair review might be faster and more productive. It's also a great opportunity to explain your code out loud: often, you'll discover parts that are hard to justify or could be simplified.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Kill your ego
&lt;/h2&gt;

&lt;p&gt;Take feedback with an open mind. Make reviewers feel appreciated for the time they spend on your work. This way, they'll be more eager to review for you again. Remember that it's usually more valuable to make the reviewing process smooth and collaborative than to be "right" at all costs. That's easy to forget when we're deep in the work.&lt;/p&gt;




&lt;p&gt;I hope those tips can help making this complex task smoother in your daily workflow!&lt;/p&gt;

</description>
      <category>git</category>
      <category>teamwork</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Automatically take care of outdated browsers thanks to Browserslist and Github Actions</title>
      <dc:creator>Nico Prat</dc:creator>
      <pubDate>Mon, 28 Jul 2025 08:46:32 +0000</pubDate>
      <link>https://dev.to/365talents/automatically-take-care-of-outdated-browsers-thanks-to-browserslist-and-github-actions-38p7</link>
      <guid>https://dev.to/365talents/automatically-take-care-of-outdated-browsers-thanks-to-browserslist-and-github-actions-38p7</guid>
      <description>&lt;p&gt;Our policy is to support browsers released within the last two years. Previously, we used Browserslist to transpile our code and automatically add polyfills. At some point, we wondered if we could use the same configuration to inform users when their browser was too old — ideally, without needing to write or maintain that logic ourselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's all in a regex
&lt;/h2&gt;

&lt;p&gt;Fortunately, there's a package that does exactly that: &lt;a href="https://github.com/browserslist/browserslist-useragent-regexp" rel="noopener noreferrer"&gt;browserslist-useragent-regexp&lt;/a&gt;. It &lt;strong&gt;generates a regular expression based on your Browserslist config&lt;/strong&gt;, which you can run against &lt;code&gt;navigator.userAgent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here’s a simple &lt;code&gt;.browserslist.rc&lt;/code&gt; config targeting our policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;last 2 years
not dead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can generate the regex using a script in your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"supportedBrowsers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx update-browserslist-db@latest &amp;amp;&amp;amp; echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;export default $(browserslist-useragent-regexp --allowHigherVersions);&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt; supportedBrowsers.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;npx update-browserslist-db@latest&lt;/code&gt; command ensures your caniuse data is current, preventing the dreaded &lt;code&gt;Browserslist: caniuse-lite is outdated&lt;/code&gt; warning.&lt;/p&gt;

&lt;p&gt;This script outputs something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default /Edge?\/(10[5-9]|1[1-9]\d|[2...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then import and use this regex in your app to notify users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;supportedBrowsers&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./supportedBrowsers.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;supportedBrowsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your browser is not supported, please update it.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automate the update
&lt;/h2&gt;

&lt;p&gt;To keep the regex up to date, we use GitHub Actions to run the script automatically every month. That way, our &lt;code&gt;last 2 years&lt;/code&gt; policy always reflects the current calendar without anyone needing to remember to update it.&lt;/p&gt;

&lt;p&gt;Here’s a simplified version of our workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI - Update browsers list&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# At 8:00AM UTC the first day of the month&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;update_frontend_browserslist&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683&lt;/span&gt; &lt;span class="c1"&gt;# v4.2.2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update browsers list&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;components/frontend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run supportedBrowsers&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Commit and push changes if any&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name Internal&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email internal@yourcompany.com&lt;/span&gt;
          &lt;span class="s"&gt;# Check if there are any changes to the browsers list&lt;/span&gt;
          &lt;span class="s"&gt;if [[ -n "$(git status --porcelain .)" ]]; then&lt;/span&gt;
            &lt;span class="s"&gt;git add .&lt;/span&gt;
            &lt;span class="s"&gt;git commit -m "chore: update supported-browsers.ts"&lt;/span&gt;
            &lt;span class="s"&gt;git push&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place, we get a fresh, accurate regex every month — keeping users informed only when necessary, without adding manual work for the team.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What we learned by running an accessibility audit of our app</title>
      <dc:creator>Nico Prat</dc:creator>
      <pubDate>Tue, 11 Feb 2025 13:30:06 +0000</pubDate>
      <link>https://dev.to/365talents/what-we-learned-by-running-an-accessibility-audit-of-our-app-37n0</link>
      <guid>https://dev.to/365talents/what-we-learned-by-running-an-accessibility-audit-of-our-app-37n0</guid>
      <description>&lt;p&gt;We recently underwent an accessibility audit (thanks to &lt;a href="https://access42.net/" rel="noopener noreferrer"&gt;Access42&lt;/a&gt;)  for our app due to &lt;a href="https://accessibilite.numerique.gouv.fr/obligations/declaration-accessibilite/" rel="noopener noreferrer"&gt;compliance regulations for large companies in France&lt;/a&gt;. Initially, we believed we were in good shape because we use modern tools, adhere to basic HTML structures, and avoid unusual ergonomics.&lt;/p&gt;

&lt;p&gt;However, we decided to invest effort into this area as we needed to meet a minimum score, and the scoring system is quite stringent: any failure on a specific criterion on any audited page counts as a failure, which can quickly degrade the overall score.&lt;/p&gt;

&lt;p&gt;That's when we realized just how poor our accessibility was, how complex the subject is, and how much work lay ahead of us...&lt;/p&gt;

&lt;h2&gt;
  
  
  Before diving in
&lt;/h2&gt;

&lt;p&gt;As a typical developer, I initially believed that accessibility was merely a technical issue to be resolved. However, I soon realized I was mistaken, and I would like to share our experience before delving into more concrete details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get a proper training
&lt;/h3&gt;

&lt;p&gt;It's easy to assume that accessibility is straightforward, but like everything in web development, there's a rich history, numerous quirks, and backward compatibility issues that complicate matters.&lt;/p&gt;

&lt;p&gt;Fortunately, four developers and I had the opportunity to participate in a three-day training session, including hands-on testing of our own app. This was incredibly instructive. We were able to experiment with tools and ask questions on the spot, which was far more efficient than spending hours on Stack Overflow trying to figure out why VoiceOver wasn't behaving as expected...&lt;/p&gt;

&lt;h3&gt;
  
  
  Involve every stakeholder
&lt;/h3&gt;

&lt;p&gt;A web app should be designed with accessibility in mind from the outset, much like performance and other cross-cutting concerns. While developers can implement fixes, these solutions are often less effective for users and require more effort from the team. Examples include manually adding aria-label attributes to icon-only buttons, creating textual alternatives for complex objects, and managing focus states manually when informations are spread on the interface.&lt;/p&gt;

&lt;p&gt;Certain design-specific aspects, such as text size, color contrasts, and considerations for color blindness, cannot be addressed by developers alone. Currently, here, developers are primarily responsible for ensuring the accessibility of the product through testing. However, we hope that a culture of accessibility will gradually permeate all aspects of our work over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the accessibility tree
&lt;/h3&gt;

&lt;p&gt;You might already be familiar with the DOM (Document Object Model) and CSSOM (Cascading Style Sheets Object Model), but we discovered that there is another, less visible underlying model: &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Accessibility_tree" rel="noopener noreferrer"&gt;the accessibility tree&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is primarily derived from HTML elements and results in four properties for each element:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt;: a kind of identifier&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt;: a secondary string describing the element, similar to a tooltip&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role&lt;/code&gt;: one of the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles" rel="noopener noreferrer"&gt;list of roles&lt;/a&gt;. By default, it is &lt;code&gt;generic&lt;/code&gt;; a &lt;code&gt;button&lt;/code&gt; will be &lt;code&gt;button&lt;/code&gt;, an &lt;code&gt;a&lt;/code&gt; will be &lt;code&gt;link&lt;/code&gt;, and it can be overridden by the &lt;code&gt;role=""&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;state&lt;/code&gt;: indicates if the element is currently active, expanded, pressed, checked, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding these properties is crucial for correctly using attributes like &lt;code&gt;aria-label&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;aria-describedby&lt;/code&gt;, and so on, as well as knowing their priority. For example, &lt;code&gt;aria-labelledby&lt;/code&gt; takes precedence over &lt;code&gt;aria-label&lt;/code&gt;. Modern browsers now offer a view of the accessibility tree in their developer tools, making it easier to debug. More on this later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subjectivity
&lt;/h3&gt;

&lt;p&gt;One final note before diving in, regarding what may be the most challenging aspect of accessibility: &lt;strong&gt;subjectivity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are some scenarios where it can be difficult because no tool can help you make a decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should this text be a heading or a paragraph?&lt;/li&gt;
&lt;li&gt;An image has an &lt;code&gt;alt&lt;/code&gt; attribute, but is it truly meaningful?&lt;/li&gt;
&lt;li&gt;A "skip" button has been added, but is it genuinely useful??&lt;/li&gt;
&lt;li&gt;Should this notification be polite or assertive?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, even a 100% accessible website could be frustrating to use with a screen reader. The best way to assess this is to use it ourselves under "real" conditions. The only downside is that it's time-consuming, so I suspect few companies actually do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using accessible librairies
&lt;/h2&gt;

&lt;p&gt;Chances are that you rely on a UI library for common components. You first want to make sure it's already accessible enough, or else you'll lose a huge amount of time trying to fix errors that you don't actually control. In our case, we're migrating from &lt;a href="https://element-plus.org/en-US/" rel="noopener noreferrer"&gt;ElementPlus&lt;/a&gt; to &lt;a href="https://www.radix-vue.com/" rel="noopener noreferrer"&gt;Radix Vue&lt;/a&gt; mainly for this reason.&lt;/p&gt;

&lt;p&gt;It seems modern tools finally treat accessibility as a first class citizen, but it was not the case just a few years ago! &lt;a href="https://ark-ui.com/" rel="noopener noreferrer"&gt;Ark UI&lt;/a&gt; looks like a good alternative too. You can check their docs and even use the tool below to check its accessibility compliance.&lt;/p&gt;

&lt;p&gt;We can also refer to &lt;a href="https://www.w3.org/WAI/WCAG21/Techniques/" rel="noopener noreferrer"&gt;Offical WCAG rules&lt;/a&gt; for simple and framework-agnostic examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual testing
&lt;/h2&gt;

&lt;p&gt;Now that we have the basic theory, it's time to get our hands dirty and find issues and test fixes. Here are the main tools we used:&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser accessibility devtools
&lt;/h3&gt;

&lt;p&gt;Browsers have come a long way in terms of tooling, and it's really helpful to understand the state of a specific element and why it's considered that way. For instance, the &lt;code&gt;aria-label&lt;/code&gt; attribute takes precedence over the &lt;code&gt;title&lt;/code&gt; attribute, so Chrome indicates that it will be ignored:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnl85z99csg6lk0pe5b58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnl85z99csg6lk0pe5b58.png" alt="Screenshot of accessibility tools in Chrome" width="453" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's not as clear in Firefox, but other information is provided:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuahdmt8dsv0ych03ozsm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuahdmt8dsv0ych03ozsm.png" alt="Screenshot of accessibility tools in Firefox" width="402" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each browser offers different tools with their own advantages and disadvantages, so feel free to try them out to find your preferences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axe browser extension
&lt;/h3&gt;

&lt;p&gt;Axe is probably the most well-known suite of tools for testing and improving accessibility. It is essentially included in every major testing solution, as we'll see next.&lt;/p&gt;

&lt;p&gt;You can find the &lt;a href="https://www.deque.com/axe/browser-extensions/" rel="noopener noreferrer"&gt;Axe extensions for browsers&lt;/a&gt; on their website (available for Chrome, Firefox, and Edge). It offers a lot of tools to identify accessibility issues:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9l3i0c9q36ixgfgj77fy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9l3i0c9q36ixgfgj77fy.png" alt="Axe browser extension screenshot" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While it might not be the most feature-complete, as indicated in &lt;a href="https://alphagov.github.io/accessibility-tool-audit/index.html" rel="noopener noreferrer"&gt;this study by the British Government&lt;/a&gt;, it is probably better to have consistent test results across your tools (unit tests, manual tests, E2E tests, etc.) than ultra-exhaustive error reports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows, Firefox &amp;amp; NVDA
&lt;/h3&gt;

&lt;p&gt;This setup is the most widely used worldwide, and NVDA is the most valid and feature-complete free tool available, so it should be preferred when doing tests. If you're on Mac, you can use a virtual machine (we chose Parallels Desktop with Windows 11, which took about 30 minutes to set up and ~130€ one time payment).&lt;/p&gt;

&lt;h3&gt;
  
  
  MacOS, Safari &amp;amp; VoiceOver
&lt;/h3&gt;

&lt;p&gt;VoiceOver is included in every Mac and can be toggled by pressing CMD+F5. It's pretty good by default, but it is known to have some quirks. For instance, it won't read aloud any alert that uses anything other than &lt;code&gt;alert&lt;/code&gt; (&lt;code&gt;log&lt;/code&gt; or &lt;code&gt;status&lt;/code&gt; won't work). If you're on Windows, unfortunately, there's no way to emulate it locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using BrowserStack
&lt;/h3&gt;

&lt;p&gt;Online virtual machines can be useful too. We didn't try them, but &lt;a href="https://www.browserstack.com/docs/live/accessibility-testing/screenreader-desktop" rel="noopener noreferrer"&gt;BrowserStack currently supports screen readers&lt;/a&gt;, and there could be other tools offering similar functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Unit Tests
&lt;/h3&gt;

&lt;p&gt;We believe unit tests are not particularly useful for accessibility, as checking only for HTML attributes is generally insufficient. Tools like Axe can handle this better than manual unit tests, so we didn't use them at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  End-to-End Accessibility Tests with Cypress
&lt;/h3&gt;

&lt;p&gt;End-to-end tests are better suited for accessibility checks because they closely mimic what users will see and interact with. However, there are some quirks with Cypress, such as &lt;a href="https://github.com/cypress-io/cypress/issues/299" rel="noopener noreferrer"&gt;handling tab keys&lt;/a&gt; to check for focus. You might want to look into &lt;a href="https://github.com/dmtrKovalenko/cypress-real-events" rel="noopener noreferrer"&gt;cypress-real-events&lt;/a&gt; for a potential solution. (Edit: It seems like this issue is finally being considered in &lt;a href="https://github.com/orgs/cypress-io/projects/13" rel="noopener noreferrer"&gt;Cypress priorities&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I suppose Playwright might not suffer from those issues as it uses a different, more native way of controlling the browser, but we don't use this tool directly for now.&lt;/p&gt;

&lt;h4&gt;
  
  
  Axe Plugin
&lt;/h4&gt;

&lt;p&gt;We used the &lt;a href="https://github.com/component-driven/cypress-axe" rel="noopener noreferrer"&gt;cypress-axe&lt;/a&gt; plugin to easily integrate Axe into our tests. The upside is that it's straightforward to use; the downside is that reading errors can be challenging (clicking on lines provides better feedback, though):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6118wvyt22t3r9qunbf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6118wvyt22t3r9qunbf.jpg" alt="Example of errors from cypress-axe plugin" width="570" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Cypress Cloud accessibility add-on
&lt;/h4&gt;

&lt;p&gt;Luckily for us, Cypress released a full featured &lt;a href="https://www.cypress.io/accessibility" rel="noopener noreferrer"&gt;accessibility add-on&lt;/a&gt; not so long ago.&lt;/p&gt;

&lt;p&gt;It relies on Axe too, but it's much more integrated in the ecosystem. For instance, the online report is very nice (I can't expose the visual of our app unfortunately):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mjjfym61emgi1uzqclr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mjjfym61emgi1uzqclr.jpg" alt="Cypress Cloud accessibility add-on results" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, the Slack integration is quite nice too:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgoe84jg1xc2wiyg95aw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgoe84jg1xc2wiyg95aw.jpg" alt="Slack message with a summary of accessibility failures" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The obvious downside is that it's a paid add-on, and it's quite expensive 🥵 so we probably won't be able to keep it in our suite.&lt;/p&gt;

&lt;h4&gt;
  
  
  Specific Tests
&lt;/h4&gt;

&lt;p&gt;For more specific tests that don't rely on rules covered by Axe, you can add some manual checks. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first tab should show and focus a "skip to content" link.&lt;/li&gt;
&lt;li&gt;After opening a dialog, its first input should be focused.&lt;/li&gt;
&lt;li&gt;A notification should be shown once an action is completed (also check its attributes to ensure it will be announced by screen readers).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can easily find the focused element in Cypress by running &lt;code&gt;cy.focused()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Component Accessibility Tests with Storybook
&lt;/h3&gt;

&lt;p&gt;Storybook proved to be really useful for accessibility testing. It's easier to set up than end-to-end (E2E) tests but still provides visual feedback and debugging tools. It also allows for complex cases where components are used together or under special conditions. You can write specific &lt;code&gt;play&lt;/code&gt; functions that use any code or components you need, not just the one being tested.&lt;/p&gt;

&lt;p&gt;By the way, Storybook uses &lt;a href="https://testing-library.com/" rel="noopener noreferrer"&gt;Testing Library&lt;/a&gt;, which you can think of as an element selector tool with accessibility in mind. For instance, it won't let you retrieve an element by its class name but forces you to check its role or name (remember the accessibility tree we talked about above?). This is because we should test what the user faces, not the underlying code. It might seem like a small detail, but it sets you in the right mindset when testing.&lt;/p&gt;

&lt;p&gt;Lastly, Storybook uses Playwright under the hood, so it actually controls the browser instead of creating fake events in JavaScript like Cypress does. This makes it easier to work with native interactions like tabbing, which is huge for accessibility.&lt;/p&gt;

&lt;h4&gt;
  
  
  Axe Plugin
&lt;/h4&gt;

&lt;p&gt;Once again, Axe is part of the solution with an &lt;a href="https://storybook.js.org/docs/writing-tests/accessibility-testing" rel="noopener noreferrer"&gt;official accessibility plugin&lt;/a&gt;. It provides a &lt;code&gt;checkA11y()&lt;/code&gt; function that can be set up to run automatically at the end of every test or manually called in a specific test (using the &lt;code&gt;play()&lt;/code&gt; function in Storybook with the &lt;a href="https://storybook.js.org/addons/@storybook/addon-interactions" rel="noopener noreferrer"&gt;addon interactions&lt;/a&gt;). Here's a truncated example of what can be done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In the config&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;preVisit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;injectAxe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;postVisit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;checkA11y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;detailedReport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;detailedReportOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// In the story&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Show a notification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is a notification message!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Accessibility tests will be run automatically here&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, it's pretty straightforward to set up. Errors in the interface are clear and actionable as shown below, but it's hard to read in the CI (GitHub Actions in our case) and we didn't spend time to try to improve it for now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5l5up8e8r80d6m7abik9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5l5up8e8r80d6m7abik9.png" alt="Accessibility errors in Storybook interface" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszk3iw9wgt4jonawxrx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszk3iw9wgt4jonawxrx3.png" alt="Accessibility errors in Github Actions interface" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Specific Tests
&lt;/h4&gt;

&lt;p&gt;We especially appreciated Storybook for running our own specific tests. For instance, we had a hard time ensuring that tooltips were used correctly (tooltips are much harder to get right than we initially thought). So, we interacted with the trigger element by focusing it or not and checked that everything was working as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;play&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;canvasElement&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hover me to show tooltip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Check tabindex to be sure it's focusable&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabindex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;canvasElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tooltip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tooltip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Check role button to be sure it's read by screen readers&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another case was ensuring our focus trap always worked within an open dialog. We used the same mechanism to check that we looped through interactive elements without tabbing out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linting
&lt;/h3&gt;

&lt;p&gt;ESLint can also help developers avoid simple mistakes early in the development process. We use the &lt;a href="https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility" rel="noopener noreferrer"&gt;ESLint plugin VueJS accessibility&lt;/a&gt;, but there are alternatives for other major libraries. While it is the least capable of all the tools we listed, it offers the quickest feedback loop, making it ideal for simple cases.&lt;/p&gt;

&lt;p&gt;We had to enforce and create custom rules for our specific use cases to ensure components are used correctly. For instance, our &lt;code&gt;&amp;lt;Tooltip&amp;gt;&lt;/code&gt; component should have its interactive element as a direct child, like a &lt;code&gt;&amp;lt;Btn&amp;gt;&lt;/code&gt;. We leveraged the &lt;code&gt;vue/no-restricted-syntax&lt;/code&gt; rule as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue/no-restricted-syntax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VElement[rawName="Tooltip"] &amp;gt; VElement:not([rawName=/^(a|router-link|button|Btn)$/]) VElement[rawName=/^(a|router-link|button|Btn)$/]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Interactive elements should be the direct child of a tooltip, or its content will not be read aloud by screen readers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It's &lt;a href="https://github.com/Heydon/principles-of-web-accessibility" rel="noopener noreferrer"&gt;virtually impossible to achieve 100% accessibility&lt;/a&gt; on a complex web app. Companies that claim otherwise are likely mistaken, whether intentionally or not. Some conduct their own internal audits, which are clearly not as reliable as those performed by an external, independent organization.&lt;/p&gt;

&lt;p&gt;We also discovered that there are some difficult and almost undocumented quirks. For instance, a tooltip must be set on an interactive element to be actually announced (e.g., with an implicit or explicit role of &lt;code&gt;button&lt;/code&gt; or &lt;code&gt;link&lt;/code&gt;; a &lt;code&gt;span&lt;/code&gt; would be ignored) and I didn't see documented in common posts and examples. Therefore, the rule is to always test manually and not rely solely on green CI checks for the right attributes.&lt;/p&gt;

&lt;p&gt;It must also be said that an audit is outdated as soon as it's done, as nothing prevents the code from changing from that date. In France, an accessibility declaration is &lt;a href="https://design.numerique.gouv.fr/accessibilite-numerique/declaration-accessibilite/" rel="noopener noreferrer"&gt;only valid for a few years&lt;/a&gt;, and it must be rechecked when the website is "substantially" updated, which is quite subjective.&lt;/p&gt;

&lt;p&gt;So, the "best effort" technique is the only way to go, and accessibility must be treated with sincerity to be achieved at its best. This involves truly treating it as an acceptance criterion, training developers and designers (and every stakeholder, by the way), having automatic tests, and using manual tools.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;a href="https://tink.uk/playing-with-the-accessibility-object-model-aom/" rel="noopener noreferrer"&gt;Accessibility Object Model (AOM)&lt;/a&gt; is an experimental JavaScript API that allows interaction with the accessibility tree directly without using attributes, such as &lt;code&gt;button.accessibleNode.expanded = true&lt;/code&gt;. This could greatly help developers, as it would eliminate the need to rely on easy-to-break IDs everywhere. However, as everything accessibility related, it might be years before it is widely supported...&lt;/p&gt;

</description>
      <category>html</category>
      <category>javascript</category>
      <category>a11y</category>
      <category>vue</category>
    </item>
    <item>
      <title>Customize TypeScript syntax highlighting in VSCode</title>
      <dc:creator>Nico Prat</dc:creator>
      <pubDate>Tue, 07 Jan 2025 18:11:18 +0000</pubDate>
      <link>https://dev.to/365talents/customize-typescript-code-in-vscode-gh4</link>
      <guid>https://dev.to/365talents/customize-typescript-code-in-vscode-gh4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkpxnecn1x4j1sezc8mlk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkpxnecn1x4j1sezc8mlk.jpg" alt="VSCode TypeScript Syntax Highlighting" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TL;DR show me the code&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript can be intimidating&lt;/strong&gt; at first glance, especially when you're used to the relative &lt;strong&gt;simplicity of JavaScript&lt;/strong&gt; (or allergic to the verbosity of other languages you might have studied, like me with Java). When I started learning it a few years ago, I found the code harder to scan, as if &lt;strong&gt;everything was slightly less readable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I then searched for a way to better &lt;strong&gt;distinguish new code from existing code&lt;/strong&gt;, and started exploring how VSCode handles visual themes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor Tokens &amp;amp; Scopes
&lt;/h2&gt;

&lt;p&gt;Similar to an Abstract Syntax Tree (AST), &lt;strong&gt;VSCode breaks down the code in each file into tokens&lt;/strong&gt; to create an abstract view of it, making it easier to manipulate. This abstraction is then used to build themes and analyze the code.&lt;/p&gt;

&lt;p&gt;To better understand this functionality, there are guides on &lt;a href="https://code.visualstudio.com/api/language-extensions/syntax-highlight-guide" rel="noopener noreferrer"&gt;syntax highlighting&lt;/a&gt; and &lt;a href="https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide" rel="noopener noreferrer"&gt;semantic highlighting&lt;/a&gt;. The former focuses on &lt;strong&gt;static&lt;/strong&gt; analysis, while the latter &lt;strong&gt;understands&lt;/strong&gt; the code (for example, recognizing that a variable is actually a parameter and thus ensuring it retains the same color for consistency across occurrences):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjavfz0eb9875afvtyndj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjavfz0eb9875afvtyndj.png" alt="image" width="774" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply put, each character in your code is assigned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;token type&lt;/strong&gt;: String, Comment, ... (&lt;a href="https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide#standard-token-types-and-modifiers" rel="noopener noreferrer"&gt;complete list here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;One or more &lt;strong&gt;scopes&lt;/strong&gt;, which act like subsets or groups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Extensions can add new scopes, for example, for Vue files (Single File Components), which contain HTML, JavaScript, and CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customization
&lt;/h2&gt;

&lt;p&gt;To help us understand this system, VSCode offers a command &lt;strong&gt;"Developer: Inspect Editor Tokens and Scopes"&lt;/strong&gt; that displays a tooltip with information about the currently selected code. Here's an example with the &lt;code&gt;compact&lt;/code&gt; function from &lt;a href="https://lodash.com/" rel="noopener noreferrer"&gt;Lodash&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmap74sf7huj1rd3mc9lo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmap74sf7huj1rd3mc9lo.png" alt="VSCode Developer: Inspect Editor Tokens and Scopes example" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s a lot of information, but the only thing we care about for now is the list of scopes; &lt;strong&gt;the most specific scopes are listed first&lt;/strong&gt;. We can use these to target the elements we want to customize.&lt;/p&gt;

&lt;p&gt;Next, we move on to the VSCode settings to adjust the theme to our needs. The &lt;a href="https://code.visualstudio.com/docs/getstarted/themes#_customizing-a-color-theme" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; explains that you need to edit your &lt;code&gt;settings.json&lt;/code&gt; file as follows (use the "Preferences: Open User Settings (JSON)" command):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"editor.tokenColorCustomizations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"textMateRules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I navigated through the code over the weeks and experimented with rule specificity, I came up with this list of scopes (which is likely not exhaustive):&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"editor.tokenColorCustomizations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"textMateRules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"support.type.builtin.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.interface.ts punctuation.definition.block.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.annotation.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.declaration.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"entity.name.type.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"support.type.primitive.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"entity.name.type.alias.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"entity.name.type.interface.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"storage.type.interface.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"keyword.operator.type.annotation.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"punctuation.definition.typeparameters.begin.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"punctuation.definition.typeparameters.end.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.object.type.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.parameters.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.object.member.ts meta.type.tuple.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"keyword.control.satisfies.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"keyword.control.as.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.declaration.ts punctuation.definition.block.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.declaration.ts keyword.operator.assignment.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.parameters.ts punctuation.separator.comma.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.parameters.ts punctuation.definition.block.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.parameters.ts meta.brace.square.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.annotation.ts keyword.operator.type.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.annotation.ts punctuation.terminator.statement.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.annotation.ts punctuation.definition.block.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.type.annotation.ts string.quoted.single.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.return.type.arrow.ts meta.type.tuple.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"keyword.operator.definiteassignment.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"keyword.operator.optional.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"storage.type.type.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"meta.interface.ts"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"fontStyle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"italic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"foreground"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#ff0000"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, all scopes end with .ts, and scopes can be further refined to ensure they’re nested within another using a space (similar to a CSS selector): "meta.type.annotation.ts keyword.operator.type.ts".&lt;/p&gt;

&lt;p&gt;The only thing left is to decide which styles to apply. Unfortunately, the options are limited: just text color and a few font styles (bold, italic, underline, strikethrough), or a combination of these. To avoid color conflicts with a theme, I chose to apply italics, which remain fairly discreet in daily use. I had considered using a very light background color, but this still isn’t possible in VSCode (open ticket since 2016).&lt;/p&gt;

&lt;p&gt;Here’s the result on an example from the TypeScript documentation with "fontStyle": "italic":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoqoyqbgrr73h1d6gwgl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoqoyqbgrr73h1d6gwgl.png" alt="image" width="800" height="649"&gt;&lt;/a&gt;{width="495"}&lt;/p&gt;

&lt;p&gt;Feel free to find what works best for you, for example, bold with "fontStyle": "bold":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpx0czk5oj3jeubnn5f2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpx0czk5oj3jeubnn5f2v.png" alt="image" width="800" height="647"&gt;&lt;/a&gt;{width="499"}&lt;/p&gt;

&lt;p&gt;You can even experiment with more unusual styles, such as &lt;code&gt;filter: drop-shadow()&lt;/code&gt; (it’s more complicated; here’s how the MoonLight theme does it)](&lt;a href="https://github.com/robb0wen/synthwave-vscode/blob/ec7e97eba96febbcf069256a6513ecedd0b187ae/README.md)):" rel="noopener noreferrer"&gt;https://github.com/robb0wen/synthwave-vscode/blob/ec7e97eba96febbcf069256a6513ecedd0b187ae/README.md)):&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4txkd1yjtlikjbgo0xdo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4txkd1yjtlikjbgo0xdo.png" alt="image" width="800" height="635"&gt;&lt;/a&gt;{width="501"}&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For me, this trick helped a lot in the beginning to better digest TypeScript's new concepts. I could remove this customization now, but I’ve grown used to it and kept it. I hope it helps some of you get started with TypeScript!&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Using BroadcastChannel API with Vue to sync a ref across multiple tabs</title>
      <dc:creator>Nico Prat</dc:creator>
      <pubDate>Mon, 02 Dec 2024 15:32:48 +0000</pubDate>
      <link>https://dev.to/365talents/using-broadcastchannel-api-with-vue-to-sync-a-ref-across-multiple-tabs-h87</link>
      <guid>https://dev.to/365talents/using-broadcastchannel-api-with-vue-to-sync-a-ref-across-multiple-tabs-h87</guid>
      <description>&lt;p&gt;We try to save time for everyone in our team, not only developers: some people spend a lot of time configuring our app for clients or demos, so we make sure it's as smooth as possible. For instance, we try to make every change real time, so there's no need to reload the app for changes to appear.&lt;/p&gt;

&lt;p&gt;Recently, we figured out those people usually work with &lt;strong&gt;multiple tabs open&lt;/strong&gt;, making sure the configuration works as expected in multiple pages of the app. So we thought about syncing the configuration across tabs.&lt;/p&gt;

&lt;p&gt;We didn't want to store it, neither in session, local storage or anything else, as we will then have to make sure it's always up to date.&lt;/p&gt;

&lt;p&gt;That's when we came across the BroadcastChannel API, I didn't even know it existed. It's not fairly new, but Safari was the last to implement it according to &lt;a href="https://caniuse.com/broadcastchannel" rel="noopener noreferrer"&gt;CanIUse&lt;/a&gt;. Anyway it's largely supported now. You can think of it like the good old &lt;code&gt;window.postMessage()&lt;/code&gt; from &lt;code&gt;iframe&lt;/code&gt;, but across multiple tabs of the same origin.&lt;/p&gt;

&lt;p&gt;Luckily for us, VueUse already made a little composable to ease its usage: &lt;a href="https://vueuse.org/core/useBroadcastChannel/#usebroadcastchannel" rel="noopener noreferrer"&gt;https://vueuse.org/core/useBroadcastChannel/#usebroadcastchannel&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isSupported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isClosed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unique-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we created a little in house composable based on it to make sure a &lt;code&gt;ref&lt;/code&gt; is always synchronized among all tabs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useBroadcastChannel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;watchPausable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vueuse/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;nextTick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;watch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useSync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Ref&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;immediate&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Name must be unique&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useBroadcastChannel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// When value changes locally, update other tabs&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resume&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;watchPausable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;structuredClone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// When value changes in another tab, update it locally&lt;/span&gt;
  &lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Prevent watch loop when updating config&lt;/span&gt;
      &lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;nextTick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now we can sync a ref with a single line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;span class="nf"&gt;useSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voilà! That's some hours saved each month across teams 🥳&lt;/p&gt;

</description>
      <category>vue</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Incrementally fixing lots of ESlint errors in a clean way with ESlint Nibble</title>
      <dc:creator>Nico Prat</dc:creator>
      <pubDate>Mon, 18 Nov 2024 16:10:46 +0000</pubDate>
      <link>https://dev.to/365talents/incrementally-fixing-lots-of-eslint-errors-in-a-clean-way-30g9</link>
      <guid>https://dev.to/365talents/incrementally-fixing-lots-of-eslint-errors-in-a-clean-way-30g9</guid>
      <description>&lt;p&gt;As our team is growing, we need more &lt;strong&gt;functional&lt;/strong&gt; and &lt;strong&gt;aesthetic rules&lt;/strong&gt; to keep our codebase &lt;strong&gt;less error-prone&lt;/strong&gt; and &lt;strong&gt;more consistent&lt;/strong&gt;. Fortunately, both our backend and frontend use JavaScript (with the same version of TypeScript) so those changes make a big impact on our daily work for the whole team.&lt;/p&gt;

&lt;p&gt;We usually do this kind of improvements on what we call "tech days" (on monday every two weeks) when we can work on tasks we choose ourselves. We try to do them &lt;strong&gt;incrementally to avoid long lasting branches&lt;/strong&gt; that are hard to maintain and review.&lt;/p&gt;

&lt;p&gt;Recently, we chose to add the &lt;a href="https://github.com/sindresorhus/eslint-plugin-unicorn" rel="noopener noreferrer"&gt;Unicorn plugin&lt;/a&gt;, which contain dozens of ESlint rules. It can feel overwhelming at first because it triggers hundreds of errors. Fortunately, we discovered &lt;a href="https://github.com/IanVS/eslint-nibble" rel="noopener noreferrer"&gt;&lt;code&gt;eslint-nibble&lt;/code&gt;&lt;/a&gt;: a command line tool that helps adding rules one by one with a graphical interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;p&gt;The first step is to install the new plugin we want to apply. Then, instead of enabling the rules one by one, we can now enable them all in &lt;code&gt;.eslintrc.js&lt;/code&gt;. Finally, instead of linting everything at once with &lt;code&gt;eslint&lt;/code&gt;, we can simply use &lt;code&gt;eslint-nibble&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx eslint-nibble &lt;span class="nt"&gt;--fixable-only&lt;/span&gt; &lt;span class="nt"&gt;--no-warnings&lt;/span&gt; &lt;span class="nt"&gt;--cache&lt;/span&gt; &lt;span class="s2"&gt;"./src/**/*.{ts,js,vue}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;it speeds up our process at first with &lt;code&gt;--fixable-only&lt;/code&gt; rules&lt;/li&gt;
&lt;li&gt;since we don't make CI fail on warnings, we use &lt;code&gt;--no-warnings&lt;/code&gt; to clear things up&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;--cache&lt;/code&gt; option is similar to the ESlint one and will make the repeated process much faster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're then welcomed by a nice graphic that shows every failing rule with the count of errors for each one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrz3wvpzr61ajdpsqxm0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrz3wvpzr61ajdpsqxm0.png" alt="graphic that shows every failing rule with the count of errors for each one" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When selecting a rule through its interface, we're proposed to autofix it (if possible). Then, we usually review it manually before committing, just in case something odd pops up.&lt;/p&gt;

&lt;p&gt;Another key point is that it's capable of fixing one and only one rule at a time, even if a line a code contains multiple rule errors. It makes the commits atomic, so it's easy to debug and review for instance.&lt;/p&gt;

&lt;p&gt;If you make any additional change before committing — for example we sometimes need to apply Prettier on the changed line — make sure you &lt;strong&gt;don't save &amp;amp; lint the file&lt;/strong&gt;, because it would fix all other errors related to other rules in the file. The correct way is to manually fix the needed problem only by focusing the line and running &lt;code&gt;Quick fix&lt;/code&gt; , then use the command &lt;code&gt;Fix without formatting&lt;/code&gt; of VS Code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79nagyxjutw9a6dw2u7s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79nagyxjutw9a6dw2u7s.png" alt="Example of multiple lint errors on the same line" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;p&gt;The main advantage is to easily &lt;strong&gt;see which rules are the simplest to add next&lt;/strong&gt;. Each day, we can now choose to fix one rule with a lot of errors, or a lot of rules that have only a few occurrences. Previously, we were blindly enabling rules one by one without knowing their impact in advance.&lt;/p&gt;

&lt;p&gt;It's also an opportunity to &lt;strong&gt;understand the rule itself&lt;/strong&gt; by reading its documentation and why it's better (or not) to do it this way, so we &lt;strong&gt;learn new things&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We sometimes decide to &lt;strong&gt;customize or completely disable a rule&lt;/strong&gt; because it doesn't fit our needs or code style. For instance, we decided to disable a rule forcing to use &lt;code&gt;Set&lt;/code&gt; in some cases: as Vue 2 doesn't support reactivity on &lt;code&gt;Map&lt;/code&gt; and &lt;code&gt;Set&lt;/code&gt;, we thought it could introduce bugs or encourage developers to use it in an unexpected way.&lt;/p&gt;

&lt;p&gt;Finally, it's easier for reviewers to read commits about one rule at a time. This tool makes &lt;strong&gt;progressive enhancement&lt;/strong&gt; a breeze by helping us adding a few ESlint rules from time to time.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>eslint</category>
    </item>
    <item>
      <title>How we migrated from Vue 2 to Vue 3</title>
      <dc:creator>Nico Prat</dc:creator>
      <pubDate>Tue, 12 Nov 2024 13:16:11 +0000</pubDate>
      <link>https://dev.to/365talents/how-we-migrated-from-vue-2-to-vue-3-3ld4</link>
      <guid>https://dev.to/365talents/how-we-migrated-from-vue-2-to-vue-3-3ld4</guid>
      <description>&lt;p&gt;About one year ago we finally migrated from Vue 2 to Vue 3. It was 6 months before its official end of life. At the time, our app has around 100 pages and 300 components, and used some of the classic dependencies tied to Vue: Vue-router, Pinia (and Pinia ORM), Vue-i18n, TipTap, ElementUI (ElementPlus for Vue 3)&lt;/p&gt;

&lt;p&gt;Here are a few advices that helped us. As Vue 2 still work pretty well, you better take the time to ease the migration instead of rushing it, breaking your app and destroying your moral!&lt;/p&gt;




&lt;h2&gt;
  
  
  1️⃣ Before starting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Warn others
&lt;/h3&gt;

&lt;p&gt;Things will certainly break, even a little. You better make sure it's ok with other teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upgrade to latest Vue 2.7
&lt;/h3&gt;

&lt;p&gt;Obviously, as most of the new features are available in Vue 2.7, and retrocompatible, it's important to catch up if you don't already have. While migrating to Vue 3 you'll still be able to enjoy its features. The step to migrate will also be smaller.&lt;/p&gt;

&lt;h3&gt;
  
  
  Take the time
&lt;/h3&gt;

&lt;p&gt;This can be a pretty big work so you better anticipate to mitigate the risk over a long period. Also, it helped us (the developers) keep the moral! Plan a few weeks or even months, as you know when you start, but you don't know when you'll finish... Finally, it's better to dedicate some time every week, so any outage won't be disastrous.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a team
&lt;/h3&gt;

&lt;p&gt;Don't rely on only one developer if possible, as it's an exhausting task. Also, you need quick feedback &amp;amp; merge loops, because a lot of changes will affect the entire codebase and could create conflicts on a daily basis. It's already hard enough without having to handle this!&lt;/p&gt;

&lt;h3&gt;
  
  
  Test driven migration
&lt;/h3&gt;

&lt;p&gt;Half of the time will be spent testing the whole app, so you better automate it the most possible. In our experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests are not very useful, as they're usually testing things not really tied to Vue (only "pure JS" functions for instance), se we didn't focus on them&lt;/li&gt;
&lt;li&gt;Component testing with &lt;a href=""&gt;vue-test-utils&lt;/a&gt; were a pain to migrate, in the end we even had to disable some of them&lt;/li&gt;
&lt;li&gt;End to end tests were the most valuable as it's not tied to any JS or Vue internals and failed as soon as a error was thrown away in the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In conclusion the best is to create so-called "smoke test", where you just navigate within the app with the most basic scenario you can think of. Keep them simple, so they're fast, because the sooner you can make them run, so the more useful they'll be. Bonus point if they run before merging (in a CI for instance).&lt;/p&gt;

&lt;h3&gt;
  
  
  Type everything
&lt;/h3&gt;

&lt;p&gt;TypeScript helped a bit, but support for it in Vue 2 is still poor. Using TypeScript is still a good idea, but it might not help too much there; Once using Vue 3, the &lt;code&gt;setup&lt;/code&gt; syntax will be your best friend though!&lt;/p&gt;

&lt;h3&gt;
  
  
  Lint everything
&lt;/h3&gt;

&lt;p&gt;It's obvious, but a lot of changes will happen in the code, so does a lot of errors. A simple ESLint / Prettier can save a lot of time. If you already do have them, you can check the new rules for Vue 3 meanwhile you migrate: &lt;a href=""&gt;https://eslint.vuejs.org/rules/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Vite
&lt;/h3&gt;

&lt;p&gt;We already had migrated from Webpack to Vite before beginning migrating Vue, so I'm not sure it helps a lot, but as it's the clear standard today, some plugins might not even give instruction for upgrade with Webpack. I guess it's safer to do it first as Vite supports Vue 2, but in the opposite the Vue 3 ecosystem might not support Webpack.&lt;/p&gt;




&lt;h2&gt;
  
  
  2️⃣ Small steps first
&lt;/h2&gt;

&lt;p&gt;The goal is to have the less changes possible when actually upgrading Vue itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upgrade dependencies
&lt;/h3&gt;

&lt;p&gt;Take a look at every Vue-related dependency you use, and check if there's a version that support both Vue 2 and Vue 3, and upgrade to it. &lt;a href=""&gt;Vue-demi&lt;/a&gt; helped a lot plugin maintainers to achieve it, so there's a good chance it's available.&lt;/p&gt;

&lt;p&gt;For instance, you might want to migrate from Vuex to Pinia, which support both versions (Vuex still requires &lt;a href="https://vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html" rel="noopener noreferrer"&gt;a small migration&lt;/a&gt;) and is the new standard anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replace dependencies
&lt;/h3&gt;

&lt;p&gt;For other dependencies, you might want to check if any modern alternative could suit your needs. For instance, we replaced &lt;a href=""&gt;vue-mq&lt;/a&gt; by &lt;a href=""&gt;vue-use&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monkeypatch when needed
&lt;/h3&gt;

&lt;p&gt;We had to be pragmatic, so as some migrations were harder than others, we ended up creating a small layer of abstractions to fix it. For instance, we had a hard time with vue-i18n v9 which brings some (sometimes undocumented) breaking changes. So we finally created a helper that expose a custom version of &lt;code&gt;$t&lt;/code&gt; to prevent having to rewrite every component (because this most used function now doesn't accept "nullish" values for basically no reason). In other terms, don't be too perfectionist!&lt;/p&gt;




&lt;h2&gt;
  
  
  3️⃣ Start migrating
&lt;/h2&gt;

&lt;p&gt;Now the hard work begins. Everything we did before will ease the pain. There's no magic here, it will probably be the hardest part of the migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change everything, but nothing
&lt;/h3&gt;

&lt;p&gt;Replace Vue 2 by Vue 3 with the "&lt;a href=""&gt;migration build&lt;/a&gt;" (also called compat mode) by following the &lt;a href=""&gt;instructions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Turn off every &lt;a href=""&gt;available flag&lt;/a&gt; so the app runs almost as it did with version 2.&lt;/p&gt;

&lt;p&gt;Check that no &lt;a href=""&gt;breaking change&lt;/a&gt; is impacting your app, or fix them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Small steps
&lt;/h3&gt;

&lt;p&gt;&lt;a href=""&gt;Incrementally enable each flag &lt;/a&gt;and test the app thoroughly. If some components are too hard to migrate, keep in mind you can &lt;a href=""&gt;override its compatibility option&lt;/a&gt;; so you better merge everything but keep a few components to migrate later instead of waiting for the whole app to work right now. Don't try to fix everything at once, let some days pass so you can catch any bugs (and recharge your mental health).&lt;/p&gt;

&lt;h3&gt;
  
  
  Big steps
&lt;/h3&gt;

&lt;p&gt;Some dependencies don't provide shortcuts though. In our case, migrating our UI library from ElementUI (Vue 2) to ElementPlus (Vue 3) was hard, and couldn't be split into smaller steps. I heard it was even harder for Vuetify. For this, you'll have to be strong, patient, and take time to do it all at once. Maybe allow a full week for it in your team calendar!&lt;/p&gt;

&lt;p&gt;Remember that you can still override the compatibility behavior of each component you import at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ElButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;element-plus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;ElButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compatConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;FEATURE_ID_A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// features can also be toggled at component level&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Last step
&lt;/h3&gt;

&lt;p&gt;Once every flag has been turned on, it's time to remove the migration build. Hopefully you won't discover new unexpected issues by now!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't forget to celebrate, you deserved it! 🥳&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>vue</category>
    </item>
    <item>
      <title>Restricting some syntax with ESLint</title>
      <dc:creator>Nico Prat</dc:creator>
      <pubDate>Mon, 04 Nov 2024 15:54:20 +0000</pubDate>
      <link>https://dev.to/365talents/restricting-some-syntax-with-eslint-57d9</link>
      <guid>https://dev.to/365talents/restricting-some-syntax-with-eslint-57d9</guid>
      <description>&lt;p&gt;ESlint is a fantastic tool to make our code more consistent, and saves our teams a lot of time. There are a ton of plugins that handle most of the generic use cases, but sometimes we have some specific needs, and creating our own rule would take too much time.&lt;/p&gt;

&lt;p&gt;For the simplest cases, when we just want to prohibit usage of a function (or anything else actually), we can leverage a default rule: &lt;code&gt;no-restricted-syntax&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Abstract Syntax Tree (AST)
&lt;/h2&gt;

&lt;p&gt;Before writing our first selector, we need to understand the underlying system. The AST is simply a representation of a program in the form of nested objects (hence the "Tree" in AST), created by a "parser". It's very flexible because it can be easily read, queried and manipulated. The alternative would be to use regular expressions, but that would be very hard to read and write. So it's basically an intermediate step that allows every great things an IDE does by understanding our code.&lt;/p&gt;

&lt;p&gt;To understand how it works, we can play with the &lt;a href="https://astexplorer.net/#/gist/1f62a8974843d367814a93ce3e1b3329/d81b480006ad0251b576cbe36ae6fe9e0d02e30c" rel="noopener noreferrer"&gt;AST Explorer&lt;/a&gt;, a handy tool that shows a bit of code and its AST in parallel, where you can hover or click on any part of the code to highlight its corresponding AST part:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftjzwxxvpt0g8o914r8z0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftjzwxxvpt0g8o914r8z0.png" alt="Example of the AST Explorer website" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Be careful to correctly select the parser when changing the language&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For instance, when writing &lt;code&gt;Vue&lt;/code&gt; code, be sure to use the &lt;code&gt;vue-eslint-parser&lt;/code&gt; in our case, because we want to write an ESlint selector. You could also inspect what the &lt;code&gt;@vue/compiler-dom&lt;/code&gt; outputs, but you won't be able to query the resulting tree with an ESlint rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a selector
&lt;/h2&gt;

&lt;p&gt;The second helpful tool we'll need is the &lt;a href="https://eslint.org/docs/latest/extend/selectors" rel="noopener noreferrer"&gt;ESLint selectors documentation&lt;/a&gt;. It lists the expression we can use to query the AST, and it might feel familiar if you're used to work with CSS. It's based on the same "cascading" behavior, with matchers like descendants, siblings, node and attributes filtering, and so on. Here are some examples from the docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AST node type: &lt;code&gt;ForStatement&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;attribute value: &lt;code&gt;[attr="foo"]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;nested attribute: &lt;code&gt;[attr.level2="foo"]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;field: &lt;code&gt;FunctionDeclaration &amp;gt; Identifier.id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, given this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dayjs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will generate the following AST using &lt;code&gt;@typescript-eslint/parser&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;VariableDeclaration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;VariableDeclarator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Identifier&lt;/span&gt;
          &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CallExpression&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;callee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Identifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dayjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nl"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="nx"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;const&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;sourceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case, we need to match a function call (&lt;code&gt;CallExpression&lt;/code&gt;) which name is &lt;code&gt;dayjs&lt;/code&gt; (&lt;code&gt;Identifier&lt;/code&gt; with &lt;code&gt;name&lt;/code&gt; property). We also need the direct descendant selector &lt;code&gt;&amp;gt;&lt;/code&gt; to be sure we don't match any function call that whould have a &lt;code&gt;dayjs&lt;/code&gt; identifier nested within. So the selector will be &lt;code&gt;CallExpression &amp;gt; Identifier[name="dayjs"]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Simple function selector
&lt;/h3&gt;

&lt;p&gt;Here's our selector to prevent &lt;code&gt;dayjs&lt;/code&gt; usage without UTC that you can try live in the &lt;a href="https://eslint.org/play/#eyJ0ZXh0IjoiY29uc3QgYSA9IGRheWpzKCk7Iiwib3B0aW9ucyI6eyJlbnYiOnsiZXM2Ijp0cnVlfSwicGFyc2VyT3B0aW9ucyI6eyJlY21hRmVhdHVyZXMiOnt9LCJlY21hVmVyc2lvbiI6ImxhdGVzdCIsInNvdXJjZVR5cGUiOiJzY3JpcHQifSwicnVsZXMiOnsibm8tcmVzdHJpY3RlZC1zeW50YXgiOlsiZXJyb3IiLHsic2VsZWN0b3IiOiJDYWxsRXhwcmVzc2lvbiA+IElkZW50aWZpZXJbbmFtZT0nZGF5anMnXSIsIm1lc3NhZ2UiOiJBbHdheXMgdXNlIGRheWpzLnV0YygpIGluc3RlYWQgb2YgZGF5anMoKSB0byBhdm9pZCB0aW1lem9uZSBpc3N1ZXMifV19fX0=" rel="noopener noreferrer"&gt;ESLint Playground&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-restricted-syntax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CallExpression &amp;gt; Identifier[name="dayjs"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Always use dayjs.utc() instead of dayjs() to avoid timezone issues&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dayjs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;//          ^^^^^ Invalid&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dayjs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also recently decided to ban &lt;code&gt;omit&lt;/code&gt; by default and prefer &lt;code&gt;pick&lt;/code&gt; so we are sure not to expose sensitive data for instance: &lt;a href="https://eslint.org/play/#eyJ0ZXh0Ijoib21pdCgpXG5fLm9taXQoKVxuXy5waWNrKCkiLCJvcHRpb25zIjp7InJ1bGVzIjp7Im5vLXJlc3RyaWN0ZWQtc3ludGF4IjpbImVycm9yIix7InNlbGVjdG9yIjoiQ2FsbEV4cHJlc3Npb25bY2FsbGVlLm5hbWU9J29taXQnXSwgQ2FsbEV4cHJlc3Npb24gSWRlbnRpZmllcltuYW1lPSdvbWl0J10iLCJtZXNzYWdlIjoiUHJlZmVyIHBpY2soKSBvdmVyIG9taXQoKSJ9XX0sImxhbmd1YWdlT3B0aW9ucyI6eyJlY21hVmVyc2lvbiI6ImxhdGVzdCIsInNvdXJjZVR5cGUiOiJzY3JpcHQiLCJwYXJzZXJPcHRpb25zIjp7ImVjbWFGZWF0dXJlcyI6e319fX19" rel="noopener noreferrer"&gt;ESLint playground&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-restricted-syntax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CallExpression[callee.name="omit"], CallExpression Identifier[name="omit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Prefer pick() over omit()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Invalid&lt;/span&gt;
&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Invalid&lt;/span&gt;
&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Inside Vue templates
&lt;/h3&gt;

&lt;p&gt;Here's &lt;a href="https://astexplorer.net/#/gist/ac809ec8d3f0bb4327f7339451d7943f/9508ce1e526d8ea19fc2041239808e0fc6b0aaf2" rel="noopener noreferrer"&gt;another example&lt;/a&gt; that prohibits a (pretty hacky) way of setting local variables in templates in Vue templates (note that the rule is prefixed by &lt;code&gt;vue/&lt;/code&gt; because it needs the &lt;code&gt;eslint-plugin-vue&lt;/code&gt; package):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue/no-restricted-syntax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VAttribute &amp;gt; VExpressionContainer &amp;gt; AssignmentExpression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Do not assign values in templates as it will not be reactive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;:set=&lt;/span&gt;&lt;span class="s"&gt;"(foo = 'bar')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Outputs &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;bar&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt; --&amp;gt;
  &lt;span class="c"&gt;&amp;lt;!--       ^^^^^^^^^^ Invalid --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the way you can &lt;a href="https://dev.to/pbastowski/comment/7fc9"&gt;read more here about this weird trick&lt;/a&gt; that caused us a few reactivity issues in the past, so we decided to prohibit it altogether.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using regex
&lt;/h3&gt;

&lt;p&gt;This is the &lt;a href="https://astexplorer.net/#/gist/21eadea61fc2414ad7574b1081d5cfb1/d46fbabe99408ed4f506faa31d0d298604c84e9f" rel="noopener noreferrer"&gt;last example&lt;/a&gt;, where we had a case where we needed to forbid the usage of a specific set of translations, so we had to find the &lt;code&gt;t&lt;/code&gt; (&lt;a href="https://vue-i18n.intlify.dev/" rel="noopener noreferrer"&gt;or any variation&lt;/a&gt;) function which has a first argument starting with &lt;code&gt;exports.&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-restricted-syntax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CallExpression[callee.name=/^(t|tc|tf|te|d|n)$/][arguments.0.value=/^exports./]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Do not assign values in templates as it will not be reactive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exports.test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;//                  ^^^^^^^^^^^^^^^^^ Invalid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you struggle to come up with the right selector, you could ask ChatGPT for help! It's good at explaining selectors too:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhf5jbbuqws04f67jsx4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhf5jbbuqws04f67jsx4m.png" alt="Example of ChatGPT explaining an ESLint rule selector" width="800" height="838"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also if you need to restrict imports only, it's simpler to use the &lt;a href="https://eslint.org/docs/latest/rules/no-restricted-imports" rel="noopener noreferrer"&gt;&lt;code&gt;no-restricted-imports&lt;/code&gt; rule&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-restricted-imports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lodash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;importNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please import lodash methods directly to allow tree-shaking, e.g. 'import { map } from 'lodash';'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solution works great in the simplest situations, but it won't let you propose an autofix. For more complete solutions, &lt;a href="https://eslint.org/docs/latest/extend/custom-rules" rel="noopener noreferrer"&gt;a custom rule should be created&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;Thanks to those rules, we save time by not repeating the same mistake twice!&lt;/p&gt;

</description>
      <category>eslint</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
