<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tomas Pustelnik</title>
    <description>Personal website of Tomas Pustelnik. A front-end developer with focus on HTML/CSS, React, performance and accessibility. Founder of Clipio.app and tooling addict.</description>
    <language>en-us</language>
    <link>https://pustelto.com/</link>
    <atom:link href="https://pustelto.com/feed.xml" rel="self" type="application/rss+xml" />
    <lastBuildDate>Mon, 19 Jan 2026 00:00:00 +0000</lastBuildDate>
    
    <item>
      <title>What every JavaScript developer should know about HTML and CSS</title>
      <link>https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/</link>
      <pubDate>Mon, 10 Feb 2020 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/</guid>
      <description>&lt;p&gt;Writing web apps in React or other JS frameworks doesn’t mean you don’t need to have a solid foundation of HTML and CSS. While you may be fine most of the time with basic knowledge, having more in-depth knowledge will help you create a much more accessible, robust and maintainable code. And in the end, deliver better products to your users.&lt;/p&gt;
&lt;p&gt;I have put together several concepts and practices I believe every front-end developer should know (not just JavaScript developers, but I often see the biggest lack of knowledge there).&lt;/p&gt;
&lt;p&gt;To avoid the article to be too long, I introduce only the necessary basics in each area and provide links to more resources on the web if you wish to learn more. There are many great articles about each of the topics mentioned here, but I found it useful to have it in one place like this.&lt;/p&gt;
&lt;p&gt;So let’s get started.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;use-html-elements-with-correct-semantics&quot; tabindex=&quot;-1&quot;&gt;Use HTML elements with correct semantics&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#use-html-elements-with-correct-semantics&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Use HTML elements with correct semantics”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;One of the best (and probably easiest) things you can do for accessibility of your web is to use semantic markup. Yet often developers just tend to use &lt;code&gt;divs&lt;/code&gt; and &lt;code&gt;spans&lt;/code&gt;. But HTML is more than just that. It stands for &lt;em&gt;Hyper Text Markup Language&lt;/em&gt; and the &lt;em&gt;markup&lt;/em&gt; word is important. HTML is used to give a structure and meaning to the document. That can hardly be achieved with the bunch of &lt;code&gt;divs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I don’t say you should stop using &lt;code&gt;divs&lt;/code&gt; and &lt;code&gt;spans&lt;/code&gt;. I use them a lot myself. But they have no semantical meaning. They are just containers to help you style your web page and section the content inside other HTML tags which do have meaning. You have probably heard and used heading elements (&lt;code&gt;h1&lt;/code&gt;, &lt;code&gt;h2&lt;/code&gt; and so on), &lt;code&gt;nav&lt;/code&gt;, &lt;code&gt;header&lt;/code&gt;, &lt;code&gt;footer&lt;/code&gt; and &lt;code&gt;main&lt;/code&gt;. Those are probably the most common semantic elements (added in HTML5). Using these is a good start. But there is much more. Do you know there are elements for abbreviations or keyboard input? Just check &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element&quot;&gt;element reference on &lt;abbr title=&quot;Mozilla Developer Network&quot;&gt;MDN&lt;/abbr&gt;&lt;/a&gt; to get the full list with explanations (I strongly suggest to read it).&lt;/p&gt;
&lt;p&gt;And why exactly is it important to use semantic markup? As I mentioned at the beginning of this section, accessibility is the main reason. People using screen readers can easily navigate using headings or so-called landmarks (you can think of a landmark as an important part of the web page). Landmark is created by using semantic HTML like &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;. The second reason is that screen readers also announces what landmark is a user on (like &lt;em&gt;main content&lt;/em&gt; or &lt;em&gt;navigation&lt;/em&gt;).&lt;/p&gt;
&lt;aside&gt;Another way how to create a landmark is to use &lt;code&gt;role&lt;/code&gt; attribute on HTML element eg. &lt;code&gt;&amp;lt;ul role=&quot;navigation&quot;&gt;&lt;/code&gt;. This is useful when creating some more advanced UI components (like menu button with dropdown etc.). But I strongly recommend using existing HTML tags whenever possible.&lt;/aside&gt;
&lt;p&gt;Semantic markup provides us with the context of what we are looking at. But it can also help machines (search engines or some reading devices) understand the structure of the document and display it accordingly. I recommend reading this great &lt;a href=&quot;https://www.brucelawson.co.uk/2018/the-practical-value-of-semantic-html/&quot;&gt;article from Bruce Lawson&lt;/a&gt; about the value of semantic markup.&lt;/p&gt;
&lt;p&gt;Here is some advice on how to start with semantic markup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;h&lt;/code&gt; tags of proper level (don’t skip heading levels) to create a correct outline of the document.&lt;/li&gt;
&lt;li&gt;Start using &lt;code&gt;nav&lt;/code&gt;, &lt;code&gt;header&lt;/code&gt;, &lt;code&gt;footer&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;article&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check other HTML tags to see what else you can use and try to slowly incorporate them into your toolbelt.&lt;/li&gt;
&lt;li&gt;Read more about this topic, for example &lt;a href=&quot;https://www.smashingmagazine.com/2020/01/html5-article-section/&quot;&gt;article on Smashing Magazine about section vs article&lt;/a&gt; or &lt;a href=&quot;https://css-tricks.com/how-to-section-your-html/&quot;&gt;article on CSS-tricks about structuring document&lt;/a&gt; (be sure to read the comments in this one as well, there is a lot of good info).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;learn-how-to-use-form-elements&quot; tabindex=&quot;-1&quot;&gt;Learn how to use form elements&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#learn-how-to-use-form-elements&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Learn how to use form elements”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Forms are often one of the most complex parts of the web app – a lot of states and moving parts. For a great UX, it is essential that all these parts work smoothly and as a user expects. Nothing kills a good impression from your web like a form you can’t submit with &lt;kbd&gt;Enter&lt;/kbd&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;wrap-forms-in-%3Cform%3E-element&quot; tabindex=&quot;-1&quot;&gt;Wrap forms in &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#wrap-forms-in-%3Cform%3E-element&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Wrap forms in &amp;lt;form&amp;gt; element”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;It seems obvious, right? But I have seen forms without it. Especially in today’s world of JS frameworks, it’s easy to create a form-like component without it. But unless it’s something simple as a standalone button with some specific functionality (like a toggle for menu) you should always use &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element. Your users will thank you. First, it tells screen readers that this is a form (we already talk about semantic markup). Second, it gives you standard UX behavior out of the box – submitting the form on pressing &lt;kbd&gt;Enter&lt;/kbd&gt;, ability to submit or reset form via a button and listen on form submit event to process submission.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;always-use-inputs-with-labels&quot; tabindex=&quot;-1&quot;&gt;Always use inputs with labels&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#always-use-inputs-with-labels&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Always use inputs with labels”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Inputs have to have a label, it tells the users what the input is for. Not a placeholder or some &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; but a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element. Placeholder is only a visual hint, it disappears once we enter something into the input field and some screen readers can ignore them (for a more detailed explanation why you should use labels instead of placeholder attribute see this &lt;a href=&quot;https://joshuawinn.com/ux-input-placeholders-are-not-labels/&quot;&gt;great summary&lt;/a&gt; from Joshua Winn). So use &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; instead and connect it to the input field with for attribute like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;username&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Name&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;username&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!--&lt;br /&gt; or nest the input into the label,&lt;br /&gt; that way id is not required, if you nest only one input :-)&lt;br /&gt;--&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Name&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;surname&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- or --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;address&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Address&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;address&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way when you click on a label a connected input field will get focus. This is extremely important for radio buttons and checkboxes since &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; will increase the touch area of the radio/checkbox and make them easier to select on mobile devices.&lt;/p&gt;
&lt;p&gt;If you can’t use &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; for some reasons you may use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute&quot;&gt;aria-label&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute&quot;&gt;aria-labelledby&lt;/a&gt; attributes as an alternative. But native &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element should be always your first candidate.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;use-type-attribute-on-a-button-element&quot; tabindex=&quot;-1&quot;&gt;Use type attribute on a button element&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#use-type-attribute-on-a-button-element&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Use type attribute on a button element”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Since you are using &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element for your forms you want to always set type attribute on your buttons. There are three possible values - &lt;code&gt;submit&lt;/code&gt;, &lt;code&gt;reset&lt;/code&gt; and &lt;code&gt;button&lt;/code&gt;. Submit and reset values are useful only nested in &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element (first submits the form, the second one will clear the input fields in the form). Type &lt;code&gt;button&lt;/code&gt; is a generic type for all other buttons (menu toggles, showing/closing modals, etc.).&lt;/p&gt;
&lt;p&gt;And why should you always specify it? Because button elements without type attribute specified get the type set to &lt;code&gt;submit&lt;/code&gt; by default. And it can lead to bugs when in the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element since you can submit a form unintentionally.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;make-things-accessible&quot; tabindex=&quot;-1&quot;&gt;Make things accessible&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#make-things-accessible&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Make things accessible”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Accessibility (a11y for short) is hard, especially in single-page applications it can be a daunting task. I struggle with it sometimes as well. But we should still try to make our products as much accessible as possible. While there is a lot to cover in terms of a11y (enough for a separate blog post or more likely a book). Most common issues I encounter often are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;sites aren’t using correct semantic markup (we already covered that)&lt;/li&gt;
&lt;li&gt;and they are hard to use without a mouse&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are a few things you can do to mitigate those issues.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;always-provide-styles-for-focused-interactive-elements&quot; tabindex=&quot;-1&quot;&gt;Always provide styles for focused interactive elements&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#always-provide-styles-for-focused-interactive-elements&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Always provide styles for focused interactive elements”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;All interactive elements have default browser styles for focus state (usually in the form of outline). Users browsing the web using a keyboard use it to see where they are on the page. Remove it and the user does not see any feedback on his position (what element he can click on). It’s like driving in the dark night without lights turned on. You have no idea where you are and what will happen if you turn the wheel.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Removing focus ring and failing to provide an alternative to the users is the same as driving a car in the dark night without lights turned on. You have no idea where you are and what will happen if you turn the wheel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you absolutely have to remove the default one, replace it with another visual hint (based on your design system) when an element is focused. But never remove it entirely.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;use-correct-elements-for-interactions-in-the-app&quot; tabindex=&quot;-1&quot;&gt;Use correct elements for interactions in the app&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#use-correct-elements-for-interactions-in-the-app&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Use correct elements for interactions in the app”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;We are back to semantic HTML. Native interactive elements like buttons and inputs can be controlled by a keyboard and focused out of the box. No JS or special attributes required. If you need a link, use &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; not a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with some javascript dark magic.&lt;/p&gt;
&lt;p&gt;Of course, you can make &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; to behave and look like a button (even for screen readers). In all my career, I have to do it only once due to some conflicts with drag and drop in React (library did not like a button to be draggable). It was ugly, and it took some time to make it accessible. I would prefer to use the native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;And one last thing. Use &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag when navigating to a different page, use &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; for everything else. That means no &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag with JS opening a side menu or &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; using JS to navigate to a different page.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;don%E2%80%99t-nest-buttons-and-links&quot; tabindex=&quot;-1&quot;&gt;Don’t nest buttons and links&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#don%E2%80%99t-nest-buttons-and-links&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Don’t nest buttons and links”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;How would you like to have an &lt;kbd&gt;Esc&lt;/kbd&gt; key placed on top of your &lt;kbd&gt;Enter&lt;/kbd&gt;? Your press one and both of them are pressed. That would be super annoying, right? That’s exactly what you do when you nest buttons and anchor links into each other. It’s not semantically correct and it’s confusing for screen reader users. Not to mentioned it can create some weird behavior in the app.&lt;/p&gt;
&lt;p&gt;How to do some more complex UI components like a clickable card with additional buttons inside of it? You will have to use a little bit of CSS and absolute positioning or wrap only some part of the component (like heading) into &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag, not the entire card. For more tips check &lt;a href=&quot;https://css-tricks.com/breakout-buttons/&quot;&gt;this article on CSS-tricks&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;box-model-and-sizes&quot; tabindex=&quot;-1&quot;&gt;Box model and sizes&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#box-model-and-sizes&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Box model and sizes”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Every element on the web page is a box. You can skew it, make it round, but for a browser, it will always be a box. I have met a few developers who didn’t know how exactly is the size and position of these boxes determined. That caused them some issues when they tried to style those elements. I won’t go into too much detail there. Plenty of resources about this topic already exist. You may check &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model&quot;&gt;the article about the box-model on &lt;abbr title=&quot;Mozilla Developer Network&quot;&gt;MDN&lt;/abbr&gt;&lt;/a&gt; or &lt;a href=&quot;https://www.w3.org/TR/CSS2/box.html&quot;&gt;dive into the spec&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I want to go through a few basic things to keep in mind.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;box-sizing-css-property&quot; tabindex=&quot;-1&quot;&gt;Box-sizing CSS property&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#box-sizing-css-property&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Box-sizing CSS property”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Box-sizing property controls how the final size of the HTML element will be calculated. By default it values is &lt;code&gt;content-box&lt;/code&gt; which means that width and height attributes (and their min and max variants) affect only a content area of the element (blue part in the picture below).&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-320.avif 320w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-480.avif 480w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-640.avif 640w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-960.avif 960w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-320.webp 320w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-480.webp 480w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-640.webp 640w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-960.webp 960w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-320.jpeg 320w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-480.jpeg 480w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-640.jpeg 640w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-960.jpeg 960w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Box model schema showing content, paddding, border and margin areas&quot; src=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/4OkzrHZvit-320.jpeg&quot; width=&quot;320&quot; height=&quot;127&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;If you add padding or border to the element, it will increase its size. That may result in the element overflowing from its container and can be a cause of some confusion for people who do not know about this (especially if they use some CSS reset without knowing what it does exactly). To see the difference between &lt;code&gt;box-sizing&lt;/code&gt; set to &lt;code&gt;content-box&lt;/code&gt; and &lt;code&gt;border-box&lt;/code&gt; have a look at the codepen below.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;324&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;Pustelto&quot; data-slug-hash=&quot;MWYdRKj&quot; data-preview=&quot;true&quot; style=&quot;height: 324px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Box sizing comparison&quot;&gt;
    &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/Pustelto/pen/MWYdRKj&quot;&gt;
    Box sizing comparison&lt;/a&gt; by Tomas Pustelnik (&lt;a href=&quot;https://codepen.io/Pustelto&quot;&gt;@Pustelto&lt;/a&gt;)
    on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
   &lt;/p&gt;
   &lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;To make &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; properties affect the padding and border of the element as well set value of &lt;code&gt;box-sizing&lt;/code&gt; to &lt;code&gt;border-box&lt;/code&gt;. Here is a suggested way of doing this.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* :root stands for html, but it has higher specificity (same as class) */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;:root&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;box-sizing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; border-box&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/*&lt;br /&gt; This way it&#39;s easier to override the box-sizing if needed&lt;br /&gt; (children will inherit it from parent)&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;*,&lt;br /&gt;*:before,&lt;br /&gt;*:after&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;box-sizing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inherit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;inline-elements-have-extra-space-in-between-them&quot; tabindex=&quot;-1&quot;&gt;Inline elements have extra space in between them&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#inline-elements-have-extra-space-in-between-them&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Inline elements have extra space in between them”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Again not very well known &lt;q&gt;feature&lt;/q&gt; of CSS - if you have several inline or inline-block elements next to each other, you get small space between them. If you dig deeper you will found out that CSS is not the culprit.&lt;/p&gt;
&lt;p&gt;It’s because space between inline elements in the code is treated as space (space between words is a good thing most of the time). However, in some cases, it’s not what you want. The easiest way of getting rid of it these days is to set &lt;code&gt;display: flex&lt;/code&gt; on the container. For more detailed info I suggest &lt;a href=&quot;https://css-tricks.com/fighting-the-space-between-inline-block-elements/&quot;&gt;this article on CSS tricks&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;margin-collapsing&quot; tabindex=&quot;-1&quot;&gt;Margin collapsing&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#margin-collapsing&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Margin collapsing”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;If you already have experience building websites, you have probably encountered this. You work on the page styles, adding margin-bottom to your article heading and then some margin-top to the main image just below it. But the white space between them is not the sum of both margins. It is only the size of the bigger one. That’s margin collapsing.&lt;/p&gt;
&lt;p&gt;When two vertical margins meet, they will collapse. This happens between sibling elements and sometimes between parent and child (if they aren’t separated by border, padding, or if there is no formating context on parent element).&lt;/p&gt;
&lt;p&gt;You may play with the codepen below to see how margin collapsing changes with different CSS properties.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;324&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;Pustelto&quot; data-slug-hash=&quot;yLypxGr&quot; data-preview=&quot;true&quot; style=&quot;height: 324px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Margin collapsing&quot;&gt;
    &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/Pustelto/pen/yLypxGr&quot;&gt;
    Margin collapsing&lt;/a&gt; by Tomas Pustelnik (&lt;a href=&quot;https://codepen.io/Pustelto&quot;&gt;@Pustelto&lt;/a&gt;)
    on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
   &lt;/p&gt;
   &lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;For a more detailed explanation, do check the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing&quot;&gt;&lt;abbr title=&quot;Mozilla Developer Network&quot;&gt;MDN&lt;/abbr&gt; page about margin collapsing&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;use-appropriate-sizing-units&quot; tabindex=&quot;-1&quot;&gt;Use appropriate sizing units&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#use-appropriate-sizing-units&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Use appropriate sizing units”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Don’t use pixels.&lt;/p&gt;
&lt;p&gt;CSS offers a variety of different length units. Probably the most well-known unit is a pixel (&lt;code&gt;px&lt;/code&gt;). My advice is – don’t use it. The main reason is accessibility. You can change the default font size in the browser settings, but if you define font sizes on your web in pixels this will not work. The text will have the same size.&lt;/p&gt;
&lt;p&gt;Instead of pixels, it’s best practice to use &lt;code&gt;rem&lt;/code&gt; (rem stand for root em). More precisely, use relative units as much as possible (viewport units, percentage or size elements using flexbox and grid properties) and for fixed sizes (margins, font-size, icons…), use &lt;code&gt;rem&lt;/code&gt;.&lt;/p&gt;
&lt;aside&gt;Under normal circumstances (user did not change the font size settings in the browser) 1rem equals 16px.&lt;/aside&gt;
&lt;p&gt;&lt;code&gt;rem&lt;/code&gt; works similarly to &lt;code&gt;em&lt;/code&gt; unit except its size is calculated from the font-size of the root element (&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;). Unlike with &lt;code&gt;em&lt;/code&gt;, there isn’t a problem with the multiplication of the sizes – if you nest elements inside each other, font-sizes with &lt;code&gt;em&lt;/code&gt; units will infer it’s size from the parent. See the codepen below for an example (I have shamelessly copied the code from &lt;a href=&quot;https://tympanus.net/codrops/css_reference/&quot;&gt;Codedrops CSS reference&lt;/a&gt;)&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;324&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;Pustelto&quot; data-slug-hash=&quot;jOEJmgN&quot; data-preview=&quot;true&quot; style=&quot;height: 324px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Em units cascade&quot;&gt;
    &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/Pustelto/pen/jOEJmgN&quot;&gt;
    Em units cascade&lt;/a&gt; by Tomas Pustelnik (&lt;a href=&quot;https://codepen.io/Pustelto&quot;&gt;@Pustelto&lt;/a&gt;)
    on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
   &lt;/p&gt;
   &lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;If we are talking about font sizes, we have to mention &lt;code&gt;line-height&lt;/code&gt; property as well. Always define it as a unitless number (eg. 1.5). This way browser will calculate &lt;code&gt;line-height&lt;/code&gt; as a multiple of the &lt;code&gt;font-size&lt;/code&gt;. Again this is important for accessibility to ensure enlarged texts are readable. For a detailed explanation of this (and with examples) check &lt;a href=&quot;https://www.24a11y.com/2019/pixels-vs-relative-units-in-css-why-its-still-a-big-deal/&quot;&gt;Kathleen McMahon’s article&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;stacking-context&quot; tabindex=&quot;-1&quot;&gt;Stacking context&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#stacking-context&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Stacking context”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The stacking context controls how elements on the page are layered on the z-axis. From the user’s perspective – how are they layered and how they overlay each other. Think of modal dialog with semi-transparent overlay covering your entire page.&lt;/p&gt;
&lt;p&gt;Sometimes you may encounter an issue where the element with high &lt;code&gt;z-index&lt;/code&gt; is covered by another element with smaller &lt;code&gt;z-index&lt;/code&gt; (even though it should be above everything else). This may be confusing for people without the idea of what and how stacking context works.&lt;/p&gt;
&lt;p&gt;There isn’t usually one but several stacking contexts at the same time. And element with stacking context works like a container for its children with the &lt;code&gt;z-index&lt;/code&gt; property. &lt;code&gt;z-index&lt;/code&gt; has an effect only inside of this stacking context, but not outside. Which in practice means that element with &lt;code&gt;z-index&lt;/code&gt; value of &lt;code&gt;999&lt;/code&gt; can be cover with another element with &lt;code&gt;z-index&lt;/code&gt; value &lt;code&gt;1&lt;/code&gt;. Check the codepen below for a concrete example.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;324&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;Pustelto&quot; data-slug-hash=&quot;abzMwvw&quot; data-preview=&quot;true&quot; style=&quot;height: 324px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Stacking context example&quot;&gt;
    &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/Pustelto/pen/abzMwvw&quot;&gt;
    Stacking context example&lt;/a&gt; by Tomas Pustelnik (&lt;a href=&quot;https://codepen.io/Pustelto&quot;&gt;@Pustelto&lt;/a&gt;)
    on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
   &lt;/p&gt;
   &lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;What you usually want to do in such a case is to add or update &lt;code&gt;z-index&lt;/code&gt; on the element which creates stacking context (&lt;code&gt;header&lt;/code&gt; and &lt;code&gt;main&lt;/code&gt; in the example codepen). Check &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context&quot;&gt;this article on &lt;abbr title=&quot;Mozilla Developer Network&quot;&gt;MDN&lt;/abbr&gt;&lt;/a&gt; if you want to see when exactly is stacking context created.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;css-specificity&quot; tabindex=&quot;-1&quot;&gt;CSS specificity&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#css-specificity&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “CSS specificity”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Let’s have a look at the code bellow.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;.red.blue&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; magenta&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;p.blue&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;red blue&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;What color do I have?&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What color will the text have? If you answered magenta, you are correct, and probably know what CSS specificity is. If you guess it incorrectly, or perhaps you are not sure why does it have magenta color and not blue, read on.&lt;/p&gt;
&lt;p&gt;Browsers apply styles in the order they appear in the source code. So later styles overwrite styles that appeared early in the document. That’s how web browsers decide which styles to use in case of conflicts. This works for duplicate properties in declaration block (this way we can provide fallbacks for older browsers) and for the declaration blocks as well if we have more selectors targeting the same element.&lt;/p&gt;
&lt;p&gt;However, with multiple selectors targeting one element, there is another factor in the game and that is CSS specificity. Which brings us back to the example at the beginning of this section. The paragraph will have magenta color because the first selector (&lt;code&gt;.red.blue&lt;/code&gt;) is more specific than the second selector (&lt;code&gt;p.blue&lt;/code&gt;). You can think of specificity as the importance of given CSS rule. Bigger specificity means bigger importance and thus such rule overwrite other rules which are less specific.&lt;/p&gt;
&lt;p&gt;You can see in the image below how is the specificity calculated.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-320.avif 320w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-480.avif 480w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-640.avif 640w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-320.webp 320w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-480.webp 480w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-640.webp 640w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-320.jpeg 320w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-480.jpeg 480w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-640.jpeg 640w, https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Schema showing how CSS specificity is calculated&quot; src=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/QxYL_oPk4_-320.jpeg&quot; width=&quot;320&quot; height=&quot;172&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;When you compare the specificity of two selectors, the higher one wins and its rules will be applied. In our case &lt;code&gt;.red.blue&lt;/code&gt; has specificity &lt;em&gt;0-2-0&lt;/em&gt; (we have two classes there) and the &lt;code&gt;p.blue&lt;/code&gt; has &lt;em&gt;0-1-1&lt;/em&gt; (one class and one element selector).&lt;/p&gt;
&lt;p&gt;You can also see that id selector beats all other selectors (classes, elements, etc.) and it can be overwritten only by another id selector, inline styles or &lt;code&gt;!important&lt;/code&gt; flag. To keep your stylesheets maintainable, try to keep specificity as low as possible. Preferably use only classes to style elements and avoid id selectors at all costs. Beating rules with high specificity with even higher specificity will lead to a circle of specificity wars that is hard to broke from without some major refactoring.&lt;/p&gt;
&lt;p&gt;Last info you need to know is how inline styles and &lt;code&gt;!important&lt;/code&gt; work with specificity. Inline styles beat any selector (even id), but they are hard to override because of that. &lt;code&gt;!important&lt;/code&gt; beats everything and only another property with a &lt;code&gt;!important&lt;/code&gt; flag can override it. So be extra careful when using it. A good use case for it is in utility classes which do only one single thing and they should take effect no matter what or when overriding some third-party styles.&lt;/p&gt;
&lt;p&gt;Again if you want to learn more about it, I suggest articles on &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity&quot;&gt;&lt;abbr title=&quot;Mozilla Developer Network&quot;&gt;MDN&lt;/abbr&gt;&lt;/a&gt; or &lt;a href=&quot;https://css-tricks.com/specifics-on-css-specificity/&quot;&gt;CSS tricks&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;And that’s it. Congratulations on reading this far, I hope you like it and find it useful. HTML and CSS may seem trivial at first and not worthy of your time. But as you have seen there are few concepts that can help you write better code, be more efficient in it and have the potential to save you or your colleagues a ton of time trying to fix poorly written front-end.&lt;/p&gt;
&lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;!-- prettier-ignore --&gt;
</description>
    </item>
    
    <item>
      <title>Don&#39;t ignore Fieldset and Legend HTML elements</title>
      <link>https://pustelto.com/blog/dont-ignore-fieldset-and-legend-html-elements/</link>
      <pubDate>Mon, 23 Mar 2020 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/dont-ignore-fieldset-and-legend-html-elements/</guid>
      <description>&lt;p&gt;Creating accessible forms (especially more complex ones) may be difficult. There are many moving parts and different kinds of inputs. And we often have to use JavaScript to make the form accessible for all (check this &lt;a href=&quot;https://daverupert.com/2020/02/html-the-inaccessible-parts/&quot;&gt;great summary from Dave Rupert to see what native HTML inputs are not accessible&lt;/a&gt;). But on the other hand, there are HTML tags which can help us a lot with structuring our form and make a great deal of work to make it more accessible. There is for example a &lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; element which got &lt;a href=&quot;https://gomakethings.com/how-to-create-an-autocomplete-input-with-only-html/&quot;&gt;some spotlight recently&lt;/a&gt;. Other useful elements are &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt;. Today I want to talk about those two.&lt;/p&gt;
&lt;p&gt;Maybe you have heard about them, but a quick introduction won’t hurt. This is what specification says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; element represents a set of form controls optionally grouped under a common name.&lt;/p&gt;
&lt;p&gt;The name of the group is given by the first &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; element that is a child of the &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; element, if any. The remainder of the descendants form the group.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A typical example could be a billing and delivery addresses or a bunch of checkboxes to select your interests. Those inputs create a logical group and for better accessibility, you should enclose the in the fieldset. Legend serves as heading for the fieldset and to make fieldset useful, you have to use it.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-are-they-good-for%3F&quot; tabindex=&quot;-1&quot;&gt;What are they good for?&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/dont-ignore-fieldset-and-legend-html-elements/#what-are-they-good-for%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What are they good for?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Now let’s have a look at why using &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; is a good idea. Nothing will change for normal users using their sight and mouse to navigate on the screen. But it’s a big help for users with a screen reader. The screen reader will announce the fieldset as a group of elements together with its name defined in &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt;. This is great as it gives users additional context about the upcoming fields (is it delivery or billing address). Some screen readers (like JAWS) will even read the legend before each input in the group.&lt;/p&gt;
&lt;p&gt;You may argue that you can simply add some text before the inputs (like heading) and give the user the same information. That’s not entirely true. First, it may confuse the user as there is no clear connection between the group of form elements and the text you provided. Second, some screen readers have a special mode to work with the forms and in this mode, non-form elements may be ignored (so your heading will not work).&lt;/p&gt;
&lt;p&gt;Fieldset has one more great benefit. You can simply put the disabled attribute on it and it will disable all the input fields in it. So no extra delivery address? Sure just put one disabled there and you are done. Isn’t that great? &lt;s&gt;And by the way, you can use this trick on the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element itself to disable the entire form&lt;/s&gt; (&lt;strong&gt;EDIT:&lt;/strong&gt; This is not a true as I checked based on some comments. &lt;code&gt;disabled&lt;/code&gt; does not work on &lt;code&gt;form&lt;/code&gt; like this. I’m sorry for confusion.).&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;some-downsides&quot; tabindex=&quot;-1&quot;&gt;Some downsides&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/dont-ignore-fieldset-and-legend-html-elements/#some-downsides&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Some downsides”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;As with most things in life, nothing is always perfect.&lt;/p&gt;
&lt;p&gt;Not all screen readers will announce the fieldset. Some will ignore it and read only a text contained in a &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; as a normal text. This also depends on the browser as I have found. For example, VoiceOver (native macOS screen reader) will announce the fieldset correctly in Chrome, but not in Firefox.&lt;/p&gt;
&lt;aside&gt;It seems this should be addressed in Firefox 76. Mozilla is aware of compatibility issues with Firefox and VoiceOver and is working on them. You can see &lt;a href=&quot;https://wiki.mozilla.org/Accessibility/Mac2020#Basic_VoiceOver_Support&quot; rel=&quot;noopener&quot;&gt;this page for a full overview&lt;/a&gt;&lt;/aside&gt;
&lt;p&gt;Styling of &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; also takes a little bit more effort, than using headings or some text element. But that shouldn’t be an excuse for not using them.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/dont-ignore-fieldset-and-legend-html-elements/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Don’t be afraid of fieldset and legend tags. For very little effort, they help you structure your form and give users extra context about the purpose of the form fields. And as front-end developers, we should always aim for a more accessible solution.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Tools &amp; resources I use for front-end development</title>
      <link>https://pustelto.com/blog/tools-i-use/</link>
      <pubDate>Mon, 13 Apr 2020 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/tools-i-use/</guid>
      <description>&lt;p&gt;Front-end development is quite a complex discipline and you will hardly ever need only a browser and code editor (at least for bigger projects). I have decided to post a list of the tools I use for development. I hope this will help others to find some great tools they can use in their workflow.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;basic-stuff&quot; tabindex=&quot;-1&quot;&gt;Basic stuff&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/tools-i-use/#basic-stuff&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Basic stuff”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Those are the tools I use daily and I couldn’t literary produce much of my work without them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt; - You need to write your code somewhere. My editor of choice is VS Code from Microsoft. It’s free, lightweight (compared to IDEs like WebStorm), has a ton of functionality out of the box and extensions which can give it even more powers.&lt;/li&gt;
&lt;li&gt;Browsers - I use Firefox as my default browser, sometimes switching to Chrome for development. But I have installed other browsers for testing as well.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devdocs.io/&quot;&gt;DevDocs&lt;/a&gt; - Web app which aggregates documentation from different projects. It can also work in offline mode. This my go-to page when I need to look for some documentation.&lt;br /&gt;
Using it mainly for JS and DOM/Browser related stuff. As I really couldn’t find anything similar (easy to search and detailed). You should check it out.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://google.com/&quot;&gt;Google&lt;/a&gt; - Yes, I google things very often. Bugs, how to do stuff (sometimes pretty basic) or examples and documentations for packages and libraries (if they aren’t on DevDocs).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US&quot;&gt;MDN&lt;/a&gt; - Greate resource for any web developer. Part of this site can be accessed via DevDocs mentioned earlier, but some pages are only on MDN.&lt;br /&gt;
I use it mainly when I need to check some &lt;abbr title=&quot;accessibility&quot;&gt;a11y&lt;/abbr&gt; related stuff as they have there some good articles about the topic.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/&quot;&gt;StackOverflow&lt;/a&gt; - Usually get there from Google. If you have some problem, you can probably find a solution there.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/&quot;&gt;Github&lt;/a&gt; - When I have some issues with a package or just need to know more about it I dig into the source code or issues. You can find here answers to your problems almost as often as on StackOverflow. I’m personally more successful with Github issues if I have problems related to one package (unless it’s React or something similar hugely popular). And of course, I use it for version control as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;css&quot; tabindex=&quot;-1&quot;&gt;CSS&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/tools-i-use/#css&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “CSS”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/&quot;&gt;CSS-Tricks&lt;/a&gt; - You can find a ton of interesting articles and tips on their blog. There is also a Guides section with in-depth articles about certain HTML/CSS and JS concepts. I usually go there if I need to refresh my Grid knowledge. But other guides are great as well, so definitely worth checking it out.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://tympanus.net/codrops/css_reference&quot;&gt;CSS Reference&lt;/a&gt; - If I need to refresh some of my CSS knowledge or use some property I do not know/use very often, this is my go-to resource. It contains an in-depth explanation of every CSS property with clear examples so it’s easy to understand and use in your project. Written by excellent &lt;a href=&quot;https://www.sarasoueidan.com/&quot;&gt;Sara Soudain&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caniuse.com/&quot;&gt;Can I Use&lt;/a&gt; - A must-have tool if you care about browser support and using cutting edge features responsibly (aka progressive enhancement).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;performance-and-optimizations-tools&quot; tabindex=&quot;-1&quot;&gt;Performance and optimizations tools&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/tools-i-use/#performance-and-optimizations-tools&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Performance and optimizations tools”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jakearchibald.github.io/svgomg&quot;&gt;SVGOMG&lt;/a&gt; - You will rarely get SVG from designer optimized for the web. In such cases, I will use SVGOMG to optimize and minify the SVG. You would be surprised how much of useless stuff you can get rid of. It’s web GUI for &lt;a href=&quot;https://github.com/svg/svgo&quot;&gt;SVGO&lt;/a&gt;, so if you can get the same results from CLI if you want to.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shrinkme.app/&quot;&gt;Shrinkme.app&lt;/a&gt; - Web app when I need to quickly optimize images. It supports batch uploads and provides very good results out of the box.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://squoosh.app/&quot;&gt;Sqoosh&lt;/a&gt; - When I need to squeeze every last drop of performance from images or need to create images in webp format. A lot of options to play with to get the results you need.&lt;br /&gt;
You can also resize images and convert them to different formats. It’s fairly cutting edge (it was done as a demo of what modern browsers can do by Google Chrome team) so you have to use Chrome or another Chromium-based browser (Opera, Brave, etc.). It didn’t work properly in Firefox last time I test it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://icomoon.io/app/#/select&quot;&gt;Icomoon app&lt;/a&gt; - My go-to tool when I need to create a custom icon set. You can choose from existing icons (free or paid) or upload your own. Then you can either use that to generate icon font or SVG icon set (which is a better solution these days).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fonts.google.com/&quot;&gt;Google Fonts&lt;/a&gt; - When I want some custom web fonts, this is usually my first stop. I usually download the files I need and self-host it myself (for better performance). What is great is that you can select only encoding you need and avoid downloading unnecessary characters.&lt;br /&gt;
&lt;strong&gt;NOTE:&lt;/strong&gt; You could select encoding on the old Google Fonts page, but I can’t fount it anywhere on their new site. If you know where it is (or if it is there at all), let me know, please.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/filamentgroup/glyphhanger&quot;&gt;Glyphhanger&lt;/a&gt; - Decreasing file size is one of the best ways how to improve the performance of the website. Web fonts often use many glyphs we do not need so I use Glyphhanger for font subsetting.&lt;br /&gt;
You can specify Unicode ranges or characters you want in the font and Glyphanger will create a new font file for you with only specified characters. It can also convert &lt;code&gt;.ttf&lt;/code&gt; files to other formats much more suitable for the web. like &lt;code&gt;.woff&lt;/code&gt; and &lt;code&gt;.woff2&lt;/code&gt;. Take a bit of effort to use it right, but I think it’s an option worth considering when font performance is an issue.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChrome/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; - I test the webpages before releasing them and one of the tools I use most often is Lighthouse audit in Chrome browser. It checks for the most common issues in several areas and gives you score for each area as well as hints on how to improve. It’s great as a first check to see if there are any problems I forgot to address during development.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://meowni.ca/font-style-matcher&quot;&gt;Font style matcher&lt;/a&gt; - If you want to make font swap less noticeable after your custom fonts are loaded you can use this great tool to match the styles of your default and custom fonts.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;accessibility&quot; tabindex=&quot;-1&quot;&gt;Accessibility&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/tools-i-use/#accessibility&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Accessibility”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;VoiceOver - default screen reader on macOS. It takes little practice to use it (I found &lt;a href=&quot;https://webaim.org/articles/voiceover&quot;&gt;this article about it&lt;/a&gt; very useful), but I try to regularly use it in the development process. Thanks to that my usage of aria attributes and screen-reader only text has increased significantly. You would be surprised how very little context and information some ordinary webpage components have for the user depending on a screen reader.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://a11yproject.com/patterns&quot;&gt;&lt;abbr title=&quot;accessibility&quot;&gt;a11y&lt;/abbr&gt; guidelines&lt;/a&gt; - I usually try to find some existing solution with good &lt;abbr title=&quot;accessibility&quot;&gt;a11y&lt;/abbr&gt; but that is not always possible. In that case, if I need to create some interactive component with accessibility in mind, I will go to this page. You can find a detailed explanation of what, why and how. Together with example codes, you can often copy and reuse with a little effort.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/wai-aria-1.1&quot;&gt;WAI-ARIA specification&lt;/a&gt; - I don’t ready specifications very often. But when I do, it’s usually this one. It contains a lot of valuable information about aria roles and attributes and how to use them. I would say this is mandatory reading for anyone who takes &lt;abbr title=&quot;accessibility&quot;&gt;a11y&lt;/abbr&gt; seriously&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;honorable-mentions&quot; tabindex=&quot;-1&quot;&gt;Honorable mentions&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/tools-i-use/#honorable-mentions&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Honorable mentions”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;And last but not least there is a list of other tools I found useful, but not using them that often.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://responsivebreakpoints.com/&quot;&gt;Responsive breakpoints generator&lt;/a&gt; - Manually creating responsive images with a lot of variants is a pain. This is a great tool to lessen that pain.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.toptal.com/designers/htmlarrows&quot;&gt;HTML Arrows&lt;/a&gt; - List of special characters together with Unicode code (in different contexts like CSS, JS, plain Unicode) and HTML entity to use them safely on a web. I use it now and then when I want to have good typography.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.w3.org/html5/html-author/charref&quot;&gt;Char reference&lt;/a&gt; - Similar to the tool above, but contains less information. It shows mainly HTML entities, so if that is what you need this may be a better option.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typewolf.com/cheatsheet&quot;&gt;Typography cheatsheet&lt;/a&gt; - Explains common typography rules and issues. If you want to improve your typesetting on the web, this is a good starting point.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.modularscale.com/&quot;&gt;Modular scale&lt;/a&gt; - When I work on a design I usually pick some modular scale using this tool. It helps you create a harmonical vertical rhythm. To learn more about it I recommend reading &lt;a href=&quot;https://alistapart.com/article/more-meaningful-typography/&quot;&gt;this article on A List Appart&lt;/a&gt; or this &lt;a href=&quot;https://vimeo.com/17079380&quot;&gt;recording of Tim Brown talk&lt;/a&gt; from Build Conf 2010.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cssgradient.io/&quot;&gt;CSS gradient generetor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://brumm.af/shadows&quot;&gt;Smooth shadow generator&lt;/a&gt; - When you want to have great looking shadows on your web.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cubic-bezier.com/&quot;&gt;Bezier curve generator&lt;/a&gt; - Custom timing function for your animation&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://easings.net/en&quot;&gt;Easing functions&lt;/a&gt; - Library of different easing functions ready to use in your CSS animations and transitions.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt; - I use it when I need to show some of my work running on localhost to someone else or if I need to test on different devices. Sometimes using it with Browserstack when their localhost extension refuses to work.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sharingbuttons.io/&quot;&gt;sharing buttons&lt;/a&gt; - This is a jewell if you need simple sharing buttons without all the JS and tracking mess around them.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dencode.com/en/date&quot;&gt;Unix timestamp converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://css2js.dotenv.dev/&quot;&gt;CSS to JS converter&lt;/a&gt; - This may be handy when you work with CSS-in-JS library and need to convert from CSS to JS or vice versa.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://browserdefaultstyles.com/&quot;&gt;Browser default styles&lt;/a&gt; - List of default styles for common browsers.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;the-end&quot; tabindex=&quot;-1&quot;&gt;The end&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/tools-i-use/#the-end&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “The end”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;That’s it. Those are the tools I used for my work and which help me to be productive and provide high-quality work. I hope you will find some of them useful. If yes please let me know on Twitter. Or if you have some other great suggestions I would love to hear about them as well.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>What have I learned when releasing Qjub</title>
      <link>https://pustelto.com/blog/what-have-i-learned-when-releasing-qjub/</link>
      <pubDate>Fri, 05 Jun 2020 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/what-have-i-learned-when-releasing-qjub/</guid>
      <description>&lt;p&gt;About two weeks ago, I have finally released &lt;a href=&quot;https://qjub.app/&quot;&gt;Qjub&lt;/a&gt; in private beta. It was a great feeling to finally kick it off the door. And a big relief as well. However, getting to that moment was not an easy task. This week I had a little time to reflect on that journey. And I would like to share my experience and the lessons I learned with you.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;tldr---lessons-i-have-learned&quot; tabindex=&quot;-1&quot;&gt;TLDR - Lessons I have learned&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-have-i-learned-when-releasing-qjub/#tldr---lessons-i-have-learned&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “TLDR - Lessons I have learned”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;For those who do not have time or do not want to read the entire article:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Steady small progress &amp;gt; occasional big chunks of work - it keeps momentum and eventually may give you more time.&lt;/li&gt;
&lt;li&gt;Set fixed deadlines and stick with them as much as you can.&lt;/li&gt;
&lt;li&gt;Cut unnecessary things and do not be afraid to release unpolished features or apps.&lt;/li&gt;
&lt;li&gt;Releasing is hard - you had to check and prepare so many little things which are often important, that it will take you a huge chunk of your time. Take it into account when planning the initial launch of your project.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;full-story&quot; tabindex=&quot;-1&quot;&gt;Full story&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-have-i-learned-when-releasing-qjub/#full-story&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Full story”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Let’s start with a little background. When I start my current job I had to learn a lot of new stuff and I wanted to get better. So I start building Qjub. To learn in-depth all the new stuff I used in my work and to build something to help myself.&lt;/p&gt;
&lt;p&gt;You see, I tend to collect a lot of resources across the web. Web development tips, books I want to read, cooking recipes, and design inspiration. But I had always had those things at different locations. Finding that one thing I was looking for was difficult. None of the products I tried worked for me, so I decided to build my own solution. Building Qjub was like to kill to birds with one stone.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;consistent-work-on-stuff-that-matters&quot; tabindex=&quot;-1&quot;&gt;Consistent work on stuff that matters&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-have-i-learned-when-releasing-qjub/#consistent-work-on-stuff-that-matters&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Consistent work on stuff that matters”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;While Qjub was slowly taking shape, it was still mainly a learning project without any pressure to release it. Somewhere along the way, I decided to go all-in and make Qjub available to everyone as an app. With the eventual prospect of getting some income from it (and ideally building it full time).&lt;/p&gt;
&lt;p&gt;At that time I had like a million features I wanted to be there, some more reasonable than the others. But still too much to ever get close to release. I start by splitting the development into several milestones and assigning then a reasonable amount of tasks. Of course, I had to prioritize and make some sort of minimum viable product (aka MVP) to be able to release.&lt;/p&gt;
&lt;p&gt;I also mostly work on Qjub during free weekends. But because of children and other activities, those times were rare (sometimes I did not touch code for several months). Inspired by some podcasts and books I was reading I decide to make a change. I have started to work on Qjub each morning before I go to work for 1 hour every day. It may seem like very little time but it gave me an incredible boost in productivity and Qjub started to move forward like never before. I can confirm what many other awesome people say - working in small chunks regularly adds up, builds momentum, and eventually allow you to build much more.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I decide to work on Qjub each morning before I go to work for 1 hour. It may seem like very little time but it gave me an incredible boost in productivity and Qjub started to move forward like never before.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A few months later Qjub was looking good but I still couldn’t see the end of it. I sit down and think really hard about the Qjub. What do I want it to do? What is needed for release and what can wait? This has lead to two thinks. First I drastically cut down the scope of the project for initial release. Second I set a fixed release date.&lt;/p&gt;
&lt;p&gt;Because of the fixed date, I had to keep asking what is important and what is not. I cut the scope a few more times during the remaining time to meet the deadline. This forced me to make some hard decisions and compromises. For example, I decided to postpone search functionality and board sharing. Or keep some stuff unpolished, intentionally limiting its scope and capabilities (eg. you can’t insert images to text notes at the moment). I can always make those things later if needed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is always some feature you can cut down or limit its scope to meet the deadline and focus on stuff that matters.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let’s take a closer look at the search feature and my reasoning behind not implementing it at the beginning. My reasoning behind it was that no one will need search functionality at the beginning because there will most likely be very little content. So I can build a search later before anyone needs it.&lt;/p&gt;
&lt;p&gt;This is the approach I try to use for everything in Qjub since then. Do I need this feature now? How much value does it give me? How much effort does it cost me? Building the Qjub myself, I have to think hard about my time and how to use it efficiently.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;releasing-it&quot; tabindex=&quot;-1&quot;&gt;Releasing it&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-have-i-learned-when-releasing-qjub/#releasing-it&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Releasing it”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Finally, my release day approach, and I found out I’m still not ready for release. Yeah, I missed my fixed deadline, even when I tried my best and have huge support from my wife, who took care of our home and children herself so I could focus on Qjub. That was disappointing for me. But I pushed hard and finally make release two weeks later. Turns our releasing an app is more difficult than building it (at least for me). There are tons of small things to check that you can easily spend another week with just that.&lt;/p&gt;
&lt;p&gt;So when you decide to release your next big thing, take into account that the release takes some time as well.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;moving-forward&quot; tabindex=&quot;-1&quot;&gt;Moving forward&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-have-i-learned-when-releasing-qjub/#moving-forward&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Moving forward”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The release of the Qjub is just a start. I plan to work on it to better suit my needs and keep my information organized and easily reachable. I have already thought about my next steps and release the &lt;a href=&quot;https://trello.com/b/hIX23oVP/qjub-public-roadmap&quot;&gt;public roadmap&lt;/a&gt;. I also plan to write more about my journey on this blog.&lt;/p&gt;
&lt;p&gt;So stay tuned and if you found Qjub interesting give it a shot. Go to &lt;a href=&quot;https://qjub.app/#request-access&quot;&gt;Qjub homepage&lt;/a&gt; and request access to the private beta. I will appreciate any feedback and suggestions.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Optimizing CSS for faster page loads</title>
      <link>https://pustelto.com/blog/optimizing-css-for-faster-page-loads/</link>
      <pubDate>Sun, 02 Aug 2020 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/optimizing-css-for-faster-page-loads/</guid>
      <description>&lt;p&gt;Not long ago I decided to improve the loading times of my website. It already loads pretty fast, but I knew there was still room for improvement and one of them was CSS loading. I will walk you through the process and show you how you can improve your load times as well.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;why-loading-time-matters%3F&quot; tabindex=&quot;-1&quot;&gt;Why loading time matters?&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#why-loading-time-matters%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Why loading time matters?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Because &lt;em&gt;&lt;strong&gt;Time is money&lt;/strong&gt;&lt;/em&gt;. That proverb is especially true for webpage load times. Your page load time has a direct impact on your profit. People are more likely to buy something on a fast e-shop than on the slow one. According to study &lt;a href=&quot;https://www2.deloitte.com/ie/en/pages/consulting/articles/milliseconds-make-millions.html&quot;&gt;Milliseconds make millions&lt;/a&gt; improvement by 0.1s on mobile site increased conversions by 10.1%  and order value by 1.9% on Travel sites. That can be a lot of money.&lt;/p&gt;
&lt;p&gt;So if you want to build a profitable business, you shouldn’t underestimate your page load times.&lt;/p&gt;
&lt;aside&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; There is more studies confirming this pattern. I used an example from the study mentioned above because it&#39;s the most recent one I could find.&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;how-does-css-affect-load-times&quot; tabindex=&quot;-1&quot;&gt;How does CSS affect load times&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#how-does-css-affect-load-times&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “How does CSS affect load times”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;To see how CSS affects the load time of a webpage we first have to know how the browser converts an HTML document into a functional webpage.&lt;/p&gt;
&lt;p&gt;First, it has to download an HTML document and parse it to create DOM (Document Object Model). Any time it encounters any external resource (CSS, JS, images, etc.) it will assign it a download priority and initiate its download. Priorities are important because some resources are critical to render a page (eg. main stylesheet and JS files) while others may be less important (like images or stylesheets for other media types).&lt;/p&gt;
&lt;aside&gt;HTTP/1.1 has also a hard limit on the number of connections per one domain (the exact number depends on the browser, it&#39;s usually 6 these days). So if you want to download a large number of resources from one domain some of them have to wait in a queue until resources with higher priorities finish downloading. So keep the number of requests small when using HTTP/1.1. HTTP/2 doesn&#39;t have this limitation, but not all sites are using HTTP/2 so far.&lt;/aside&gt;
&lt;p&gt;In the case of CSS, this priority is usually high because stylesheets are necessary to create CSSOM (CSS Object Model). To render a webpage browser has to construct both DOM and CSSOM. Without those browser will not render any pixels on the screen. The reason for this is that styles define the look of the page and rendering page first without them would be a waste of processing powers and bad user experience. Only when the browser has both DOM and CSSOM available it can create render tree by combining them and start rendering the screen. In short no CSS downloaded, no page rendered.&lt;/p&gt;
&lt;p&gt;As you can see CSS has a huge impact on the load time of your webpage. There are two basic areas affecting webpage load time when we talk about CSS:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CSS file size and the total amount of CSS on the page (number of files). Too large CSS files will take a longer time to download and thus the entire page will take much more time to render (it has to wait for that big CSS to download first).&lt;/li&gt;
&lt;li&gt;When and how we initiate and download our CSS. You want to download your styles as soon as possible.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s see in detail how we can improve those.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;limit-size-of-your-stylesheet&quot; tabindex=&quot;-1&quot;&gt;Limit size of your stylesheet&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#limit-size-of-your-stylesheet&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Limit size of your stylesheet”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Configure your tools correctly to use modern code whenever possible.&lt;/p&gt;
&lt;p&gt;If you want to faster load times, making your CSS files smaller is a good idea. These days it’s pretty common to use some tool to modify the CSS on build time (either post processor or &lt;a href=&quot;https://postcss.org/&quot;&gt;PostCSS&lt;/a&gt;) to provide fallbacks for older browsers or some other enhancements.&lt;/p&gt;
&lt;p&gt;I would suggest checking the result code for unnecessary bloat. Especially if you are using PostCSS with multiple plugins. In my case, I had CSS with generated fallbacks for CSS variables and with prefixes for older flexbox syntax. That may seem like a trivial issue with very little effect, but resulting savings were around 3 kB for small stylesheet like mine. I think that is a great improvement for very little work. And for large CSS it has the potential to have an even bigger impact.&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;old index.css:  12.5kB (without GZip)&lt;br /&gt;new index.css:   9.2kB (without GZip, ~26.4% smaller)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All I had to do was to update a &lt;a href=&quot;https://github.com/browserslist/browserslist&quot;&gt;browserslist&lt;/a&gt; config which is used by Autoprefixer and other similar tools to target generated code for specific browser versions. I have updated my PostCSS config a bit as well. (I also added the plugin to concatenate media queries together to save some extra space). See the &lt;a href=&quot;https://github.com/Pustelto/personal_web/blob/master/src/styles/index.11ty.js&quot;&gt;PostCSS config in the source code&lt;/a&gt; and &lt;a href=&quot;https://github.com/Pustelto/personal_web/blob/master/package.json#L47&quot;&gt;my browserslist definition&lt;/a&gt; if you want to see my exact setup.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;use-critical-css&quot; tabindex=&quot;-1&quot;&gt;Use critical CSS&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#use-critical-css&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Use critical CSS”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;So we shrank our CSS file, but we still need to download it. We can speed up the webpage load time by reducing network requests. And best network requests are no requests at all. We can inline our styles directly into the HTML to avoid the need for downloading any external stylesheets and thus saving some time.&lt;/p&gt;
&lt;p&gt;Of course, including an entire 9kb stylesheet (or large for bigger projects) on every page is not very effective. So we will include only the styles necessary to render the part of the page &lt;em&gt;above the fold&lt;/em&gt; and lazy-load the rest of the styles. That way we can still leverage browser caching for other pages and make our webpage load faster. Since we include styles that are critical for page rendering this technique is called &lt;em&gt;Critical CSS&lt;/em&gt;.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-640.avif 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-960.avif 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-640.webp 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-960.webp 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-640.jpeg 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-960.jpeg 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Above the fold is the content visible right away after page loads. Content not visible without scrolling is below a fold.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/AMDWk49SQ2-320.jpeg&quot; width=&quot;320&quot; height=&quot;241&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;Luckily you don’t have to decide what styles should be included in the HTML. Some tools will do it for you, like &lt;a href=&quot;https://github.com/addyosmani/critical&quot;&gt;Critical&lt;/a&gt; from Addy Osmani. Please keep in mind this technique is about compromises. You need to find the right balance between what to include and the size of the CSS since this technique will save you one request when loading page but it also makes each page bigger (and thus makes it longer to download). So you want to experiment with this and measure the results to find the best setup for your site.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;lazy-load-stylesheets&quot; tabindex=&quot;-1&quot;&gt;Lazy-load stylesheets&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#lazy-load-stylesheets&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Lazy-load stylesheets”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Since we use Critical CSS we want to lazy-load our stylesheets to avoid blocking the render of the page. Unless you need to support some old browsers, modern solution these days is using normal link tag you use for stylesheets but with different media type and a little bit of JS. This clever little trick is fully described in the &lt;a href=&quot;https://www.filamentgroup.com/lab/load-css-simpler/&quot;&gt;Filament Group blog post&lt;/a&gt;. Below you can see the snippet for lazy-loading CSS from the post, but I suggest reading the entire thing.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/path/to/my.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;print&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;onload&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value javascript language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;media&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;all&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;aside&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; If you use Critical package from above, it transforms your stylesheet to be lazy loaded like that automatically.&lt;/aside&gt;
&lt;p&gt;You may want to include fallback when JS is disabled. That way your styles will load normally and you will avoid unstyled content which would badly affect user experience.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/path/to/my.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;print&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;onload&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value javascript language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;media&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;all&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;noscript&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/path/to/my.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;noscript&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the waterfall diagrams below you can see that page with critical CSS starts rendering right away (violet portion of the graph in &lt;em&gt;Browser main thread&lt;/em&gt; row) and is interactive much sooner compared to the old version where CSS file has to be downloaded first.&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-640.avif 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-960.avif 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-640.webp 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-960.webp 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-640.jpeg 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-960.jpeg 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Page starts rendering after 3.6 seconds and is interactive after 3.8 seconds.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/j_0hrh7WcI-320.jpeg&quot; width=&quot;320&quot; height=&quot;108&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Waterfall chart for projects page &lt;strong&gt;without&lt;/strong&gt; critical CSS.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-640.avif 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-960.avif 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-640.webp 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-960.webp 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-640.jpeg 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-960.jpeg 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Page starts rendering after 3.2 seconds and is interactive after 3.3 seconds.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/yS-zKowlRf-320.jpeg&quot; width=&quot;320&quot; height=&quot;108&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Waterfall chart for projects page &lt;strong&gt;with&lt;/strong&gt; critical CSS.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;use-code-splitting-for-your-stylesheets&quot; tabindex=&quot;-1&quot;&gt;Use code-splitting for your stylesheets&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#use-code-splitting-for-your-stylesheets&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Use code-splitting for your stylesheets”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;We have CSS with properties we need for modern browsers and we use critical CSS and lazy-load the rest. But we can probably decrease our file size a bit more. In Chrome dev tools there is a tool called Coverage. It can show you what portion of CSS and JS files is used on the current page. Open dev tools and press &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;p&lt;/kbd&gt; to open a command pallet and type &lt;em&gt;Coverage&lt;/em&gt;. Select &lt;em&gt;Show coverage&lt;/em&gt; option to show the panel. Now reload the page.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-640.avif 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-640.webp 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-640.jpeg 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Coverage report for my home page before any optimizations, over 45% of the CSS is not used on the page.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/lyfhSjLGcI-320.jpeg&quot; width=&quot;320&quot; height=&quot;66&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-640.avif 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-640.webp 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-640.jpeg 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Coverage report for my projects page before any optimizations, over 53% of the CSS is not used on the page.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/LrApveWPaM-320.jpeg&quot; width=&quot;320&quot; height=&quot;57&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;I had almost 50% of my CSS code unused on the page. When we check another page we get even more – almost 54% of unused CSS. That’s a lot of unnecessary code. And this number can be even bigger on large legacy apps.&lt;/p&gt;
&lt;p&gt;When using JS we often use code-splitting to create multiple smaller files (bundles). We download those bundles when needed them instead of fetching one large JS bundle on page load. We can use a similar approach for CSS as well. We can split our CSS in three different ways.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;split-css-based-on-media-queries&quot; tabindex=&quot;-1&quot;&gt;Split CSS based on media queries&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#split-css-based-on-media-queries&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Split CSS based on media queries”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;In this approach, you split your big CSS into smaller stylesheets based on your media queries (PostCSS have &lt;a href=&quot;https://github.com/hail2u/node-css-mqpacker&quot;&gt;plugin&lt;/a&gt; for that) and reference those stylesheets in your HTML.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;index.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;all&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mobile.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(max-width:44.9375rem)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;table.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(min-width: 45rem)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Be aware that this approach doesn’t make much sense when using Critical CSS and lazy-loading of the stylesheet. The browser will download all stylesheets no matter what media query is used. It will only use media attribute to prioritize the downloads. So basically it will download CSS with a high priority for active media query and lazy-load the rest of the stylesheets.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;page-based-code-splitting&quot; tabindex=&quot;-1&quot;&gt;Page based code-splitting&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#page-based-code-splitting&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Page based code-splitting”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Another approach is to use separate CSS for each page. As we have seen above there is a lot of unused styles for different pages. It would be great if we could remove those unused styles and keep only what is necessary for a given page. This is what I choose to do. Sadly I couldn’t find any tool to do this — take one large CSS file and generate a smaller bundle for each page based on its content.&lt;/p&gt;
&lt;p&gt;Sounds fairly simple so I decided to give it a shot and build a node script which can do this kind of thing. It’s called &lt;a href=&quot;https://github.com/Pustelto/css-split&quot;&gt;CSS Split&lt;/a&gt; and it works great for sites built using static site generator (like &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; which I use for my site). It uses &lt;a href=&quot;https://purgecss.com/&quot;&gt;PurgeCSS&lt;/a&gt; to remove unused styles so it should work on other non-HTML files as well (based on their documentation). I didn’t test it for anything else than HTML so when using it this way, be sure to double-check the results.&lt;/p&gt;
&lt;p&gt;Using this technique I was able to reduce the file size of requested CSS by almost 50%. Below are some stats after implementing Critical CSS and page based code-splitting:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;single index.css for all pages:      9.2kB (without GZip)&lt;br /&gt;CSS file for homepage:               5.4kB (without GZip)&lt;br /&gt;CSS file for projects:               4.4kB (without GZip)&lt;/code&gt;&lt;/pre&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--640.avif 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--640.webp 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--640.jpeg 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Coverage report for my home page after code-splitting, only around 400 bytes are unused.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/leiVBUl1K--320.jpeg&quot; width=&quot;320&quot; height=&quot;61&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-640.avif 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-640.webp 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-640.jpeg 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Coverage report for my projects page after code-splitting, only around 400 bytes are unused.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/dnMz_91abG-320.jpeg&quot; width=&quot;320&quot; height=&quot;65&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;You can see that there are still some unused bytes. That’s ok as Coverage doesn’t include hover or focus states or queries. It is unlikely that you will ever get unused bytes to 0.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;component-based-code-splitting&quot; tabindex=&quot;-1&quot;&gt;Component based code-splitting&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#component-based-code-splitting&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Component based code-splitting”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I’ve got this tip from &lt;a href=&quot;https://csswizardry.com/&quot;&gt;Harry Roberts&lt;/a&gt;. We can also split CSS on the component basis and only load progressively CSS for components we use on the page (footer, header, article, etc.). You can read more about this neat trick in &lt;a href=&quot;https://csswizardry.com/2018/11/css-and-network-performance/&quot;&gt;Harry’s article&lt;/a&gt;. This technique I’m talking about is described in the last section of the article. But read the entire article, it’s full of great info about improving CSS network performance I don’t cover here (couldn’t write it better anyway).&lt;/p&gt;
&lt;p&gt;I still didn’t test this technique to see how well it will work compared to my current setup, but it’s on my To-do list. So stay tuned for some future article.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/#summary&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Summary”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Even though my site is fairly simple and doesn’t have too much room for improvements, by using the techniques mentioned there I was able to speed up the initial load of my webpage and lower the total size of assets. You can use the same process for any webpage to improve it’s loading performance (probably with better results for larger projects).&lt;/p&gt;
&lt;p&gt;Below you can see some final results after the updates. Graphs show what percentage of the page was rendered at what time. Those tests were run on a slow 3G connection, that’s why it takes so long to load the page.&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-640.avif 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-960.avif 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-640.webp 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-960.webp 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-640.jpeg 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-960.jpeg 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Graph comparing time to fully render homepage before and after optimizations. Homepage loads around 0.2 second faster after optimizations.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/eDcgtHbXLG-320.jpeg&quot; width=&quot;320&quot; height=&quot;142&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Homepage &amp;ndash; final comparison&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-640.avif 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-960.avif 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-640.webp 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-960.webp 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-640.jpeg 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-960.jpeg 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Graph comparing time to fully render projects page before and after optimizations. Page loads around 0.5 second faster after optimizations.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/vIzdkqfO1T-320.jpeg&quot; width=&quot;320&quot; height=&quot;142&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Projects page &amp;ndash; final comparison&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-320.avif 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-480.avif 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-640.avif 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-960.avif 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-320.webp 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-480.webp 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-640.webp 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-960.webp 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-320.jpeg 320w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-480.jpeg 480w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-640.jpeg 640w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-960.jpeg 960w, https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Graph comparing time to fully render single blog post before and after optimizations. Page loads around 0.6 second faster after optimizations.&quot; src=&quot;https://pustelto.com/blog/optimizing-css-for-faster-page-loads/q_e8989F1A-320.jpeg&quot; width=&quot;320&quot; height=&quot;142&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Single article &amp;ndash; final comparison&lt;/figcaption&gt;&lt;/figure&gt;
</description>
    </item>
    
    <item>
      <title>What I read to stay up-to-date</title>
      <link>https://pustelto.com/blog/what-i-read-to-stay-up-to-date/</link>
      <pubDate>Thu, 17 Sep 2020 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/what-i-read-to-stay-up-to-date/</guid>
      <description>&lt;p&gt;Some time ago I have published a post about &lt;a href=&quot;https://pustelto.com/blog/tools-i-use/&quot;&gt;tools I use in front-end development&lt;/a&gt;, and it had a bigger response than I expected (in a positive manner 😊). And quite a few people ask me how do I stay up-to-date with modern web dev. So I decided to share another list. This time it’s about blogs and other resources I watch and read to keep myself up-to-date with ever-evolving web development.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-is-my-workflow&quot; tabindex=&quot;-1&quot;&gt;What is my workflow&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-i-read-to-stay-up-to-date/#what-is-my-workflow&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What is my workflow”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I use &lt;a href=&quot;https://feedly.com/&quot;&gt;Feedly&lt;/a&gt; to aggregate RSS feeds from the blogs listed below. Many people seem surprised by the fact I use RSS, but honestly, it’s a great way how to keep an eye on many resources without losing time and valuable stuff. Of course, I don’t read every article published on the sites listed below. If I did, I would be reading 24/7, not exactly what I want. So I’m quite picky what to read (but still have a huge buffer).&lt;/p&gt;
&lt;p&gt;I usually keep interesting articles unread and dismiss the articles I don’t found relevant to me. Then from time to time, I open a bunch of articles and either read them if I have the time or save them to &lt;a href=&quot;https://getpocket.com/&quot;&gt;Pocket&lt;/a&gt; and read them later. I usually read them offline when I’m commuting to work. Feedly and Pocket are a great combo and work very well for me and save me a lot of time.&lt;/p&gt;
&lt;p&gt;So without any more talking, here is the list of web sites and other resources I keep an eye on (in no particular order):&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;blogs&quot; tabindex=&quot;-1&quot;&gt;Blogs&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-i-read-to-stay-up-to-date/#blogs&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Blogs”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/&quot;&gt;&lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt;-Tricks&lt;/a&gt; - great resource for a front-end related stuff. Often with links to other interesting resources. I would say the main focus is on &lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt;/HTML related stuff (no surprises), animations, SVGs, etc. But you can found articles on static-site generators, React, Vue, and much more.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/&quot;&gt;Smashing Magazine&lt;/a&gt; - a great site full of in-depth articles on design, UX/UI, front-end, and accessibility. I found especially the mix of design and development articles very valuable since I think front-end developers should have some knowledge of design principles as well.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scotch.io/&quot;&gt;Scotch.io&lt;/a&gt; - tutorial site focusing on all web dev (frontend and backend). I think it’s great for junior developers. I don’t personally read their articles too often these days, but I still found it valuable to keep it in my RSS.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.sitepoint.com/blog/&quot;&gt;SitePoint&lt;/a&gt; - another tutorial site, similar to Scotch.io. Covers backend as well. It has a wide range of topics covered and some interesting authors. You can often found articles focusing on best practices. Introductions to new technologies etc.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tympanus.net/codrops/&quot;&gt;Codrops&lt;/a&gt; - I keep an eye on this site mainly for their &lt;a href=&quot;https://tympanus.net/codrops/collective/&quot;&gt;Collective&lt;/a&gt; section. It is a basically web-based newsletter with the newest and coolest development and design stuff from around the internet. Aside from the Collective, they also post a lot of cool demos. Mainly focusing on high-quality animations and transitions. So if you looking for something fancy on your new page, this is the place to go.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;personal-sites&quot; tabindex=&quot;-1&quot;&gt;Personal sites&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-i-read-to-stay-up-to-date/#personal-sites&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Personal sites”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://csswizardry.com/&quot;&gt;&lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt; Wizardry&lt;/a&gt; - personal website of Harry Roberts. He publishes irregularly about web performance and &lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt;. His posts are always high quality and go really deep. So if you are into the &lt;abbr title=&quot;Web performance&quot;&gt;WebPerf&lt;/abbr&gt; this is one of the resources I suggest to follow.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addyosmani.com/&quot;&gt;Addy Osmani&lt;/a&gt; - Addy is Engineering Manager at Google working on Chrome. As such he mostly writes about &lt;abbr title=&quot;Web performance&quot;&gt;WebPerf&lt;/abbr&gt;, often with relation to Chrome Browser.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://adrianroselli.com/&quot;&gt;Adrian Roselli&lt;/a&gt; - Adrian writes about accessibility (&lt;abbr title=&quot;accessibility&quot;&gt;a11y&lt;/abbr&gt;) and has plenty of great articles about this topic. Definitely worth following if you are interested in this topic (and you should as a front-end dev).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wattenberger.com/&quot;&gt;Amelia Wattenberger&lt;/a&gt; - Amelia writes mostly about D3 and data visualization, but you can found articles about &lt;abbr title=&quot;Scalable Vector Graphics&quot;&gt;SVG&lt;/abbr&gt;, &lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt;, and React. What makes Amelia’s blog stand out is the high quality and interactivity of her articles. Each of them is like an interactive microsite with a lot of examples to help you understand the given topic.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.sarasoueidan.com/blog/&quot;&gt;Sara Soueidan&lt;/a&gt; - Sara has a large collection of articels about &lt;abbr title=&quot;Scalable Vector Graphics&quot;&gt;SVG&lt;/abbr&gt;, &lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt; and &lt;abbr title=&quot;accessibility&quot;&gt;a11y&lt;/abbr&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://timkadlec.com/&quot;&gt;Tim Kadlec&lt;/a&gt; - Tim is well known for is focus on &lt;abbr title=&quot;Web performance&quot;&gt;WebPerf&lt;/abbr&gt;, so you can found lots of articles about this topic on his blog.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://joshwcomeau.com/&quot;&gt;Josh Comeau&lt;/a&gt; - Josh writes about various subjects related to front-end development, like React, Gatsby, &lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt; and animations, JavaScript.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://philipwalton.com/&quot;&gt;Philip Walton&lt;/a&gt; - Phil is an engineer at Google, and his articles focus a lot on web performance (mainly fast loading of the website) and JavaScript, but you can found articles about other topics as well.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hawksworx.com/blog&quot;&gt;Phil Hawksworth&lt;/a&gt; - Phil works at Netlify in the Developer experience team, so expect his articles to focus mainly on &lt;a href=&quot;https://jamstack.org/&quot;&gt;JAMstack&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bitsofco.de/&quot;&gt;Bits of Code&lt;/a&gt; - Ire Aderinokun’s blog about front-end development. Some topics covered on the blog: &lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt;, JS, HTML and DOM, Puppeteer, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;other&quot; tabindex=&quot;-1&quot;&gt;Other&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-i-read-to-stay-up-to-date/#other&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Other”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Recently I started to follow the blogs of &lt;abbr title=&quot;World Wide Web Consortium&quot;&gt;W3C&lt;/abbr&gt; (World Wide Web Consortium) and &lt;abbr title=&quot;The Web Hypertext Application Technology Working Group&quot;&gt;WHATWG&lt;/abbr&gt; (The Web Hypertext Application Technology Working Group) to keep an overview of specification updates for HTML and &lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.whatwg.org/&quot;&gt;The &lt;abbr title=&quot;The Web Hypertext Application Technology Working Group&quot;&gt;WHATWG&lt;/abbr&gt; Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/blog/CSS/&quot;&gt;https://www.w3.org/blog/&lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt;/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;newsletters&quot; tabindex=&quot;-1&quot;&gt;Newsletters&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-i-read-to-stay-up-to-date/#newsletters&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Newsletters”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Aside from following many websites and blogs, I have also signup for plenty of newsletters (more than I like actually 😅). I have already unsubscribed from some I don’t read and I send the rest to a special folder in Gmail which I check from time to time. Below you can found a list of newsletters I still receive and check regularly. Again I do not read all the articles listed inside, but only a few which seem relevant or interested to me. My flow for newsletters is similar to the one I use for blog articles.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kentcdodds.com/&quot;&gt;Kent C. Dodds&lt;/a&gt; - Kent writes blog mostly about React and testing. I have subscribed to his newsletter to get the blog post updates sooner and to get info about his new projects or courses.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javascriptweekly.com/&quot;&gt;JavaScript Weekly&lt;/a&gt; - news from JS world, upcoming additions to JS, cool projects and tools. Tutorials and community articles.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackshare.io/&quot;&gt;StackShare Weekly&lt;/a&gt; - since I’m a bit a tooling geek, I have an account on StackShare and get their newsletter. It contains a list of new interesting tools and case studies about scaling infrastructure. Not extra relevant for a front-end dev, but if you are into DevOps, this may be an interesting option for you.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lengstorf.com/newsletter/&quot;&gt;Jason Lengstorf newsletter&lt;/a&gt; - Jason mostly writes about motivation and productivity. Apart from articles on his blog, he also sent extra stuff in the newsletter. Since I’m running my own blog and working on &lt;a href=&quot;https://qjub.app/&quot;&gt;Qjub&lt;/a&gt; in my free time, these topics are quite relevant to me (combining day job with two little kids,  blog/side-project and to have some free time for me and my wife is pretty difficult).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tinyreact.email/&quot;&gt;Tiny React&lt;/a&gt; - 3 links about React each week. Short and simple (I like those).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://esnextnews.com/&quot;&gt;ES.next news&lt;/a&gt; - latest news from JS world, what is coming to spec. New tools etc. Similar to JavaScript Weekly mentioned above. Some info is the same in both (like new spec and releases), but not all.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://calibreapp.com/newsletter&quot;&gt;Calibre’s performance newsletter&lt;/a&gt; - a newsletter about web performance and how to improve it.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;twitter&quot; tabindex=&quot;-1&quot;&gt;Twitter&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what-i-read-to-stay-up-to-date/#twitter&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Twitter”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I also follow a bunch of smart and amazing people on Twitter, so I get some gems and new ideas from here as well. But because of Twitter’s nature and my attempt to limit my time there, it’s not an extra reliable source to keep up to date with front-end dev. Never the less if you want to who I follow, check my Twitter profile (I mostly follow people whos blog I read).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>What you might not know about sizing in CSS</title>
      <link>https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/</link>
      <pubDate>Tue, 08 Dec 2020 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/</guid>
      <description>&lt;p&gt;A project I currently work on has a lot of different and sometimes non-trivial layouts. And it can be a struggle to get all the different elements sized correctly to avoid overflows and extra scrollbars. So I have deep dive into the CSS specification to learn about sizing and layout models as much as possible. And how to handle issues like this better in the future. I will share my findings with you.&lt;/p&gt;
&lt;p&gt;We will cover those topics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How percentage works for the height property&lt;/li&gt;
&lt;li&gt;Two ways how to handle stretching in Flexbox&lt;/li&gt;
&lt;li&gt;What &lt;code&gt;1fr&lt;/code&gt; really means to the browser&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;how-does-height%3A-100%25%3B-work&quot; tabindex=&quot;-1&quot;&gt;How does &lt;code&gt;height: 100%;&lt;/code&gt; work&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/#how-does-height%3A-100%25%3B-work&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “How does height: 100%; work”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Sometimes you need to set the fixed height of some element. You can use absolute units like &lt;code&gt;px&lt;/code&gt;, or relative units like &lt;code&gt;em&lt;/code&gt; or &lt;code&gt;rem&lt;/code&gt;. Those work much better in the responsive design. There is not much to talk about. You set the height to some exact number and it just works. But, when you want to use a percentage, that’s when you can encounter some trouble.&lt;/p&gt;
&lt;p&gt;A common mistake is that people set &lt;code&gt;height&lt;/code&gt; to &lt;code&gt;100%&lt;/code&gt; and think it will stretch the entire height of the parent element. But that is not always the case. Here is what the specification says about this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The percentage is calculated with respect to the height of the generated box’s containing block. If the height of the containing block is not specified explicitly (i.e., it depends on content height), and this element is not absolutely positioned, the value computes to ‘auto’.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To translate it into simple English. If the browser doesn’t know the height of a parent it can’t calculate the height of the element (you can’t calculate percentage from unknown value). In such a case it will let the content control the height instead. This often means that you have to set height across many parent nodes (eventually up to the &lt;code&gt;html&lt;/code&gt; element itself). That can lead to fragile layouts, where one change in the markup will break the whole damn thing.&lt;/p&gt;
&lt;p&gt;One cool trick to avoid this problem is the usage of CSS Grid. When you set the height of the grid item’s child using percentage it will work. For the purpose of calculating height, a grid item is considered to have explicit height. This works even for grid sizing keywords like &lt;code&gt;auto&lt;/code&gt;, &lt;code&gt;max-content&lt;/code&gt;, and &lt;code&gt;min-content&lt;/code&gt;. You can see it working in the codepen below.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;324&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;Pustelto&quot; data-slug-hash=&quot;bGexMaO&quot; data-preview=&quot;true&quot; style=&quot;height: 324px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Set column height with percentage in grid items&quot;&gt;
    &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/Pustelto/pen/bGexMaO&quot;&gt;
    Set column height with percentage in grid items&lt;/a&gt; by Tomas Pustelnik (&lt;a href=&quot;https://codepen.io/Pustelto&quot;&gt;@Pustelto&lt;/a&gt;)
    on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
   &lt;/p&gt;
   &lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;In the codepen below, you can explore how is the height with the percentage units affected by different properties.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;324&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;Pustelto&quot; data-slug-hash=&quot;YzWdpPV&quot; data-preview=&quot;true&quot; style=&quot;height: 324px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;How height in percentage works&quot;&gt;
    &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/Pustelto/pen/YzWdpPV&quot;&gt;
    How height in percentage works&lt;/a&gt; by Tomas Pustelnik (&lt;a href=&quot;https://codepen.io/Pustelto&quot;&gt;@Pustelto&lt;/a&gt;)
    on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
   &lt;/p&gt;
   &lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;If you want to know how exactly percentage units work in different properties, definitely check the amazing article from &lt;a href=&quot;https://wattenberger.com/blog/css-percents&quot;&gt;Amelia Wattenberger&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;how-sizing-in-flexbox-works&quot; tabindex=&quot;-1&quot;&gt;How sizing in Flexbox works&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/#how-sizing-in-flexbox-works&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “How sizing in Flexbox works”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Flexbox is a powerful layout module, solving a lot of common layout and alignment issues easily. But, if you have been using Flexbox for some time, you have probably encountered a situation where the flex items didn’t size exactly as you expect. Flexbox works great when you don’t care so much about the size of the items. But it can get a little tricky when you need exact sizes.&lt;/p&gt;
&lt;aside&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; I assume you have some basic knowledge of flexbox module and how to use it. If not, I would suggest checking some great materials like the &lt;a href=&quot;https://css-tricks.com/snippets/css/a-guide-to-flexbox/&quot;&gt;Flexbox guide here on CSS-Tricks&lt;/a&gt; or the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox&quot;&gt;Flexbox guide at MDN&lt;/a&gt;.&lt;/aside&gt;
&lt;p&gt;To explain how Flexbox really works we will use a simple example. We have a flex container with 3 items of different widths. If we set only &lt;code&gt;display: flex&lt;/code&gt; on parent it will look like on the image below.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-320.avif 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-480.avif 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-640.avif 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-960.avif 960w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-320.webp 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-480.webp 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-640.webp 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-960.webp 960w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-320.jpeg 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-480.jpeg 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-640.jpeg 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-960.jpeg 960w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Flexbox container with 3 items showing default browser behavior when settings display: flex on HTML element. Each item width is based on the content, items will not grow even if there is empty space in the container.&quot; src=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/B1FyQvPN1_-320.jpeg&quot; width=&quot;320&quot; height=&quot;104&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;In Flexbox,  &lt;code&gt;flex-grow&lt;/code&gt; and &lt;code&gt;flex-shrink&lt;/code&gt; properties control the shrinking and the expansion of the flex items. Those properties can be also set by shorthand &lt;code&gt;flex&lt;/code&gt; property. The default value for the browsers is &lt;code&gt;flex: 0 1 auto&lt;/code&gt;. This means that items can shrink if there is not enough space, but they will not expand to fill the container.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-320.avif 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-480.avif 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-640.avif 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-320.webp 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-480.webp 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-640.webp 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-320.jpeg 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-480.jpeg 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-640.jpeg 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Flex shorthand property can take 3 arguments. First is the value of flex-grow, second is for flex-shrink and third is for flex-basis.&quot; src=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/0pKySRG4D5-320.jpeg&quot; width=&quot;320&quot; height=&quot;108&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;When you add &lt;code&gt;flex-grow: 1&lt;/code&gt; to flex items, they will stretch to fill the entire width of the container. But the items don’t have the same size. Even though they can easily fit into the container and all of them have the same value of &lt;code&gt;flex-grow&lt;/code&gt; property. Also, the right part is a bit smaller than the left part.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-320.avif 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-480.avif 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-640.avif 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-960.avif 960w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-320.webp 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-480.webp 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-640.webp 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-960.webp 960w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-320.jpeg 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-480.jpeg 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-640.jpeg 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-960.jpeg 960w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Flex container with 3 items which have flex-grow set to 1. All items will stretch to fill the entire container.&quot; src=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/7E3vPt4qjn-320.jpeg&quot; width=&quot;320&quot; height=&quot;104&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;How is that possible? Shouldn’t all items have the same width? No, because Flexbox by default distributes the remaining empty space (hatched area in the image below). Not the entire container width, as some developers often think. Even if you set the same &lt;code&gt;flex-grow&lt;/code&gt; value to all flex items, they will not have equal widths. Unless their basic width is the same as well (set via &lt;code&gt;width&lt;/code&gt; or &lt;code&gt;flex-basis&lt;/code&gt; properties).&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-320.avif 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-480.avif 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-640.avif 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-960.avif 960w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-320.webp 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-480.webp 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-640.webp 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-960.webp 960w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-320.jpeg 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-480.jpeg 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-640.jpeg 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-960.jpeg 960w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Flex container with 3 flex items before the items will be stretched thanks to flex-grow property. Remaining free space will be distributed to the flex items based on the value of their respective flex-grow properties.&quot; src=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/ud16qns6Kc-320.jpeg&quot; width=&quot;320&quot; height=&quot;104&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;To avoid this way of space distribution you can set the &lt;code&gt;flex-basis&lt;/code&gt; value to &lt;code&gt;0&lt;/code&gt;. When the &lt;code&gt;flex-basis&lt;/code&gt; is set to any value other than &lt;code&gt;auto&lt;/code&gt;, the &lt;code&gt;width&lt;/code&gt; property of the items is ignored and browses will consider that all items have basic width equal to the given &lt;code&gt;flex-basis&lt;/code&gt;. So when you set &lt;code&gt;flex-basis: 0&lt;/code&gt; you are telling the browser that all flex items have zero width. Thus the entire flex container width will be used as a free space to distribute based on values of &lt;code&gt;flex-grow&lt;/code&gt;.&lt;/p&gt;
&lt;aside&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; While width and height are ignored when `flex-basis` is set to any value other than `auto`. Min-height and max-height properties (and their width counter parts) are still honored. This can be used for some clever CSS magic like &lt;a href=&quot;https://heydonworks.com/article/the-flexbox-holy-albatross/&quot;&gt;Holy albatros technique&lt;/a&gt;.&lt;/aside&gt;
&lt;p&gt;You can see on the schema below how flex-basis &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;auto&lt;/code&gt; work exactly (image is taken from the &lt;a href=&quot;https://www.w3.org/TR/css-flexbox-1/#flex-property&quot;&gt;specification&lt;/a&gt;).&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-320.avif 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-480.avif 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-640.avif 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-320.webp 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-480.webp 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-640.webp 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-320.jpeg 320w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-480.jpeg 480w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-640.jpeg 640w, https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Difference between space distribution when we use flex-basis: auto and flex-basis: 0. With value auto only remaining free space is distributed. With value 0, all space in the flex container is distributed based on the values of flex-grow properties defined on the flex items.&quot; src=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/2WIQFq9L6t-320.jpeg&quot; width=&quot;320&quot; height=&quot;152&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;beware-of-the-extra-padding-and-border&quot; tabindex=&quot;-1&quot;&gt;Beware of the extra padding and border&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/#beware-of-the-extra-padding-and-border&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Beware of the extra padding and border”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;One more thing to be aware of is that borders and padding on child items will increase the item’s width. And the flex items will not honor the &lt;code&gt;flex-grow&lt;/code&gt; ratio precisely (even with &lt;code&gt;flex-basis: 0&lt;/code&gt;). Here is a related part of the specification:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The hypothetical main size is the item’s flex base size clamped according to its used min and max main sizes (and flooring the content box size at zero).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Add padding or border to the child of flex item to fix this. As seen in the codepen below.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;324&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;result&quot; data-user=&quot;Pustelto&quot; data-slug-hash=&quot;QWEVmga&quot; data-preview=&quot;true&quot; style=&quot;height: 324px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;Flexbox sizing&quot;&gt;
    &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/Pustelto/pen/QWEVmga&quot;&gt;
    Flexbox sizing&lt;/a&gt; by Tomas Pustelnik (&lt;a href=&quot;https://codepen.io/Pustelto&quot;&gt;@Pustelto&lt;/a&gt;)
    on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
   &lt;/p&gt;
   &lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;large-grid-items-breaking-from-the-grid-layout&quot; tabindex=&quot;-1&quot;&gt;Large grid items breaking from the grid layout&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/#large-grid-items-breaking-from-the-grid-layout&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Large grid items breaking from the grid layout”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Imagine you have two columns layout. Sidebar with header and footer and a large list of items we want to scroll. And then the main area where we display the detail of the selected item. This layout can be usually seen in an email client. We have set the fixed height of the grid. After all, we don’t want to scroll the page itself, but the items in the sidebar or the detail in the content area. &lt;code&gt;grid-template-rows&lt;/code&gt; is set to 1fr to fill all available space.&lt;/p&gt;
&lt;p&gt;You can see the layout in codepen below.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;324&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;Pustelto&quot; data-slug-hash=&quot;KKMavPM&quot; data-preview=&quot;true&quot; style=&quot;height: 324px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-pen-title=&quot;how the sizing of fr units works&quot;&gt;
    &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/Pustelto/pen/KKMavPM&quot;&gt;
    how the sizing of fr units works&lt;/a&gt; by Tomas Pustelnik (&lt;a href=&quot;https://codepen.io/Pustelto&quot;&gt;@Pustelto&lt;/a&gt;)
    on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
   &lt;/p&gt;
   &lt;script async=&quot;&quot; src=&quot;https://static.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;But for some reason items in the sidebar are overflowing out of the grid area and the entire page is scrollable. Not exactly what we want or expect.&lt;/p&gt;
&lt;p&gt;The problem is in the &lt;code&gt;grid-template-rows&lt;/code&gt; property. When we set its size using the &lt;code&gt;fr&lt;/code&gt; unit, grid items min-size is set to &lt;code&gt;auto&lt;/code&gt; by default (according to specification). With value &lt;code&gt;auto&lt;/code&gt; grid item will resize to fit in all the content, even it is bigger than the defined grid cell.&lt;/p&gt;
&lt;p&gt;So &lt;code&gt;grid-template-rows: 1fr;&lt;/code&gt; is equal to &lt;code&gt;grid-template-rows: minmax(auto, 1fr);&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To fix this problem let’s change out the definition of grid rows to &lt;code&gt;grid-template-rows: minmax(0, 1fr);&lt;/code&gt;. With this, our extra-large &lt;code&gt;ul&lt;/code&gt; list will shrink to fit the grid defined area as we would expect. You can test it in the codepen mentioned above.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/what_you_might_not_know_about_sizing_in_css/#summary&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Summary”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;And that’s it. We have covered a few cases where the sizing doesn’t behave as we could expect at first. Hopefully, this article will help you to build layouts in CSS more easily and with less frustration.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Freezing development of Qjub</title>
      <link>https://pustelto.com/blog/freezing-qjub/</link>
      <pubDate>Sun, 10 Jan 2021 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/freezing-qjub/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; I don’t have enough time to focus on all my side-projects and activities. So I have decided to focus more on &lt;a href=&quot;https://pustelto.com/&quot;&gt;my blog&lt;/a&gt; than on the Qjub. For more detailed reasons, continue reading.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;why-i%E2%80%99ve-decided-that-way%3F&quot; tabindex=&quot;-1&quot;&gt;Why I’ve decided that way?&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/freezing-qjub/#why-i%E2%80%99ve-decided-that-way%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Why I’ve decided that way?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;A little bit of context first. I have a full-time job (last couple of months I worked only 4 days/week actually), two little kids, a few small &lt;a href=&quot;https://pustelto.com/projects/&quot;&gt;open-source projects&lt;/a&gt;, and I wrote my own blog. That is a lot of going on and while one extra day to focus on these things helped. It still doesn’t allow me to focus on all those things equally.&lt;/p&gt;
&lt;p&gt;When I start writing my blog last year, my goal was to publish at least one article a month. I didn’t meet that goal (yet), but I want to continue to focus on writing more. To do that, I decided to freeze the development of the Qjub. Here are some reasons why I choose to do so:&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;1.-writing-a-blog-makes-more-sense-to-me&quot; tabindex=&quot;-1&quot;&gt;1. Writing a blog makes more sense to me&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/freezing-qjub/#1.-writing-a-blog-makes-more-sense-to-me&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Writing a blog makes more sense to me”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;When I start my blog, I haven’t had a feeling I have too much to offer (hello imposter syndrome). But I gave it a shot and tried to regularly post new articles and republish them 14 days later to &lt;a href=&quot;https://dev.to/&quot;&gt;Dev.to&lt;/a&gt; community. I still have a small audience, but two articles get some serious notice and more than 12k page views (on my blog or on Dev.to). Given these are some of my first articles, I consider that amazing success. And it’s a great motivation to continue with writing. Don’t get me wrong. Even if my article helps a single person I consider it a success. But seeing so many people read your blog feels amazing.&lt;/p&gt;
&lt;p&gt;While working with other developers and checking some existing apps, I realized many front-end developers lack deeper knowledge of web performance, semantic HTML, advanced CSS, and accessibility. So I decide to continue with my blog and focus on those things to teach other developers. And in general, help make the Web better for the users as well.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;2.-the-unclear-focus-of-qjub&quot; tabindex=&quot;-1&quot;&gt;2. The unclear focus of Qjub&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/freezing-qjub/#2.-the-unclear-focus-of-qjub&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. The unclear focus of Qjub”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I start Qjub as a learning project, but it was designed right from the start to solve one of my problems – how to effectively store and organize different notes and resources. Mainly from the web. I believe Qjub does a good job in this case. Or at least it works great for my needs. But I really didn’t figure out how to sold this to others. Currently, I have around 20 users, but very few of them are using Qjub regularly. A great example of this is traffic from my blog. One of my popular blog posts brought to Qjub about 300 visitors in two weeks (a huge spike in visits for me). But none of them become regular users.&lt;/p&gt;
&lt;p&gt;That leads me to the hypothesis that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;My idea is not so great and aside from me, no one needs it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Qjub’s homepage is really bad at its job to explain Qjub to others and convince them to sign up.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I believe that both points are valid. I plan to do something with point number 2 before really putting Qjub aside. As with number 1. I’m not sure I can do much with that.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-it-means-for-qjub-and-its-users&quot; tabindex=&quot;-1&quot;&gt;What it means for Qjub and its users&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/freezing-qjub/#what-it-means-for-qjub-and-its-users&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What it means for Qjub and its users”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Freezing Qjub’s development doesn’t mean I’m going to shut down the project. Qjub will be still there, and it is free to use for anyone. I use it quite a lot, so it’s not going anywhere. It would be a huge pain for me to migrate all my notes and other stuff somewhere else. And domain aside, I run it almost for free. So I have no reason to shut it down completely.&lt;/p&gt;
&lt;p&gt;I will simply stop developing new features and instead use available time for my other activities. You may say that I have already done it if you check &lt;a href=&quot;https://qjub.app/changelog/&quot;&gt;Qjub’s changelog&lt;/a&gt;. But declaring it to the public like this will help me get rid of the mental overhead I currently have with it. And it’s aligned with my value of transparency to the users.&lt;/p&gt;
&lt;p&gt;I still may update it from time to time, since there are plenty of features I would like to add. But that will be an occasional activity.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next steps&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/freezing-qjub/#next-steps&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Next steps”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Before completely putting Qjub aside, I plan to update the homepage as a final effort to better explain its value to the users. After that don’t expect any large updates.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Real-world CSS vs. CSS-in-JS performance comparison</title>
      <link>https://pustelto.com/blog/css-vs-css-in-js-perf/</link>
      <pubDate>Fri, 09 Apr 2021 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/css-vs-css-in-js-perf/</guid>
      <description>&lt;p&gt;CSS-in-JS has taken a solid place in front-end tooling, and it seems this trend will continue in the near future. Especially in the React world. For example, out of 11492 people who participate in &lt;a href=&quot;https://2020.stateofcss.com/en-US/&quot;&gt;State of CSS&lt;/a&gt; survey in 2020 only 14.3% didn’t hear of &lt;a href=&quot;https://styled-components.com/&quot;&gt;Styled Components&lt;/a&gt; (a dominant CSS-in-JS library). And more than 40% of participants have used the library.&lt;/p&gt;
&lt;p&gt;I wanted to see an in-depth performance comparison of CSS-in-JS libraries like Styled Components and a good old CSS for a long time. Sadly I was unable to found a comparison on a real-world project and not some simple test scenario. So I decided to do it myself. I have migrated the real-world app from Styled Components to &lt;a href=&quot;https://linaria.dev/&quot;&gt;Linaria&lt;/a&gt;, which will extract CSS on build time. No runtime generation of the styles on the user’s machine.&lt;/p&gt;
&lt;p&gt;A short notice, before we begin. I’m not a hater of CSS-in-JS. I admit they have great DX, and the composition model inherited from React is great. It can provide developers with some nice advantages like &lt;a href=&quot;https://twitter.com/joshwcomeau&quot;&gt;Josh W. Comeau&lt;/a&gt; highlights in his article &lt;a href=&quot;https://www.joshwcomeau.com/css/styled-components/&quot;&gt;The styled-components Happy Path&lt;/a&gt;. I also use Styled Components on several of my projects or projects I have worked on. But I wondered, what is the price for this great DX from the user’s point of view.&lt;/p&gt;
&lt;p&gt;Let’s see what I have found.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;tldr%3A&quot; tabindex=&quot;-1&quot;&gt;TLDR:&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/#tldr%3A&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “TLDR:”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Don’t use runtime CSS-in-JS if you care about the load performance of your site. &lt;strong&gt;&lt;em&gt;Simply less JS = Faster Site.&lt;/em&gt;&lt;/strong&gt; There isn’t much we can do about it. But if you want to see some numbers, continue reading.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-i-measured-and-how&quot; tabindex=&quot;-1&quot;&gt;What I measured and how&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/#what-i-measured-and-how&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What I measured and how”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The app I have used for the test is a pretty standard React app. Bootstrapped using Create React App project, with Redux and styled using Styled components (v5). It is a fairly large app with many screens, customizable dashboards, customer theming, and more. Since it was built with CRA, it doesn’t have server-side rendering, so everything is rendered on the client (since it’s a B2B app, this wasn’t a requirement).&lt;/p&gt;
&lt;p&gt;I took this app and replaced the Styled Components with Linaria, which seems to have a similar API. I thought the conversion would be easy. It turned out it wasn’t that easy. It took me over two months to migrate it, and even then, I have migrated only a few pages and not the entire app. I guess that’s why there is no comparison like this 😅. Replacing the styling library was the only change. Everything else remained intact.&lt;/p&gt;
&lt;p&gt;I have used Chrome dev tools to run several tests on the two most used pages. I have always run the tests three times, and the presented numbers are an average of those 3 runs. For all the tests, I have set &lt;em&gt;CPU throttling to 4x&lt;/em&gt; and &lt;em&gt;network throttling to Slow 3G&lt;/em&gt;. I used a separate Chrome profile for performance testing without any extensions.&lt;/p&gt;
&lt;p&gt;Run test:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Network (size of the JS and CSS assets, coverage, number of requests)&lt;/li&gt;
&lt;li&gt;Lighthouse audits (performance audit with mobile preset).&lt;/li&gt;
&lt;li&gt;Performance profiling (tests for page load, one for drag and drop interaction)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;network-comparison&quot; tabindex=&quot;-1&quot;&gt;Network comparison&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/#network-comparison&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Network comparison”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;We will start with a network. One of the advantages of CSS-in-JS is that there are no unused styles, right? Well, not exactly. While you have active only the styles used on the page, you may still download unnecessary styles. But instead of having them in a separate CSS file, you have them in your JS bundle.&lt;/p&gt;
&lt;p&gt;Here is a data comparison of the same home page build with Styled Components and Linaria. Size before the slash is gzipped size, uncompressed size is after it.&lt;/p&gt;
&lt;div role=&quot;region&quot; aria-labelledby=&quot;home-network-comparison&quot; tabindex=&quot;0&quot;&gt;
&lt;table&gt;
  &lt;caption id=&quot;home-network-comparison&quot;&gt;Home page network stats comparison&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th style=&quot;text-align:left&quot;&gt;&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Styled Component&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Linaria&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Total number of requests&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;11&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;13&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Total size&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;361kB/1.8MB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;356kB/1.8MB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;CSS size&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;2.3kB/7.2kB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;14.7kB/71.5kB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;No. of CSS requests&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;1&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;3&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;JS size&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;322kB/1.8MB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;305kB/1.7MB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;No. of JS requests&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;6&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;6&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div role=&quot;region&quot; aria-labelledby=&quot;search-network-comparison&quot; tabindex=&quot;0&quot;&gt;
&lt;table&gt;&lt;caption id=&quot;search-network-comparison&quot;&gt;Search page network stats comparison&lt;/caption&gt;&lt;thead&gt; &lt;tr&gt; &lt;th style=&quot;text-align:left&quot;&gt;&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Styled Component&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Linaria&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Total number of requests&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;10&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;12&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Total size&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;395kB/1.9MB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;391kB/1.9MB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;CSS size&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;2.3kB/7.2kB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;16.0kB/70.0kB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;No. of CSS requests&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;1&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;3&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;JS size&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;363kB/1.9MB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;345kB/1.8MB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;No. of JS requests&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;6&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;6&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Even though our CSS payload increased quite a lot, we are still downloading fewer data in total in both test cases (yet the difference is almost neglectable in this case). But what is more important, the sum of CSS and JS for Linaria is still smaller than the size of the JS itself in Styled Component.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;coverage&quot; tabindex=&quot;-1&quot;&gt;Coverage&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/#coverage&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Coverage”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;If we compare coverage, we get a lot of unused CSS for Linaria (around 55kB) compared with 6kB for Styled Component (this CSS is from npm package, not from the Styled Components itself). The size of the unused JS is 20kB smaller for Linaria compared to Styled Component. But the overall size of the unused assets is larger in Linaria. This is one of the trade-offs of external CSS.&lt;/p&gt;
&lt;div role=&quot;region&quot; aria-labelledby=&quot;home-coverage-comparison&quot; tabindex=&quot;0&quot;&gt;
&lt;table&gt;&lt;caption id=&quot;home-coverage-comparison&quot;&gt;Coverage comparison &amp;ndash; Home page&lt;/caption&gt;&lt;thead&gt; &lt;tr&gt; &lt;th style=&quot;text-align:left&quot;&gt;&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Styled Component&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Linaria&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Size of unused CSS&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;6.5kB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;55.6kB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Size of unused JS&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;932kB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;915kB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Total size&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;938.5k&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;970.6kB&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;
&lt;div role=&quot;region&quot; aria-labelledby=&quot;search-coverage-comparison&quot; tabindex=&quot;0&quot;&gt;
&lt;table&gt;&lt;caption id=&quot;search-coverage-comparison&quot;&gt;Coverage comparison &amp;ndash; Search page&lt;/caption&gt;&lt;thead&gt; &lt;tr&gt; &lt;th style=&quot;text-align:left&quot;&gt;&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Styled Component&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Linaria&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Size of unused CSS&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;6.3kB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;52.9kB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Size of unused JS&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;937kB&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;912kB&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Total size&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;938.5k&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;970.6kB&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;lighthouse-performance-audit&quot; tabindex=&quot;-1&quot;&gt;Lighthouse performance audit&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/#lighthouse-performance-audit&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Lighthouse performance audit”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;If we are talking about performance, it would be a shame not to use Lighthouse. You can see the comparisons in the charts below (average from 3 LI runs.). Aside from Web Vitals, I have also include Main thread work (time to parse, compile and execute assets, the biggest part of this is JS, but it covers layout and styles calculation, painting, etc.) and JS Execution time. I have omitted Cumulative Layout Shift since it was close to zero, and there was almost no difference between Linaria and Styled Component.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-320.avif 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-480.avif 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-640.avif 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-320.webp 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-480.webp 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-640.webp 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-320.jpeg 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-480.jpeg 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-640.jpeg 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Lighthouse performance audit comparison of home page. Linaria has better speed index and larges contentful paint by more that 800 milliseconds. And main thread work is is lower by 1.63 seconds.&quot; src=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/PFEN-wkVkb-320.jpeg&quot; width=&quot;320&quot; height=&quot;198&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-320.avif 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-480.avif 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-640.avif 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-320.webp 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-480.webp 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-640.webp 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-320.jpeg 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-480.jpeg 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-640.jpeg 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Lighthouse performance audit comparison of search page. Linaria has better speed index by 900 milliseconds and larges contentful paint by 1.2 seconds. Main thread work is is lower by 1.27 seconds.&quot; src=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/UwqC9cd-GG-320.jpeg&quot; width=&quot;320&quot; height=&quot;198&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;As you can see, Linaria is better in most of the Web Vitals (lost once in CLS). And sometimes by a large margin. For example, LCP is faster by 870ms on the home page and by 1.2s on the Search page. Not only does the page render with normal CSS much faster, but it requires fewer resources as well. Blocking time and time necessary to execute all the JS are smaller by 300ms and roughly 1.3 seconds respectively.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;performance-profiling&quot; tabindex=&quot;-1&quot;&gt;Performance profiling&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/#performance-profiling&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Performance profiling”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Lighthouse can give you many insights on the performance. But to get into the details, the performance tab in the dev tools is the best bet. In this case, the performance tab confirms the Lighthouse results. You can see the details on the charts below.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-320.avif 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-480.avif 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-640.avif 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-320.webp 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-480.webp 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-640.webp 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-320.jpeg 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-480.jpeg 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-640.jpeg 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Profiling comparison of the home page. Rendering and paint are almost identical. But Linaria spend almost 1 second less time on scripting. And have total blocking time smaller by more than 1.5 seconds.&quot; src=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/H488gE9OPZ-320.jpeg&quot; width=&quot;320&quot; height=&quot;197&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-320.avif 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-480.avif 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-640.avif 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-320.webp 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-480.webp 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-640.webp 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-320.jpeg 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-480.jpeg 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-640.jpeg 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Profiling comparison of the search page. Rendering and paint are almost identical. But Linaria spend more than 1 second less time on scripting and have total blocking time smaller almost by than 1.5 seconds.&quot; src=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/GDmQoJsBxq-320.jpeg&quot; width=&quot;320&quot; height=&quot;197&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;Screens build with Styled Component had more long-running tasks. Those tasks also took longer to complete, compared to the Linaria variant.&lt;/p&gt;
&lt;p&gt;To give you another look at the data, here is the visual comparison of the performance charts for loading the home page with Styled Component (top) and Linaria (bottom).&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-320.avif 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-480.avif 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-640.avif 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-960.avif 960w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-320.webp 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-480.webp 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-640.webp 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-960.webp 960w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-320.jpeg 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-480.jpeg 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-640.jpeg 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-960.jpeg 960w, https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Comparison of Chrome dev tools performance minimap chart of home page build with Styled Components and Linaria. Pages build with Linaria have a visually smaller amount of long-running task, loading finished earlier and had better FPS.&quot; src=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/VNRUVqfxLE-320.jpeg&quot; width=&quot;320&quot; height=&quot;138&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;comparing-user-interaction&quot; tabindex=&quot;-1&quot;&gt;Comparing user interaction&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/#comparing-user-interaction&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Comparing user interaction”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;To compare user interaction as well, not only the page load. I have measured the performance of the drag and drop activity used to assign items into groups. The result summary is below. Even in this case, Linaria beat the runtime CSS-in-JS in several categories.&lt;/p&gt;
&lt;div role=&quot;region&quot; aria-labelledby=&quot;dnd-comparison&quot; tabindex=&quot;0&quot;&gt;
  &lt;table&gt;&lt;caption id=&quot;dnd-comparison&quot;&gt;Drag and drop comparison&lt;/caption&gt;&lt;thead&gt; &lt;tr&gt; &lt;th style=&quot;text-align:left&quot;&gt;&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Styled Component&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Linaria&lt;/th&gt; &lt;th style=&quot;text-align:right&quot;&gt;Diff&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Scripting&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;2955&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;2392&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;-563ms&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Rendering&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;3002&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;2525&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;-477ms&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Painting&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;329&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;313&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;-16ms&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&quot;text-align:left&quot;&gt;Total Blocking Time&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;1862.66&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;994.07&lt;/td&gt; &lt;td style=&quot;text-align:right&quot;&gt;-868ms&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt;
&lt;/div&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-320.avif 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-480.avif 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-640.avif 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-960.avif 960w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-320.webp 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-480.webp 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-640.webp 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-960.webp 960w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-320.jpeg 320w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-480.jpeg 480w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-640.jpeg 640w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-960.jpeg 960w, https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Comparison of Chrome dev tools performance minimap chart of drag and drop interaction for pages build with Styled Components and Linaria. Linaria shows less long-running tasks and less JS to execute.&quot; src=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/E8opCFUOGL-320.jpeg&quot; width=&quot;320&quot; height=&quot;38&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/css-vs-css-in-js-perf/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;That’s it. As you can see runtime CSS-in-JS can have a noticeable impact on your webpage. Mainly for low-end devices and regions with a slower internet connection or more expensive data. So maybe we should think better about what and how we use our tooling. Great developer experience shouldn’t come at the expense of the user experience.&lt;/p&gt;
&lt;p&gt;I believe we (developers) should think more about the impact of the tools we choose for our projects. The next time I will start a new project, I will not use runtime CSS-in-JS anymore. I will either use good old CSS or use some build-time CSS-in-JS alternative to get my styles out of JS bundles.&lt;/p&gt;
&lt;p&gt;I think build-time CSS-in-JS libs will be the next big thing in the CSS ecosystem as more and more libs are coming out (the last one being &lt;a href=&quot;https://github.com/seek-oss/vanilla-extract&quot;&gt;vanilla-extract&lt;/a&gt; from Seek). And big companies are heading this way as well, like Facebook with their &lt;a href=&quot;https://www.youtube.com/watch?v=9JZHodNR184&quot;&gt;styling lib&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;*[CRA]: Create React App&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How and why I&#39;ve migrated from Netlify to Cloudflare</title>
      <link>https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/</link>
      <pubDate>Wed, 12 May 2021 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/</guid>
      <description>&lt;p&gt;Up until recently, I have hosted my site on &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt;. They offer free hosting with great Github integration and a smooth developer experience. All this is supported by plenty of other great services. I was super happy with them. But then &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; announced their new service &lt;a href=&quot;https://pages.cloudflare.com/&quot;&gt;Pages&lt;/a&gt;. So far, Cloudflare was known for its fast CDN and networking services. Given their reputation, I have decided to give it a shot. I was wondering if changing my hosting could have some noticeable impact on my page load times.&lt;/p&gt;
&lt;p&gt;And oh boy, it did.&lt;/p&gt;
&lt;p&gt;In this article, I will briefly guide you through the entire process of moving my site from Netlify to Cloudflare Pages with all the little bumps I had along the road. Then I will show you how it impacted the performance of the site.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;migration-steps&quot; tabindex=&quot;-1&quot;&gt;Migration steps&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#migration-steps&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Migration steps”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;At first look, Cloudflare Pages is pretty similar to Netlify or &lt;a href=&quot;https://vercel.com/&quot;&gt;Vercel&lt;/a&gt;. You connect your repo, set your build command, hit save and that’s it. You have automatic deployments with preview links for your PRs. Cloudflare has great &lt;a href=&quot;https://developers.cloudflare.com/pages/getting-started&quot;&gt;documentation&lt;/a&gt;, including specific guides for different frameworks and static site generators.&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-320.avif 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-480.avif 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-640.avif 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-960.avif 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-320.webp 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-480.webp 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-640.webp 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-960.webp 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-320.jpeg 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-480.jpeg 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-640.jpeg 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-960.jpeg 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Form for connecting Github repo to Cloudflare Pages after a user selected a repository. Contains fields for a project name, production branch and framework presets.&quot; src=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/aJ72jEgWmg-320.jpeg&quot; width=&quot;320&quot; height=&quot;433&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Adding a new web page to Cloudflare Pages is easy. Just select a branch and your project&#39;s build command.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Ok. Now I have my site on Cloudflare.&lt;/p&gt;
&lt;p&gt;Well, not really…&lt;/p&gt;
&lt;p&gt;I have moved my hosting. Unfortunately, I’m using more tools from the Netlify ecosystem (build plugins, inject code snippets, etc.). So the migration of my setup 1 to 1 is not that straightforward.&lt;/p&gt;
&lt;p&gt;Here is the list of the stuff I need to solve to move entirely to Cloudflare Pages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Migrate Netlify build plugins&lt;/li&gt;
&lt;li&gt;Include code snippets (analytics) only during production build&lt;/li&gt;
&lt;li&gt;Transfer my domain to Cloudflare&lt;/li&gt;
&lt;li&gt;How to set up custom HTTP headers for my page&lt;/li&gt;
&lt;li&gt;URL redirects&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;build-plugins&quot; tabindex=&quot;-1&quot;&gt;Build plugins&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#build-plugins&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Build plugins”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I use Netlify’s build plugin to extract Critical CSS for my blog. So I need to get this on Cloudflare as well. HTML won’t start rendering until it has downloaded CSS, but if you inline your critical CSS into your HTML and lazy-load the rest. HTML can start to render right away after the first network request. This may improve your page load time quite a lot.&lt;/p&gt;
&lt;p&gt;I picked probably the easiest possible way. Since the Netlify plugins are open source, I simply found the source code of the Critical CSS plugin on Github and extract the code into the Node.js script. I run this script during the build process. I have migrated the build plugins, and it took only a couple of minutes of work. Open source &lt;abbr title=&quot;For The Win&quot;&gt;FTW&lt;/abbr&gt; 🎉.&lt;/p&gt;
&lt;p&gt;To the next step, which is…&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;include-code-snippets-during-build-time&quot; tabindex=&quot;-1&quot;&gt;Include code snippets during build time&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#include-code-snippets-during-build-time&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Include code snippets during build time”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I use &lt;a href=&quot;https://plausible.io/&quot;&gt;Plausible&lt;/a&gt; as my analytics. It’s paid, but privacy-focused analytics tool which gives me all the information I really need. It’s super lightweight, and I’m more than happy to support independent developers and their great products 🙂.&lt;/p&gt;
&lt;p&gt;On Netlify, I used the build setting to include the Plausible code snippet during build time. It was an easy way, how to avoid firing analytics events during development. Since Cloudflare doesn’t have this option, I had to use some other method. Luckily there is &lt;a href=&quot;https://github.com/google/eleventy-high-performance-blog&quot;&gt;Eleventy high performance blog template&lt;/a&gt; which utilize &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; data cascade in a clever way to add &lt;code&gt;isDevelopment&lt;/code&gt; flag into templates. Another quick copy-paste action and another finished item from my to-do list.&lt;/p&gt;
&lt;aside&gt;&lt;strong&gt;NOTE: &lt;/strong&gt;If you use Eleventy as your static site generator (which you should, because it&#39;s awesome). I highly suggest giving the High performance blog template a closer look. There is a ton of amazing stuff, which will teach you how to make a better and faster web. Some of them are applicable even for non-eleventy sites.&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;domain-transition&quot; tabindex=&quot;-1&quot;&gt;Domain transition&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#domain-transition&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Domain transition”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The next step was to move the domain to Cloudflare. I had to change my nameservers to point to Cloudflare. I buy my domains via Vercel (I use it for few projects and love how easy and smooth is the process of buying domain there). But since Vercel isn’t a domain registrar, I had to contact Vercel’s support in order to change the nameservers. Based on their info, Vercel support process requests for free accounts within 3-5 days. But I had got my nameservers changed the next day 👍.&lt;/p&gt;
&lt;p&gt;Once Cloudflare noticed correct nameservers, I could add a custom domain to my webpage, assign the worker to modify the headers, and handle the URL rewrites.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;adding-custom-http-headers-to-cloudflare-pages&quot; tabindex=&quot;-1&quot;&gt;Adding custom HTTP headers to Cloudflare Pages&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#adding-custom-http-headers-to-cloudflare-pages&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Adding custom HTTP headers to Cloudflare Pages”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;aside&gt;&lt;strong&gt;UPDATE&lt;/strong&gt; (6 November): Cloudflare now supports custom headers directly in the Pages using `_headers` file. For most cases that should be more than enough.&lt;/aside&gt;
&lt;p&gt;If you want to set custom headers for your web page hosted on Cloudflare Pages, you simply have to add &lt;code&gt;_headers&lt;/code&gt; file to the root of the build folder (pretty easy in Eleventy, just add &lt;code&gt;config.addPassthroughCopy(&amp;quot;src/_headers&amp;quot;);&lt;/code&gt; into your &lt;code&gt;eleventy.js&lt;/code&gt; file).&lt;/p&gt;
&lt;p&gt;Syntax of the &lt;code&gt;_headers&lt;/code&gt; file is simple as well. You just need to specify the URL pattern and then custom headers below it.&lt;/p&gt;
&lt;figure&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;/*.avif&lt;br /&gt;  Content-Type: image/avif&lt;br /&gt;  Content-Disposition: inline&lt;br /&gt;&lt;br /&gt;https://:project.pages.dev/*&lt;br /&gt;  X-Robots-Tag: noindex&lt;/code&gt;&lt;/pre&gt;
&lt;figcaption&gt;Setting custom via _headers file.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/cloudflare_headers&quot;&gt;official documentation for headers&lt;/a&gt; or my own &lt;a href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/my_headers&quot;&gt;_headers&lt;/a&gt; file.&lt;/p&gt;
&lt;p&gt;This should probably satisfy most of your needs when customizing headers. But if you need something more advanced, then you have to use Cloudflare Workers. Continue reading, if you are interested in that, otherwise feel free to skip to the next section.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h4 id=&quot;headers-customization-using-cloudflare-workers&quot; tabindex=&quot;-1&quot;&gt;Headers customization using Cloudflare Workers&lt;/h4&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#headers-customization-using-cloudflare-workers&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Headers customization using Cloudflare Workers”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Custom headers for Cloudflare Pages were probably the most complicated part of the entire migration. Compared to Netlify, Cloudflare doesn’t have any direct tool to customize your headers. If you need this functionality, you have to use another service – &lt;a href=&quot;https://workers.cloudflare.com/&quot;&gt;Cloudflare Workers&lt;/a&gt;. You have to create a custom worker script to modify the headers. Luckily basic version of workers is free, and &lt;a href=&quot;https://developers.cloudflare.com/workers/&quot;&gt;Workers documentation&lt;/a&gt; even include example showing &lt;a href=&quot;https://developers.cloudflare.com/workers/examples/alter-headers&quot;&gt;how to modify the headers&lt;/a&gt;. Just exactly what we need.&lt;/p&gt;
&lt;p&gt;You can either copy-paste the code and modify it in the web interface. Or you can download Cloudflare CLI, create the script locally and publish it when ready. Below is the example of my worker script:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fetch&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reqUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Make the headers mutable by re-constructing the Response.&lt;/span&gt;&lt;br /&gt;  response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reqUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.avif&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/avif&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Content-Disposition&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;inline&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Permissions-Policy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;interest-cohort=()&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;X-Frame-Options&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;sameorigin&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;strict-transport-security&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;max-age=31536000&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Referrer-Policy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;strict-origin-when-cross-origin&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another downside is that you can’t connect workers to the default URL provided by Cloudflare for your pages. You have to set up a custom domain for your page and add the worker script to the domain by specifying its route. If you need to change headers on your webpage, but you don’t have a custom domain for some reason, you are at a dead end.&lt;/p&gt;
&lt;p&gt;This sure isn’t as easy as using Netlify’s config file, but it is still relatively simple to set up. Still, it may be one thing to consider when deciding whether to choose Cloudflare as your hosting.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;custom-redirects&quot; tabindex=&quot;-1&quot;&gt;Custom redirects&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#custom-redirects&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Custom redirects”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;On Netlify, I had custom redirects to route all my traffic from the &lt;code&gt;www&lt;/code&gt; subdomain to the root domain. And then from my original domain &lt;code&gt;*.pustelto.cz&lt;/code&gt; to &lt;code&gt;https://pustelto.com&lt;/code&gt;. The first redirect is handled on Netlify automatically when setting up DNS records. The second one can be done fairly easily as well.&lt;/p&gt;
&lt;p&gt;On Cloudflare, I didn’t have much trouble with this. I have moved my second domain to Cloudflare too, and then used rules config to handle the redirects. You can set up the redirects in few clicks. In a free Cloudflare plan, you have 3 Page rules for free. That is more than enough to handle my use case. But if you have more advanced redirect logic, it may not be enough (you can buy additional rules if necessary).&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-320.avif 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-480.avif 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-640.avif 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-960.avif 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-320.webp 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-480.webp 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-640.webp 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-960.webp 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-320.jpeg 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-480.jpeg 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-640.jpeg 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-960.jpeg 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Page rule for forwarding www.pustelto.com to https://pustelto.com using 301 redirect code&quot; src=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/M4YcDWxeDh-320.jpeg&quot; width=&quot;320&quot; height=&quot;198&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-320.avif 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-480.avif 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-640.avif 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-960.avif 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-320.webp 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-480.webp 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-640.webp 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-960.webp 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-320.jpeg 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-480.jpeg 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-640.jpeg 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-960.jpeg 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Page rule for redirecting all traffic from pustelto.cz domain to pustelto.com.&quot; src=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/g7q9x-10wC-320.jpeg&quot; width=&quot;320&quot; height=&quot;198&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;performance-comparison&quot; tabindex=&quot;-1&quot;&gt;Performance comparison&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#performance-comparison&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Performance comparison”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I compared the page load times of my website on Cloudflare and Netlify using &lt;a href=&quot;https://www.webpagetest.org/&quot;&gt;WebPageTest.org&lt;/a&gt;. The test setup was mobile emulation of Mogo G (gen 4) on a fast 3G connection. &lt;abbr title=&quot;First contentful paint&quot;&gt;FCP&lt;/abbr&gt; and &lt;abbr title=&quot;Largest contentful paint&quot;&gt;LCP&lt;/abbr&gt; on Cloudflare were almost 400ms faster! And the page was visually complete in 1.1s compared to 2s on Netlify. That is a really huge improvement I’ve got basically for free. Just by changing hosting, I have got over 300ms better page load across the pages. Another benefit is a slightly smaller payload from Cloudflare, thanks to the support of Brotli compression.&lt;/p&gt;
&lt;p&gt;Check out the pictures below for more details. Notice the waterfall chart for Netlify where the image is downloaded from another domain and so it requires another SSL connection which will add to the total time necessary to download the file.&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-320.avif 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-480.avif 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-640.avif 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-960.avif 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-320.webp 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-480.webp 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-640.webp 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-960.webp 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-320.jpeg 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-480.jpeg 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-640.jpeg 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-960.jpeg 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;On Cloudflare first contentful paint appeared after 0.9 seconds, compared to 1.3 seconds on Netlify. Visually complete page was after 1.1 seconds on Cloudflare and 2 seconds on Netlify&quot; src=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RZkmrTuvXI-320.jpeg&quot; width=&quot;320&quot; height=&quot;125&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Visual comparison of loading home page on Cloudflare and Netlify&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-320.avif 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-480.avif 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-640.avif 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-960.avif 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-320.webp 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-480.webp 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-640.webp 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-960.webp 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-320.jpeg 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-480.jpeg 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-640.jpeg 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-960.jpeg 960w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;On Cloudflare first contentful paint (page was visually complete at this moment as well) appeared after 1 second, compared to 1.3 seconds on Netlify.&quot; src=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/2_YRqMFl72-320.jpeg&quot; width=&quot;320&quot; height=&quot;164&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Visual comparison of loading article page on Cloudflare and Netlify&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-320.avif 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-480.avif 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-640.avif 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-320.webp 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-480.webp 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-640.webp 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-320.jpeg 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-480.jpeg 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-640.jpeg 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;HTML document download took 706ms, followed by CSS, images and plausible analytics in the end.&quot; src=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/0ikfqeAQRn-320.jpeg&quot; width=&quot;320&quot; height=&quot;109&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Waterfall chart when loading the home page from Cloudflare&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-320.avif 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-480.avif 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-640.avif 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-320.webp 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-480.webp 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-640.webp 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-320.jpeg 320w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-480.jpeg 480w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-640.jpeg 640w, https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;HTML document download took 1167ms, followed by CSS, images, and plausible analytics in the end. For some reason, the image required another SSL connection. On Cloudflare, this was downloaded directly from my domain.&quot; src=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/RDj93ifElB-320.jpeg&quot; width=&quot;320&quot; height=&quot;109&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Waterfall chart when loading the home page from Netlify&lt;/figcaption&gt;&lt;/figure&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;so-should-i-migrate-to-cloudflare%3F&quot; tabindex=&quot;-1&quot;&gt;So should I migrate to Cloudflare?&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#so-should-i-migrate-to-cloudflare%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “So should I migrate to Cloudflare?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;It depends 😅.&lt;/p&gt;
&lt;p&gt;You should consider what you have to migrate and what would be the cost of it. If you are already heavily invested in another hosting platform (be it Netlify, Vercel, or something else), it might add extra effort to convert your entire setup. Netlify, for example, offers a bunch of other great tools, and leaving the entire ecosystem may be problematic. But the performance gains can outweigh the cons of the migration. As always, when working on performance-related stuff, measure it.&lt;/p&gt;
&lt;p&gt;Another thing to consider is the price. As mentioned above, you can get the basic stuff for free on Cloudflare as well. But for more complex sites, this may add an additional cost. Especially with workers and page routes, you can go from free Netlify to paid Cloudflare quickly in more advanced cases.&lt;/p&gt;
&lt;p&gt;But for simple static sites, I think it is worth it.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I hope this article will help you in case you decide to give Cloudflare Pages a chance. And eventually help you with migration, since there were few little things I had to figure out myself (eg. custom headers and redirects) since documentation didn’t mention this. But overall switching to Cloudflare wasn’t too difficult and I manage to finish it in few hours of work.&lt;/p&gt;
&lt;p&gt;That’s it, folks. Let me know on Twitter if you tested the Cloudflare Pages and what do you think? Or if you decided to stick with a different platform, let me know which one and why. I would love to see what are you using.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;updates&quot; tabindex=&quot;-1&quot;&gt;Updates&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-and-why-i-have-migrated-from-netlify-to-cloudflare/#updates&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Updates”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;6 November 2021&lt;/strong&gt; - add info about new way how to set custom headers for Cloudflare Pages.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>5 easy steps to improve accessibility.</title>
      <link>https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/</link>
      <pubDate>Sat, 16 Apr 2022 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/</guid>
      <description>&lt;p&gt;Want to get better at the accessibility, but you don’t know where to start?&lt;/p&gt;
&lt;p&gt;You can do a lot for the accessibility of your websites with only a few basic techniques. Below you will find a list of items, which are easy to use, but I see many websites doing mistakes in them.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;tldr%3A&quot; tabindex=&quot;-1&quot;&gt;TLDR:&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#tldr%3A&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “TLDR:”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Use semantic HTML&lt;/li&gt;
&lt;li&gt;Add meaningful labels to interactive elements and page sections on the page&lt;/li&gt;
&lt;li&gt;Make focused elements clearly visible&lt;/li&gt;
&lt;li&gt;Test with keyboard&lt;/li&gt;
&lt;li&gt;Learn how to use screen readers&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Please not that tips mentioned in this article will help you kickstart your accessibility efforts, but they won’t be most likely sufficient to make your website fully compliant with WCAG specification.&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;why-should-we-care%3F&quot; tabindex=&quot;-1&quot;&gt;Why should we care?&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#why-should-we-care%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Why should we care?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The Interner has become an integral part of our lives. We spend a large amount of our time on the internet. This has become especially true during Covid pandemia. Unfortunately, not all people can use the Internet with ease. Some can’t use a mouse, others may need a screen reader or even use voice to control their computer. It’s our responsibility as developers to build a web that is accessible and useable by all.&lt;/p&gt;
&lt;p&gt;With that said, let’s get to the business. Here is what you can  do to improve accessibility on your websites:&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;1.-use-semantic-html&quot; tabindex=&quot;-1&quot;&gt;1. Use semantic HTML&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#1.-use-semantic-html&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Use semantic HTML”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;One of the easiest things to do when you want to get better with accessibility is to start using semantic HTML. Yes, you can build an entire site with &lt;code&gt;div&lt;/code&gt; and CSS, but that would mean a terrible user experience for users relying on screen readers.&lt;/p&gt;
&lt;p&gt;Using semantic HTML has a few benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Properly implemented semantic markup (and labeling, see next section) allows users with a screen reader to easily navigate and find what they need. It helps them understand what kind of content they are consuming. I highly recommend checking this &lt;a href=&quot;https://www.youtube.com/watch?v=HE2R86EZPMA&amp;amp;list=LL&amp;amp;index=1&quot;&gt;video how semantic HTML helps screen reader users with navigation&lt;/a&gt; or how &lt;a href=&quot;https://www.youtube.com/watch?v=OUDV1gqs9GA&amp;amp;t=1764s&quot;&gt;people with disabilities use screen readers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Native HTML elements (like buttons) have a lot of functionality baked in. If you use for example div to create interactive elements you will lose all those accessible features already available.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is what you can do to boost the semantics of your HTML:&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;build-the-structure-of-the-page&quot; tabindex=&quot;-1&quot;&gt;Build the structure of the page&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#build-the-structure-of-the-page&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Build the structure of the page”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Use HTML tags to mark important regions like &lt;code&gt;main&lt;/code&gt;,&lt;code&gt;nav&lt;/code&gt;, &lt;code&gt;header&lt;/code&gt;, &lt;code&gt;footer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add correct heading structure - aka don’t skip the headings level. &lt;code&gt;h2&lt;/code&gt; should always follow &lt;code&gt;h1&lt;/code&gt;, &lt;code&gt;h3&lt;/code&gt; after &lt;code&gt;h2&lt;/code&gt; etc. No &lt;code&gt;h4&lt;/code&gt; after &lt;code&gt;h2&lt;/code&gt; and similar, pretty please.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I suggest reading  &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element&quot;&gt;HTML elements reference on MDN&lt;/a&gt; to learn about all the elements available in HTML. You might be surprised what elements you discover.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; If you want to test your knowledge of HTML elements you may try this &lt;a href=&quot;https://codepen.io/plfstr/full/zYqQeRw&quot;&gt;game on the codepen&lt;/a&gt;&lt;/p&gt;
&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;use-correct-interactive-elements&quot; tabindex=&quot;-1&quot;&gt;Use correct interactive elements&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#use-correct-interactive-elements&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Use correct interactive elements”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Web apps these days are more capable than ever, to make the interactive experience we often have many interactive elements on the page. Buttons, links, inputs, date pickers, select, and so on.&lt;/p&gt;
&lt;p&gt;It is important to use those elements for their real purpose. Otherwise, you may confuse users relying on screen readers. For example, don’t mix buttons and links. A link should take the user to the new page and change the URL. Use a button for the rest.&lt;/p&gt;
&lt;p&gt;If possible, prefer native HTML elements as they usually provide accessibility and other functionalities out-of-the-box (&lt;a href=&quot;https://daverupert.com/2020/02/html-the-inaccessible-parts/&quot;&gt;there are certain native HTML elements for poor accessibility though&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I have also covered this topic in more detail in my older article &lt;a href=&quot;https://pustelto.com/blog/what-every-javascript-developer-should-know-about-html/&quot;&gt;What every JavaScript developer should know about HTML and CSS&lt;/a&gt;. So please check it here.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;2.-label-the-stuff-properly&quot; tabindex=&quot;-1&quot;&gt;2. Label the stuff properly&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#2.-label-the-stuff-properly&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. Label the stuff properly”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Good labels are important to all users. They help them navigate the user interface and finish the task successfully. But they are essential for screen reader users.&lt;/p&gt;
&lt;p&gt;Without proper labels, the screen reader has nothing to announce to the users, except for the role of the element. Imagine this scenario: You need to press Add to cart button on the e-commerce site, but how can you pick the correct button if all you hear is: “button”. And you hear that 24 times.&lt;/p&gt;
&lt;p&gt;To avoid this, keep these simple rules in mind:&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;every-interactive-element-must-have-a-proper-accessible-name&quot; tabindex=&quot;-1&quot;&gt;Every interactive element must have a proper accessible name&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#every-interactive-element-must-have-a-proper-accessible-name&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Every interactive element must have a proper accessible name”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;So the screen reader users can understand what the element does. Eg. use a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element for the form inputs, add a text label to all buttons and links (yes, even &lt;a href=&quot;https://www.sarasoueidan.com/blog/accessible-icon-buttons/&quot;&gt;button with only an icon can have a text label&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;When you don’t have a native HTML element to label something (eg. naming several &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; elements on the page so screen readers can easily jump between them), you can use &lt;code&gt;aria-labelledby&lt;/code&gt; or &lt;code&gt;aria-label&lt;/code&gt; attributes.&lt;/p&gt;
&lt;p&gt;Adrian Roselli has a great summary of &lt;a href=&quot;https://twitter.com/aardrian/status/1498357239337426946&quot;&gt;how to prioritize different labeling techniques&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;labels-should-be-clear-and-short-but-have-enough-information-to-understand-the-context.&quot; tabindex=&quot;-1&quot;&gt;Labels should be clear and short but have enough information to understand the context.&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#labels-should-be-clear-and-short-but-have-enough-information-to-understand-the-context.&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Labels should be clear and short but have enough information to understand the context.”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;For example, visually it is ok to have &lt;code&gt;Add&lt;/code&gt; button next to an item to add it into a cart. But screen reader users may need more information, so it is often a good idea to add extra information to the button visible only for screen readers. When a screen reader move focus to our Add button, it may announce something like this: &lt;em&gt;Add to cart, A-Wing Lego Star Wars&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Want to learn how to create good labels for link and button checkout these articles: &lt;a href=&quot;https://a11y-collective.com/blog/the-perfect-link/&quot;&gt;The perfect link&lt;/a&gt; and &lt;a href=&quot;https://www.sarasoueidan.com/blog/accessible-text-labels/&quot;&gt;Accessible Text Labels For All&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;3.-make-focused-elements-visible&quot; tabindex=&quot;-1&quot;&gt;3. Make focused elements visible&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#3.-make-focused-elements-visible&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. Make focused elements visible”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Hidding focus outline from users seems like a design fetish. It doesn’t help anyone, and I doubt it will improve a design. Rather design a nice and visible focus outline.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not sure what more to write to this one. Just make the focusable interactive elements visible, please. If you don’t like the default outline, design a better one, but don’t disable it.&lt;/p&gt;
&lt;p&gt;I use keyboard navigation quite often as well. Especially when filling some forms and sometimes I really struggle to see where the hell is my focus.&lt;/p&gt;
&lt;p&gt;If you want to learn how to do a great focus outline, I suggest an article about &lt;a href=&quot;https://www.sarasoueidan.com/blog/focus-indicators/&quot;&gt;designing WCAG-compliant focus indicators&lt;/a&gt; from &lt;a href=&quot;https://twitter.com/SaraSoueidan&quot;&gt;Sara Soueidan&lt;/a&gt;. And &lt;a href=&quot;https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/&quot;&gt;be careful when using &lt;code&gt;box-shadow&lt;/code&gt; property for outline&lt;/a&gt;. It doesn’t play nice with Windows high-contrast mode.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;4.-test-with-keyboard&quot; tabindex=&quot;-1&quot;&gt;4. Test with keyboard&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#4.-test-with-keyboard&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “4. Test with keyboard”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;When you are building a new webpage, always try to navigate it using only a keyboard. It is an extremely easy way of testing, but it can show you a lot of potential issues early on. This is also good practice when selecting some UI package for your new web project 😉.&lt;/p&gt;
&lt;p&gt;When testing with a keyboard I usually try to check for those use-cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can I focus and control all interactive elements with a keyboard?&lt;/li&gt;
&lt;li&gt;Is the focused element always visible so the users know where are they?&lt;/li&gt;
&lt;li&gt;Does the focus move correctly for more complex patterns (eg. when I open a modal window, is the focus in the modal or still on the trigger button)?&lt;/li&gt;
&lt;li&gt;Is the tab order logical? Modern layouts built with a grid and a flexbox can change the visual order of the elements in CSS. But tab order is determined by the position in the HTML, not by CSS layout. This may confuse users, so be careful.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;5.-learn-how-to-use-screen-readers&quot; tabindex=&quot;-1&quot;&gt;5. Learn how to use screen readers&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#5.-learn-how-to-use-screen-readers&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “5. Learn how to use screen readers”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;So you already test with a keyboard. Great. Now it’s time to move to &lt;a href=&quot;https://twitter.com/pustelto/status/1393904239185993728&quot;&gt;next level in accessibility testing&lt;/a&gt;. Learn how to use at least one screen reader and use it to test your pages (and other pages as well).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Learning how to use a screen reader helped me to be more emphatic with screen reader users. It’s shocking how very little information they often have available and how hard it is to use the web.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using a screen reader opened my eyes. I started to think differently about the labels, alt texts, and accessible names in general. And instead of plain &lt;code&gt;save&lt;/code&gt; or &lt;code&gt;delete&lt;/code&gt; I have started to provide much more info into the labels. Or put the labels at places where I wouldn’t add them in the past.&lt;/p&gt;
&lt;p&gt;Don’t know how to use a screen reader?&lt;/p&gt;
&lt;p&gt;Check out these &lt;a href=&quot;https://dequeuniversity.com/screenreaders/&quot;&gt;guides from Deque about various screen readers&lt;/a&gt; and start practicing. It takes some time to be effective with them, but if you are serious about a11y, then this is a must.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/5-easy-steps-to-improve-accessibility/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;That was quite a lot to digest. Congratulations for getting all the way here. I hope this article will help you to make the Web a little bit better for everyone.&lt;/p&gt;
&lt;p&gt;And if you still think there is too much to deal with, don’t worry. Start slow and add new stuff when you feel comfortable. Learning accessibility is a never-ending process and we have just barely scratched the surface. We didn’t even talk about ARIA attributes and roles, or how JS fits in, and so on.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to (not) make a button</title>
      <link>https://pustelto.com/blog/how-to-make-a-button/</link>
      <pubDate>Fri, 16 Sep 2022 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/how-to-make-a-button/</guid>
      <description>&lt;p&gt;Today’s web is a very interactive experience, yet we often fail to provide the same experience to everyone.&lt;/p&gt;
&lt;p&gt;Complex form widgets, interactive configurators, tables, shop listings, and the list goes on. We should use buttons and links to interact with those components. But often, we just put an &lt;code&gt;onClick&lt;/code&gt; handler on a div and call it a day.&lt;/p&gt;
&lt;p&gt;That leads to a poor experience for some users.&lt;/p&gt;
&lt;p&gt;I want to show you how to properly turn a &lt;code&gt;div&lt;/code&gt; into a button, in case you ever need it. So the result will be accessible and useable by everyone.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;&lt;strong&gt;DISCLAIMER&lt;/strong&gt;: Please don’t do this. I’m not suggesting using div (or any other element) instead of a native button. Instead, I urge you to use the button. I only have to use a div instead of a button once in my life. And most likely, even in that one case, I could have solved it differently.&lt;/p&gt;
&lt;p&gt;So whatever your use case. You can most likely use a button for the job.&lt;/p&gt;
&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;why-would-you-want-to-create-a-custom-button%3F&quot; tabindex=&quot;-1&quot;&gt;Why would you want to create a custom button?&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-make-a-button/#why-would-you-want-to-create-a-custom-button%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Why would you want to create a custom button?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;There may be various reasons for this.&lt;/p&gt;
&lt;p&gt;We may have limited control over the markup (eg. when we use 3rd party library). Or there is some specific use case that doesn’t seem to be a good fit for a button or a link (and I have seen a lot of them in my career). For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interactive clickable card with the product info and other controls (add to cart, compare, etc.)&lt;/li&gt;
&lt;li&gt;Nested controls (a button with another button inside)&lt;/li&gt;
&lt;li&gt;Clickable table rows.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While having limited control over markup may be hard to overcome. We can often solve these specific cases with a little bit of effort.&lt;/p&gt;
&lt;p&gt;Changing the design may be a valid option in some cases. But often we just need to rethink the markup a little bit. Here are some links for common patterns and how to do them correctly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://inclusive-components.design/cards/&quot;&gt;Cards - Inclusive component&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://adrianroselli.com/2019/09/table-with-expando-rows.html&quot;&gt;Table with Expando Rows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://adrianroselli.com/2021/06/multi-column-sortable-table-experiment.html&quot;&gt;Multi-Column Sortable Table Experiment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But if you think you still have to build a custom button, then read on:&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;building-a-button-from-a-div&quot; tabindex=&quot;-1&quot;&gt;Building a button from a div&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-make-a-button/#building-a-button-from-a-div&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Building a button from a div”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Your first step will most likely be adding an &lt;code&gt;onClick&lt;/code&gt; handler. So let’s start with this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;doSomething&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Click me&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt;&#39;m a fake button&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Often this is also where it ends.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: I use &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; syntax since I work with React most of the time. First, I think it’s readable, and second, JS frameworks like React make it really easy to put events like this into non-interactive elements since you write markup in JS as well.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;But as soon as you try to use a keyboard to interact with your new button, you will find that you can’t select it using the Tab key.&lt;/p&gt;
&lt;p&gt;So let’s fix that and add a few more properties.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;keyboard-navigation&quot; tabindex=&quot;-1&quot;&gt;Keyboard navigation&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-make-a-button/#keyboard-navigation&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Keyboard navigation”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div tabindex&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onClickButtonHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; onKeyDown&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onKeyDownHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  Click me&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt;&#39;m a fake button&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have to add a &lt;code&gt;tabindex&lt;/code&gt; attribute with the value &lt;code&gt;0&lt;/code&gt;. This will make the button focusable when using a keyboard (an important part of any accessible interface).&lt;/p&gt;
&lt;p&gt;Next, we have to assign a &lt;code&gt;keydown&lt;/code&gt; event listener to our custom button. HTML button can be pressed using Enter and Space keys. So we need to add that behavior as well. The listener callback will look something like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onKeyDownHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Enter&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* space */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// We have to disable default space behavior which is scrolling page down.&lt;/span&gt;&lt;br /&gt;    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;onClickButtonHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But in reality, this is not exactly correct behavior. The native button works slightly differently, as Adrian Roselli pointed out in his article &lt;a href=&quot;https://adrianroselli.com/2022/04/brief-note-on-buttons-enter-and-space.html&quot;&gt;Brief Note on Buttons, Enter, and Space&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Enter triggers the native button on the &lt;code&gt;keydown&lt;/code&gt; event, but Space triggers the button on the &lt;code&gt;keyup&lt;/code&gt; event (the press with Space can be canceled). If we want to mimic this behavior we have to update our handlers again:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;br /&gt;  tabindex&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onClickButtonHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  onKeyDown&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onKeyDownHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  onKeyUp&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onKeyUpHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  Click me&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt;&#39;m a fake button&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onKeyDownHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// We have to disable default space behavior which is scrolling page down.&lt;/span&gt;&lt;br /&gt;    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Enter&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;onClickButtonHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onKeyUpHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* space */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;onClickButtonHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;aside&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: all this stuff is handled on the native button via the &lt;code&gt;onClick&lt;/code&gt; method. So there is no need to pass keyboard event handlers for a button press.&lt;/p&gt;
&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;accessibility-improvements&quot; tabindex=&quot;-1&quot;&gt;Accessibility improvements&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-make-a-button/#accessibility-improvements&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Accessibility improvements”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Right now, we have a fake div button that users can click on. And they can control it with a keyboard as well. But we still have to do a few more things to make this button accessible. First, we need to tell screen readers that this is really a button, not a div. We will add the &lt;code&gt;role=&amp;quot;button&amp;quot;&lt;/code&gt; attribute. Without it, screen readers won’t announce this as a button to the users.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;br /&gt;  tabindex&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onClickButtonHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  onKeyDown&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onKeyDownHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  onKeyUp&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onKeyUpHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  role&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;button&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  Click me&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt;&#39;m a fake button&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Are we done?&lt;/p&gt;
&lt;p&gt;For the basic functionality, yes. But what if you need to disable that button?&lt;/p&gt;
&lt;p&gt;For an HTML button, you would just use the &lt;code&gt;disabled&lt;/code&gt; attribute and the browser would take care of everything (default styles, turn off the interactivity, etc.). But that is not going to work for our fake button.&lt;/p&gt;
&lt;p&gt;We have to use the &lt;code&gt;aria-disabled&lt;/code&gt; attribute. But we also have to handle the styling and also disabling of the event handlers. You might also want to prevent the button to be unfocusable via tab. All this means just more work for us.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;final-result&quot; tabindex=&quot;-1&quot;&gt;Final result&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-make-a-button/#final-result&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Final result”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;This is our final code for a button with support for the disabled state:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;br /&gt;  tabindex&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onClickButtonHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  onKeyDown&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onKeyDownHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  onKeyUp&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onKeyUpHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  role&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;button&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  Click me&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt;&#39;m a fake button&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isDisabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eventTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ariaDisabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; eventTarget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;aria-disabled&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isDisabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ariaDisabled &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; ariaDisabled &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; isDisabled&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onKeyDownHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// We have to disable default space behavior which is scrolling page down.&lt;/span&gt;&lt;br /&gt;    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Enter&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDisabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;onClickButtonHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onKeyUpHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isDisabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;onClickButtonHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quite a lot of code for a simple component, right? And even if you write all this code you still don’t get the full button feature set&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://pustelto.com/blog/how-to-make-a-button/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I hope by this time that you realize that instead of writing all this code yourself, it is much easier to write this code and get all the goodies for free:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;button&quot;&lt;/span&gt; onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onClickButtonHandler&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  Click me&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt;&#39;m a real button&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isn’t that much easier?&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-make-a-button/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;While the button seems to be a simple component, which we can easily hack together with the &lt;code&gt;div&lt;/code&gt; and &lt;code&gt;onClick&lt;/code&gt; handler, the opposite is true.&lt;/p&gt;
&lt;p&gt;I hope you have learned how complex it is to create a good button component and how much time and effort you can save using a native button.&lt;/p&gt;
&lt;p&gt;And your users will probably thank you as well.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;additional-resources&quot; tabindex=&quot;-1&quot;&gt;Additional resources&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-make-a-button/#additional-resources&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Additional resources”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Want to learn more? Check out these amazing resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://adrianroselli.com/2022/04/brief-note-on-buttons-enter-and-space.html&quot;&gt;Brief Note on Buttons, Enter, and Space&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A three-part series about implementing a button in the React-Aria library:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://react-spectrum.adobe.com/blog/building-a-button-part-1.html&quot;&gt;Building a Button Part 1: Press Events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react-spectrum.adobe.com/blog/building-a-button-part-2.html&quot;&gt;Building a Button Part 2: Hover Interactions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react-spectrum.adobe.com/blog/building-a-button-part-3.html&quot;&gt;Building a Button Part 3: Keyboard Focus Behavior&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;updates&quot; tabindex=&quot;-1&quot;&gt;Updates&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-make-a-button/#updates&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Updates”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;10 October 2022&lt;/strong&gt; - Added &lt;code&gt;e.preventDefault()&lt;/code&gt; to event handlers. &lt;a href=&quot;https://twitter.com/_aaronbnb/status/1578267248606646272&quot;&gt;Aaron&lt;/a&gt; corretly pointed out, that I forgot to disable native space behavior to scroll the page down.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot; aria-label=&quot;Footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;We created a generic button, but the HTML button has more types. It can submit or reset a HTML form, when nested inside &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag without any extra JS (aside from handling the submitted data).&lt;/p&gt;
&lt;p&gt;And when we add a &lt;code&gt;form&lt;/code&gt; attribute on the button to connect it to a form. We can do that even if the button is outside of the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element. &lt;a href=&quot;https://pustelto.com/blog/how-to-make-a-button/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>I have switched from VS Code to VIM and I will never go back</title>
      <link>https://pustelto.com/blog/i-switched-to-vim/</link>
      <pubDate>Mon, 16 Jan 2023 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/i-switched-to-vim/</guid>
      <description>&lt;p&gt;Some time ago, I posted a &lt;a href=&quot;https://twitter.com/pustelto/status/1552915586203541505&quot;&gt;tweet announcing I had switched from VS Code to VIM&lt;/a&gt; (&lt;a href=&quot;https://neovim.io/&quot;&gt;Neovim&lt;/a&gt; to be precise).&lt;/p&gt;
&lt;p&gt;Given that most of my colleagues looked at me in disbelief when I told them. I have decided to summarize my reasons behind this and outline the process for those brave enough to follow 😁.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;why-i-ditched-vs-code&quot; tabindex=&quot;-1&quot;&gt;Why I ditched VS Code&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/i-switched-to-vim/#why-i-ditched-vs-code&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Why I ditched VS Code”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I would say my two main reasons are &lt;strong&gt;performance&lt;/strong&gt; and &lt;strong&gt;navigation&lt;/strong&gt; in the code. VIM is just great at those.&lt;/p&gt;
&lt;p&gt;And while you can easily add navigation to VS Code with &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vscodevim.vim&quot;&gt;VIM plugin&lt;/a&gt;. You can’t do&lt;br /&gt;
much about performance.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;performance&quot; tabindex=&quot;-1&quot;&gt;Performance&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/i-switched-to-vim/#performance&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Performance”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;VS Code has become unbearable slow for me, especially on large projects. And I’m using a mac, so I can’t blame the slow machine. Main issues I had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There was a noticeable delay in the response when I triggered suggestions and before I got a response from Intellisense.&lt;/li&gt;
&lt;li&gt;Opening large files.&lt;/li&gt;
&lt;li&gt;Changing a project. When I wanted to check other projects, it took some time until the editor was fully responsive.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;navigation&quot; tabindex=&quot;-1&quot;&gt;Navigation&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/i-switched-to-vim/#navigation&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Navigation”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I tend to learn a lot of shortcuts (and prefer to use a keyboard most of the time). I could work like this in VS Code for most of the stuff, even though some keyboard shortcuts I used were quite complicated and required a combination of three keys most of the time.&lt;/p&gt;
&lt;p&gt;But for the detailed movements in the code, I had to use mouse or arrow keys. Getting my hand aways from letters to reach for arrows just become annoying for me. In the end I add custom keybinding which simulated VIM’s &lt;code&gt;hjkl&lt;/code&gt; basic navigation.&lt;/p&gt;
&lt;p&gt;That was a point where I realized I should probably use at least VIM plugin instead of reinventing wheel.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;how-do-i-feel-about-neovim&quot; tabindex=&quot;-1&quot;&gt;How do I feel about Neovim&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/i-switched-to-vim/#how-do-i-feel-about-neovim&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “How do I feel about Neovim”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I love it.&lt;/p&gt;
&lt;p&gt;Seriously.&lt;/p&gt;
&lt;p&gt;I don’t think I will ever switch back to VS Code (or a similar IDE). Here is a quick summary of the good, ugly and bad from VIM (please note I’m still a beginner and know like 1% of VIM capabilities):&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;the-good-parts&quot; tabindex=&quot;-1&quot;&gt;The good parts&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/i-switched-to-vim/#the-good-parts&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “The good parts”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;It does everything I need, but faster (a &lt;strong&gt;lot faster&lt;/strong&gt; in many cases).&lt;/li&gt;
&lt;li&gt;It starts almost instantly. Getting into a file and quickly editing a few lines is a breeze. I am using a tmux and I have a running session for other projects. So this process is usually a matter of a few strokes. The ergonomics of this flow are genuinely nice.&lt;/li&gt;
&lt;li&gt;Sleek minimalistic look.&lt;/li&gt;
&lt;li&gt;Navigation and motions in the code (once you learn it, it is hard to go back).&lt;/li&gt;
&lt;li&gt;I don’t have to use a mouse.&lt;/li&gt;
&lt;li&gt;I like to mingle with my configs, so I enjoy this part as well (it is a good opportunity to learn something new).&lt;/li&gt;
&lt;li&gt;It gives me that nerdy feeling of terminal superiority 🤓.&lt;/li&gt;
&lt;li&gt;Editor in the terminal.
&lt;ul&gt;
&lt;li&gt;I can easily edit any file from everywhere. This seems like a minor thing, but it actually is a great boost for productivity. Yes, I know I could open files in VS Code from terminal, but it took some time as well. Now I just type &lt;code&gt;vim docker-compose.yml&lt;/code&gt;, and I can start editing. And I have all my keyboard shortcuts and plugins ready.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;ugly-parts&quot; tabindex=&quot;-1&quot;&gt;Ugly parts&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/i-switched-to-vim/#ugly-parts&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Ugly parts”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;I miss multi-cursor sometimes, but less often than I would expect. I expect this will get less relevant over time as I learn how to properly use VIM for this kind o stuff (macros, search and replace, global command, etc.).&lt;/li&gt;
&lt;li&gt;I still haven’t finished my setup. Less used things do not have a proper config or do not have a keybinding.&lt;/li&gt;
&lt;li&gt;Hard to debug sometimes, why something is not working. But most of the time everything just works as intended.&lt;/li&gt;
&lt;li&gt;You must be willing to invest time to learn VIM (but you should invest your time to learn any other editor of your choice anyway).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;bad-parts&quot; tabindex=&quot;-1&quot;&gt;Bad parts&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/i-switched-to-vim/#bad-parts&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Bad parts”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Initial setup takes quite a lot of time. You could use one of the ready-to-use distros like &lt;a href=&quot;https://www.lunarvim.org/&quot;&gt;LunarVim&lt;/a&gt; or &lt;a href=&quot;https://astronvim.github.io/&quot;&gt;AstroVim&lt;/a&gt;, but I haven’t check those too much.&lt;/li&gt;
&lt;li&gt;Given the large ecosystem and nature of VIM, some plugins might conflict with each other. That may be hard to debug. So far I’ve had this issue only once, but it was still a pain to solve.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;how-did-i-do-it&quot; tabindex=&quot;-1&quot;&gt;How did I do it&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/i-switched-to-vim/#how-did-i-do-it&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “How did I do it”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-320.avif 320w, https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-480.avif 480w, https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-640.avif 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-320.webp 320w, https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-480.webp 480w, https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-640.webp 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-320.jpeg 320w, https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-480.jpeg 480w, https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-640.jpeg 640w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Boromir meme. Boromir telling is that one does not simply switch to VIM.&quot; src=&quot;https://pustelto.com/blog/i-switched-to-vim/mhUlHAPCeu-320.jpeg&quot; width=&quot;320&quot; height=&quot;188&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;So you get interested in Vim and would like to give it a shot as well? Then you will need quite a lot of patience. But in the end, I think it is not so difficult as many people (and Boromir) believe. You can learn a few basic commands to be reasonably productive in a fairly short time.&lt;/p&gt;
&lt;p&gt;Here is what I did:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I installed the VIM plugin for &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vscodevim.vim&quot;&gt;VS Code&lt;/a&gt;. It is a great for learning VIM. When you don’t know some commands, you still have a mouse and command palette at your disposal.&lt;/li&gt;
&lt;li&gt;Then I spent a month or two playing with it, learning more keybinding and gettings used to it (there are some nice &lt;a href=&quot;https://quickref.me/vim&quot;&gt;VIM cheatsheets&lt;/a&gt; that can help)&lt;/li&gt;
&lt;li&gt;When I have decided to switch I wrote down a list of features I wanted and needed in my editor.&lt;/li&gt;
&lt;li&gt;I jump on Google and YouTube to see what is possible and how to configure the stuff. I found YouTube series &lt;a href=&quot;https://www.youtube.com/playlist?list=PLhoH5vyxr6Qq41NFL4GvhFp-WLd5xzIzZ&quot;&gt;Neovim from scratch&lt;/a&gt;. The setup covered in the series have almost everything I need. So I mostly copy-paste and tweak a the config a little bit (there is a GitHub repo so you can just clone it).&lt;/li&gt;
&lt;li&gt;Once I had a feeling I have the most critical features I make the move and add more stuff as needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/i-switched-to-vim/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;That was my story with VIM in short.&lt;/p&gt;
&lt;p&gt;This is not meant to convince anyone to move to VIM. Use what you feel is the best tool for the job. In the end, you can be incredibly productive in any editor if you invest your time in learning it properly.&lt;/p&gt;
&lt;p&gt;As for me, I will stay with VIM. I don’t have a reason to go back to VS Code or any other “standard” editor. I’m really enjoying the experience and feel more productive.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Rust through the eyes of FE developer</title>
      <link>https://pustelto.com/blog/my-experience-with-rust/</link>
      <pubDate>Thu, 30 Mar 2023 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/my-experience-with-rust/</guid>
      <description>&lt;p&gt;&lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; is a programming language that has gained a lot of popularity recently. As someone who has been interested in Rust for a while, I finally decided to play with it for a month.&lt;/p&gt;
&lt;p&gt;In this article, I’ll share my experience building a command-line tool with Rust and highlight what I loved about the language, as well as what I struggled with.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-did-i-do&quot; tabindex=&quot;-1&quot;&gt;What did I do&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#what-did-i-do&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What did I do”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I decided to build a command-line tool because it was a perfect fit for my study project.&lt;/p&gt;
&lt;p&gt;First, I went through the exercise projects in the official Rust documentation, including Command line apps in Rust and chapters 12 and 20 in the Rust book on building a command-line program and a multithreaded web server, respectively.&lt;/p&gt;
&lt;p&gt;Once I finished those tutorials, I started building my own project.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;introducing-rx&quot; tabindex=&quot;-1&quot;&gt;Introducing Rx&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#introducing-rx&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Introducing Rx”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I work a lot with Nx in the terminal, but I don’t always remember all the available commands and projects. So my goal was to build a utility that could show me all the projects in the monorepo and list the available tasks for each selected project. I chose this project for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It solved my own problem (I couldn’t find something similar).&lt;/li&gt;
&lt;li&gt;There were a lot of interesting concepts in Rust that I could use to improve my skills, such as JSON parsing, file manipulation (read and write), and handling user input.&lt;/li&gt;
&lt;li&gt;It allowed me to get my hands dirty and learn as much as possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can check out the &lt;a href=&quot;https://github.com/pustelto/rx&quot;&gt;Rx code on my Github&lt;/a&gt;, but be warned that it is not production-ready code.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-i-love&quot; tabindex=&quot;-1&quot;&gt;What I love&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#what-i-love&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What I love”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;1.-very-clear-and-informative-warnings-and-errors&quot; tabindex=&quot;-1&quot;&gt;1. Very Clear and Informative Warnings and Errors&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#1.-very-clear-and-informative-warnings-and-errors&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Very Clear and Informative Warnings and Errors”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;One thing I really appreciated about Rust was its clear and informative error messages. Whenever there was a problem with my code, Rust would usually tell me exactly what the problem was and how to fix it. This was a refreshing change from the sometimes cryptic error messages I’m used to seeing in TypeScript.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-320.avif 320w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-480.avif 480w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-640.avif 640w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-960.avif 960w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-320.webp 320w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-480.webp 480w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-640.webp 640w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-960.webp 960w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-320.jpeg 320w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-480.jpeg 480w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-640.jpeg 640w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-960.jpeg 960w, https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Exapmple of error messaged shown by rust compiler. Including
precise code location when the error happened. Error message: arguments to this function are incorrect. expected `&amp;mut std::string::String`, found struct `std::string::String`, help: consider mutably borrowing here: `&amp;mut buffer`&quot; src=&quot;https://pustelto.com/blog/my-experience-with-rust/RUGX_uQDrX-320.jpeg&quot; width=&quot;320&quot; height=&quot;119&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;2.-iterators&quot; tabindex=&quot;-1&quot;&gt;2. Iterators&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#2.-iterators&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. Iterators”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Rust’s iterators are much more powerful than those in JavaScript. They are lazy evaluated by default and developers have much more utility functions available. It’s like having Lodash or Ramda as part of the standard library.&lt;/p&gt;
&lt;p&gt;In the simple example below, we take first 10 elements from infinit sequence of numbers, use filter function to keep only multiples of 3, multiply them by 3 and sum the resulting numbers into one.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; sequence &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;u8&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sequence&lt;br /&gt;                  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;take&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;x&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token closure-params&quot;&gt;&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;x&lt;span class=&quot;token closure-punctuation punctuation&quot;&gt;|&lt;/span&gt;&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token macro property&quot;&gt;assert_eq!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;54&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another nice example of iterators can be seen in &lt;a href=&quot;https://doc.rust-lang.org/rust-by-example/trait/iter.html&quot;&gt;Rust by example book&lt;/a&gt; where it is used to implement the Fibonacci sequence.&lt;/p&gt;
&lt;p&gt;A complete list of available methods can be found in the &lt;a href=&quot;https://doc.rust-lang.org/1.39.0/core/iter/trait.Iterator.html#provided-methods&quot;&gt;documentation for Iterator&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;3.-pattern-matching&quot; tabindex=&quot;-1&quot;&gt;3. Pattern Matching&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#3.-pattern-matching&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. Pattern Matching”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;For JavaScript developers, pattern matching in Rust is like a switch statements on steroids. You’ll find yourself using it a lot to return and transform values based on conditions. Rust will also force you to handle all the branches, which is great for making sure your code is robust and handles all possible scenarios.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; file_result&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;nx.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; file_result &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;match&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;ErrorKind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;NotFound&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;panic!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;The file you are looking for does not exists.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;ErrorKind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PermissionDenied&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;panic!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;You shall not pass into this file.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;ErrorKind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;InvalidInput&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;panic!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Bad arguments for the command, doc.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        _ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;panic!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Something else went wrong, but I was too lazy to deal with it.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is even &lt;a href=&quot;https://github.com/tc39/proposal-pattern-matching&quot;&gt;JS proposal to add pattern matching into the specification&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;4.-option-and-result-types&quot; tabindex=&quot;-1&quot;&gt;4. Option and Result Types&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#4.-option-and-result-types&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “4. Option and Result Types”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Most of the time, Rust will return &lt;code&gt;Option&lt;/code&gt; or &lt;code&gt;Result&lt;/code&gt; types instead of throwing an error. This is inspired by &lt;a href=&quot;https://jrsinclair.com/articles/2016/marvellously-mysterious-javascript-maybe-monad/&quot;&gt;monads&lt;/a&gt; from functional programming and allows you to handle errors in a more controlled and elegant way.&lt;/p&gt;
&lt;p&gt;It plays nicely with pattern matching, allowing you to handle various errors separately. This leads you to handle errors by default (but you can bypass this). It also allows you to compose the flow of your program more effectively. As you can see in the previous code example.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;5.-structs-and-traits&quot; tabindex=&quot;-1&quot;&gt;5. Structs and traits&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#5.-structs-and-traits&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “5. Structs and traits”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Rust’s variant of objects is a struct. A struct can have properties and methods (both static and instance methods) just like JavaScript objects. And if you want to create some generic behavior that can be reused across different structs, you can create a trait.&lt;/p&gt;
&lt;p&gt;For example, in my case, I reused a trait for parsing and deserializing JSON from 3rd party library. I simply added a trait to the struct describing my JSON shape (one line of code), and it parsed the JSON file into a strictly-typed struct that I could easily work with. Which is a awesome compared to JS/TS world.&lt;/p&gt;
&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(Serialize, Deserialize, Debug)]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;NxProjectFile&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    tags&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    targets&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Target&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// later in the code&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; project_json&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NxProjectFile&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;serde_json&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from_reader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;project_file_reader&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;6.-superb-documentation&quot; tabindex=&quot;-1&quot;&gt;6. Superb documentation&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#6.-superb-documentation&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “6. Superb documentation”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The Rust documentation is excellent and was a huge help as I learned the language. I was able to find most of what I needed in the docs, and most of the time, I read it in my editor as a tooltip when I needed to check how a method works.&lt;/p&gt;
&lt;p&gt;I also found the documentation to be really good for the crates I worked with (Rust’s version of NPM packages).&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;7.-support-in-neovim-is-great-%F0%9F%98%85&quot; tabindex=&quot;-1&quot;&gt;7. Support in Neovim is great 😅&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#7.-support-in-neovim-is-great-%F0%9F%98%85&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “7. Support in Neovim is great 😅”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;As someone who uses Neovim as their primary text editor, I was happy to find that Rust has great tooling support in Neovim.&lt;/p&gt;
&lt;p&gt;I was able to set up my environment quickly and start working with Rust in just a few minutes thanks to this &lt;a href=&quot;https://www.youtube.com/watch?v=Mccy6wuq3JE&amp;amp;ab_channel=TJDeVries&quot;&gt;video Rust setup for Neovim&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-i-need-to-get-used-to&quot; tabindex=&quot;-1&quot;&gt;What I need to get used to&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#what-i-need-to-get-used-to&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What I need to get used to”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;1.-ownership&quot; tabindex=&quot;-1&quot;&gt;1. Ownership&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#1.-ownership&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Ownership”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I still feel a little confused about the ownership logic. When you are passing a variable to a lower scope and when you only reference it (function in lower scope will borrow it).&lt;/p&gt;
&lt;p&gt;Luckily, Rust will often tell you what to do to fix the errors.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;2.mandatory-semicolons.&quot; tabindex=&quot;-1&quot;&gt;2.Mandatory semicolons.&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#2.mandatory-semicolons.&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2.Mandatory semicolons.”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;In Rust, every statement must end with a semicolon. Otherwise, the expression is considered a return statement (if it is the last expression in the block scope). As a JavaScript developer, I’m not used to typing semicolons anymore, and Prettier handles this for me. So I often forget to type it which is annoying.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;3.-low-level-nature-of-the-language&quot; tabindex=&quot;-1&quot;&gt;3. Low-level nature of the language&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#3.-low-level-nature-of-the-language&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. Low-level nature of the language”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;When working with Rust you have to sometimes deal with the low-level nature of the language, which is something I’m not used to as a front-end developer.&lt;/p&gt;
&lt;p&gt;For example, I wanted to write to a file and overwrite its content. But instead, Rust did always append the content. After a little googling I found out that when you read from a file first, you have to rewind a pointer back to the beginning of the file. Otherwise write operation will append to content to the file.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/my-experience-with-rust/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I enjoyed the short time with Rust. It was a nice experience, and I had fun learning it. I think it takes the good parts from both object-oriented programming and functional programming.&lt;/p&gt;
&lt;p&gt;I will probably return to it in the future to play with it a little bit more. Most likely, to polish the Rx utility or maybe when I need to build another CLI utility or when playing with WebAssembly.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to implement Supabase auth in a browser extension</title>
      <link>https://pustelto.com/blog/supabase-auth/</link>
      <pubDate>Fri, 17 May 2024 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/supabase-auth/</guid>
      <description>&lt;p&gt;Supabase promise an easy authentication without a hasle.&lt;/p&gt;
&lt;p&gt;Turns out there was a lot to figure out.&lt;/p&gt;
&lt;p&gt;While building &lt;a href=&quot;https://clipio.app/&quot;&gt;Clipio&lt;/a&gt;, I had to implement authentication in a browser extension without a regular website. I decide to use &lt;a href=&quot;https://supabase.com/&quot;&gt;Supabase&lt;/a&gt; and Supabase UI to setup everthing quickly. And while Supabase does a lot of heavy lifting, there was still a lot of work. And resources for this topic are rare.&lt;/p&gt;
&lt;p&gt;In this article, I’ll show you how I handle authentication in browser extension running in Chrome and Firefox.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick tip:&lt;/strong&gt; Upload your extension into the Chrome and Mozilla stores (a signed self-hosted extension will do as well) as soon as possible to obtain stable extension IDs. You will need those for the OAuth setup. So doing this early will save you trouble changing the IDs and redirect URLs later.&lt;/p&gt;
&lt;p&gt;Since this article is longer than usually here is the outline for a quick navigation:&lt;/p&gt;
&lt;nav class=&quot;toc&quot;&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#initial-attempt&quot;&gt;Initial attempt&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#what-i-was-missing&quot;&gt;What I was missing&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#basic-setup&quot;&gt;Basic setup&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#extension-permissions&quot;&gt;Extension permissions&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#preparing-the-google-login&quot;&gt;Preparing the Google login&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#google-chrome%3A&quot;&gt;Google Chrome:&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#mozilla-firefox&quot;&gt;Mozilla Firefox&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#connect-google-auth-to-supabase&quot;&gt;Connect Google auth to Supabase&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#email-and-password-login&quot;&gt;Email and password login&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#logout&quot;&gt;Logout&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#session-refreshing&quot;&gt;Session refreshing&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/supabase-auth/#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;initial-attempt&quot; tabindex=&quot;-1&quot;&gt;Initial attempt&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#initial-attempt&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Initial attempt”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;When looking for resources I really couldn’t find any complete guide. The best I could find is the article &lt;a href=&quot;https://beastx.ro/supabase-login-with-oauth-in-chrome-extensions&quot;&gt;Supabase login with OAuth for Chrome extensions&lt;/a&gt; from &lt;a href=&quot;https://twitter.com/sebestin_dragos&quot;&gt;Dragos Sebestin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I managed to set up the basic authentication flow based on the amazing guide mentioned above. However, there was still a lot of work that was not covered.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;what-i-was-missing&quot; tabindex=&quot;-1&quot;&gt;What I was missing&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#what-i-was-missing&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What I was missing”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Email login &amp;amp; Password Reset&lt;/strong&gt;: handling email-based authentication requires additional changes and updates (mostly for password reset flow).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session Management&lt;/strong&gt;: How to handle refreshing the session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logout Functionality&lt;/strong&gt;: Auth flow in the browser extension is spread across several origins. When the user logout we have to ensure all data is cleared.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-Browser Support&lt;/strong&gt;: When building an extension that can run in multiple browsers (eg. Chrome and Firefox) requires a few more tweaks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supabase UI&lt;/strong&gt;: I decided to use Supabase UI to have the auth flow build quickly, but that requires some tweaking since some flow has to be handled manually from the parent component (eg. password reset).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;basic-setup&quot; tabindex=&quot;-1&quot;&gt;Basic setup&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#basic-setup&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Basic setup”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Following the guide mentioned above, we will open the auth flow in a new tab. In the case of the Clipio, that means checking if the user is logged in before the extension opens. If not the auth flow is initiated.&lt;/p&gt;
&lt;p&gt;This code lives in the background script and will look like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onClicked&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;tab&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;openClipio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tab&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;openLoginPage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tabs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runtime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./src/pages/auth.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;openClipio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tab&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; storageContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;local&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; refresh_token &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; storageContent&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;refreshSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; refresh_token &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;local&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;signOut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;openLoginPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tabs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tab&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;open&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s build the auth screen that we will redirect the user to.&lt;/p&gt;
&lt;p&gt;The initial version of our auth page will look like this. Please note I have removed the styles and unnecessary markup for the sake of brevity.&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; queryParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  access_type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;offline&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  prompt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;consent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Auth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setSession&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;setSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; subscription &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onAuthStateChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;setSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runtime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SET_AUTH&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; auth&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unsubscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Login&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SupabaseAuth&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token attr-name&quot;&gt;supabaseClient&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;supabase&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token attr-name&quot;&gt;providers&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;google&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token attr-name&quot;&gt;queryParams&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;queryParams&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token attr-name&quot;&gt;redirectTo&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;identity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRedirectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Successfully logged in&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;You can now close this tab.&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example is taken from the official &lt;a href=&quot;https://supabase.com/docs/guides/auth/quickstarts/react&quot;&gt;Supabase guide for React&lt;/a&gt; and tweaked for the browser extension context.&lt;/p&gt;
&lt;p&gt;Now we need to add a handler in the extension’s background script watch for tabs change and finish the auth flow when the extension’s redirect URL is open (code below is taken from Dragos guide).&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tabs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onUpdated&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tabId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; changeInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;changeInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;identity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRedirectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;finishOAuthSignIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;changeInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;finishUserOAuth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;handling user OAuth callback ...&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supabase &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;secrets&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; secrets&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// extract tokens from hash&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hashMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseUrlHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; access_token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hashMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;access_token&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; refresh_token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hashMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;refresh_token&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;access_token &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;refresh_token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;no supabase tokens found in URL hash&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// check if they work&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      access_token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      refresh_token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Persist session to storage - background script can become inactive and the session will be lost,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// we need to be able to recover it by storing the tokens in extension&#39;s storage.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;local&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// finally redirect to a post-oauth page&lt;/span&gt;&lt;br /&gt;    browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tabs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://myapp.com/user-login-success/&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;finished handling user OAuth callback&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseUrlHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hashParts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&amp;amp;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hashMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    hashParts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;part&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;=&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; hashMap&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;extension-permissions&quot; tabindex=&quot;-1&quot;&gt;Extension permissions&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#extension-permissions&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Extension permissions”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;To make this all work, we have to add these permissions to the extension manifest:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;permissions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;identity&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;tabs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;storage&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Identity permission is necessary to have access to redirect URL&lt;/li&gt;
&lt;li&gt;Tabs permission is required to be able to observe changes in tabs URLs (so we can detect navigation to redirect URLs).&lt;/li&gt;
&lt;li&gt;Storage will be used to store the user session in the extension context&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;preparing-the-google-login&quot; tabindex=&quot;-1&quot;&gt;Preparing the Google login&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#preparing-the-google-login&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Preparing the Google login”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;With all the code setup, all we have to do now is to enable Google auth in our Supabase project.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;google-chrome%3A&quot; tabindex=&quot;-1&quot;&gt;Google Chrome:&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#google-chrome%3A&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Google Chrome:”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;For Google auth in Chrome I suggest taking a look at this &lt;a href=&quot;https://supabase.com/docs/guides/auth/social-login/auth-google?queryGroups=platform&amp;amp;platform=chrome-extensions#configuration-chrome-extension&quot;&gt;guide in Supabase docs&lt;/a&gt;. Since we are using Supabase UI we can skip the code part and skip down to the Configuration section:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure the &lt;a href=&quot;https://console.cloud.google.com/apis/credentials/consent&quot;&gt;OAuth consent screen in Google Cloud Console (GCC)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Add new credentials of type &lt;em&gt;Chrome extension&lt;/em&gt; at the &lt;a href=&quot;https://console.cloud.google.com/apis/credentials&quot;&gt;credentials screen&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;mozilla-firefox&quot; tabindex=&quot;-1&quot;&gt;Mozilla Firefox&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#mozilla-firefox&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Mozilla Firefox”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;To support Google auth in Firefox we have to create a new credentials on GCC.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visit the Credentials screen again and create a new credential.&lt;/li&gt;
&lt;li&gt;As a type select &lt;em&gt;Web application&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Add your extension’s redirect URL (from Firefox build) into the Authorized origins input. You can get the correct redirect URL by calling &lt;code&gt;browser.identity.getRedirectURL()&lt;/code&gt;. It will look like this: &lt;code&gt;https://a1b3223966f841b1a78844dc3ac27f75.extensions.allizom.org&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add a callback URL from Supabase (Auth → Providers → Google) into the redirect URI field.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note for configuring OAuth: If you request only name and email and no sensitive scopes you don’t go through Google verification for your app.&lt;/p&gt;
&lt;p&gt;However, if you upload the app logo you will be forced to go through the verification. This came by surprise and &lt;a href=&quot;https://twitter.com/pustelto/status/1778755202264469666&quot;&gt;I didn’t initially know this was due to the app logo&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;connect-google-auth-to-supabase&quot; tabindex=&quot;-1&quot;&gt;Connect Google auth to Supabase&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#connect-google-auth-to-supabase&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Connect Google auth to Supabase”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Add the OAuth ID and Client secret to Supabase in the Providers section (use those from Firefox Credentials).&lt;/li&gt;
&lt;li&gt;In the Authorized Client IDs field, you have to insert a comma-separated list of all of your client credentials IDs - one for the Chrome extension and one for the Firefox.&lt;/li&gt;
&lt;li&gt;You also have to add redirect URLs to your Supabase project. Go to Authentication → URL Configuration and add both redirect URLs (for Chrome and Firefox). Please note that unbundled extensions during dev will have different redirect URLs than the production build.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;email-and-password-login&quot; tabindex=&quot;-1&quot;&gt;Email and password login&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#email-and-password-login&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Email and password login”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;When using Supabase UI, email signup and login will work out of the box. You only have to configure redirect URLs in your project as we did for Google auth. So if you have finished the previous section you are good to go.&lt;/p&gt;
&lt;p&gt;The problem will arise when a users need to reset a forgotten password. We have to make a few changes to our &lt;code&gt;Auth.tsx&lt;/code&gt; component and background script to handle the click on the reset link.&lt;/p&gt;
&lt;p&gt;The reset link will use redirect URLs, but it will contain one more parameter: &lt;code&gt;type=recovery&lt;/code&gt; to help us recognize this is a password reset (Supabase adds this parameter automatically).&lt;/p&gt;
&lt;p&gt;We will update our &lt;code&gt;tabs.onUpdated&lt;/code&gt; listener in the background script to check for the presence of this param.&lt;/p&gt;
&lt;pre class=&quot;language-diff-javascript&quot;&gt;&lt;code class=&quot;language-diff-javascript&quot;&gt;browser.tabs.onUpdated.addListener((tabId, changeInfo) =&gt; {&lt;br /&gt;&lt;span class=&quot;token unchanged language-javascript&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;changeInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;identity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRedirectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted language-javascript&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hashMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseUrlHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;changeInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; redirectType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hashMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;type&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;redirectType &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;recovery&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tabs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getLoginPageUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hashMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged language-javascript&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token function&quot;&gt;finishOAuthSignIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;changeInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the param is present we will append it together with other params to the auth page URL and open it to finishe the password reset flow.&lt;/p&gt;
&lt;p&gt;On the auth page we have to check if we have those params in the URL. If yes, there are a couple of things we have to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Take access and refresh tokens from the URL and create a new session&lt;/li&gt;
&lt;li&gt;Change the &lt;code&gt;view&lt;/code&gt; prop to &lt;code&gt;update_password&lt;/code&gt; value in &lt;code&gt;&amp;lt;SupabaseAuth/&amp;gt;&lt;/code&gt; component.&lt;/li&gt;
&lt;li&gt;Once the &lt;code&gt;USER_UPDATED&lt;/code&gt; event is detected in &lt;code&gt;onAuthStateChanged&lt;/code&gt; listener we will change the URL to remove the params. This will show a success confirmation message.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-diff-tsx&quot;&gt;&lt;code class=&quot;language-diff-tsx&quot;&gt;export function Auth() {&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted language-tsx&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Get tab URL from the window object&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tabUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tabUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;type&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; access_token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tabUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;access_token&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; refresh_token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tabUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;refresh_token&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged language-tsx&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setSession&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged language-tsx&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted language-tsx&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;access_token &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; refresh_token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; access_token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; refresh_token &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        &lt;span class=&quot;token function&quot;&gt;setSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted language-tsx&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;    supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;      &lt;span class=&quot;token function&quot;&gt;setSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged language-tsx&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; subscription &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onAuthStateChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &lt;span class=&quot;token function&quot;&gt;setSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged language-tsx&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runtime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SET_AUTH&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; auth&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted language-tsx&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        &lt;span class=&quot;token comment&quot;&gt;// Redirect to the auth page without the params to show the success message&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;USER_UPDATED&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;          window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;tabUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;tabUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged language-tsx&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged language-tsx&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unsubscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged language-tsx&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Login&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       &amp;lt;SupabaseAuth &lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted language-tsx&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         view&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;recovery&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;update_password&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged language-tsx&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         supabaseClient&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;supabase&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         providers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;google&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         queryParams&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;queryParams&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         redirectTo&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;identity&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRedirectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Successfully logged in&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;You can now close this tab.&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this we have finished the Google/email password flows and our extension is ready to accept the users.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;logout&quot; tabindex=&quot;-1&quot;&gt;Logout&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#logout&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Logout”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Logout needs a few adjustments as well. We call &lt;code&gt;supabase.auth.signOut()&lt;/code&gt; from the background script and clear all user data here. But if we try to login now our Auth page will look like the user is still logged in. The problem is that our Auth page, while part of the extension, is actually treated as different webpage - it has a different context and storage namespace in the browser.&lt;/p&gt;
&lt;p&gt;So if we clear the session in the background script we still have access and refresh tokens available in the local storage on the Auth page. This is not good, as it prevents the user to login and can be a potential security issue.&lt;/p&gt;
&lt;p&gt;I have decided to solve this by adding one more extra page, this time for logout. It just shows the message to the users that they have been logged out successfully. Once this page is opened we simply call &lt;code&gt;localStorage.clear()&lt;/code&gt; to remove the user’s data from here as well.&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;LogoutConfirmation&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    localStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;You have been logout from Clipio&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;You can now close this tab.&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;session-refreshing&quot; tabindex=&quot;-1&quot;&gt;Session refreshing&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#session-refreshing&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Session refreshing”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;The last thing we have to solve is session persistence and token refreshing. Background scripts can become inactive and any &lt;a href=&quot;https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle#idle-shutdown&quot;&gt;global or in memory variables will be lost&lt;/a&gt;. We have to persist the session in the extension’s storage and refresh the token when needed.&lt;/p&gt;
&lt;p&gt;For Clipio I refresh the token every time users open the extension and logout the user if the refresh method returns any error.&lt;/p&gt;
&lt;p&gt;However, during development, I have encountered the issue that the refresh token has become invalid after some time. After a little digging, I have found out that the Supabase is refreshing the token after some time in the background script automatically as well, thus making my persisted refresh token invalid.&lt;/p&gt;
&lt;p&gt;I have solved this issue by adding &lt;code&gt;onAuthStateChanged&lt;/code&gt; listener to the background script and moved most of the persistence logic here:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;supabase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onAuthStateChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; session&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SIGNED_IN&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; event &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;TOKEN_REFRESHED&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;     browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;local&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SIGNED_OUT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;local&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will correctly handle all cases when refreshing of token and persist the tokens for the future.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/supabase-auth/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Implementing authentication in a browser extension without a traditional website took me more time than I thought. That’s why I have summarize all the issues in this guide and I hope it will help others.&lt;/p&gt;
&lt;p&gt;If you have any questions or run into issues, feel free to reach out. And if you like this article, please share it on social media. Happy coding!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to add Shadcn UI into an existing React app in Nx Monorepo</title>
      <link>https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/</link>
      <pubDate>Sat, 21 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/</guid>
      <description>&lt;p&gt;When integrating &lt;a href=&quot;https://ui.shadcn.com/&quot;&gt;Shadcn UI&lt;/a&gt; into an Nx monorepo you can stumble upon few issues.&lt;/p&gt;
&lt;p&gt;At least I did when I decided to add Shadcn UI into &lt;a href=&quot;https://clipio.app/&quot;&gt;Clipio&lt;/a&gt;. Due to Nx structure, running &lt;code&gt;shadcn init&lt;/code&gt; will not work. So I have decided to write this article to help others have a smooth setup journey for Shadcn UI and Nx monorepo using pure React.&lt;/p&gt;
&lt;p&gt;Let’s get into it.&lt;/p&gt;
&lt;aside&gt;&lt;strong&gt;NOTE:&lt;/strong&gt;  I&#39;m not working with Next.js or any other framework. Since I&#39;m building a browser extension, I&#39;m using pure React. So this article focuses on that setup.&lt;/aside&gt;
&lt;p&gt;Most of the time, you can follow the steps in the &lt;a href=&quot;https://ui.shadcn.com/docs/installation/manual&quot;&gt;Shadcn UI documentation for manual installation&lt;/a&gt;. I will briefly describe the steps below for easy follow-up and highlight extra steps relevant for Nx monorepo.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;1.-bootstrap-the-react-ui-library&quot; tabindex=&quot;-1&quot;&gt;1. Bootstrap the React UI Library&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/#1.-bootstrap-the-react-ui-library&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Bootstrap the React UI Library”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;In my setup, I want to have Nx library which will contain Shadcn UI components, I will call it &lt;code&gt;ui&lt;/code&gt;. Later I can easily import it to other libs or apps in my monorepo.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;add-nx-library&quot; tabindex=&quot;-1&quot;&gt;Add Nx Library&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/#add-nx-library&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Add Nx Library”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;You can easily add your new library for Shadcn UI using the Nx generator. It will do most of the heavy lifting for you; just double-check the correct imports after the process is complete.&lt;/p&gt;
&lt;p&gt;Run this command in your terminal:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; nx g @nx/react:library ui &lt;span class=&quot;token parameter variable&quot;&gt;--directory&lt;/span&gt; libs/ui &lt;span class=&quot;token parameter variable&quot;&gt;--compiler&lt;/span&gt; swc &lt;span class=&quot;token parameter variable&quot;&gt;--bundler&lt;/span&gt; vite&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, delete the content of the &lt;code&gt;src&lt;/code&gt; folder in the UI lib we just generated, and create two new folders:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ui&lt;/code&gt; where the Shadcn UI components will live&lt;/li&gt;
&lt;li&gt;&lt;code&gt;styles&lt;/code&gt; where we will put the global styles for the UI lib&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add a &lt;code&gt;global.css&lt;/code&gt; file to the styles folder. We will update it later.&lt;/p&gt;
&lt;aside&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Names of folders and files are not mandatory; feel free to name them whatever you like, e.g., components instead of ui. But you will have to update the names in other places as well.&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;update-typescript-config-paths&quot; tabindex=&quot;-1&quot;&gt;Update TypeScript Config Paths&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/#update-typescript-config-paths&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Update TypeScript Config Paths”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Now, go to the &lt;code&gt;tsconfig.base.json&lt;/code&gt; file in the root of your monorepo. You should see the new path for the UI lib:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;paths&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;@shadcn-in-nx/ui&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;libs/ui/src/index.ts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;other paths&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change it to this format:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;paths&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;@shadcn-in-nx/ui&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;libs/ui/src/ui&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;other paths&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using an import path in this format will allow you to import individual components into your codebase like this:&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Button &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@shadcn-in-nx/ui/button&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;2.-set-up-tailwind-css&quot; tabindex=&quot;-1&quot;&gt;2. Set Up Tailwind CSS&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/#2.-set-up-tailwind-css&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. Set Up Tailwind CSS”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Install the additional dependencies as described in the Shadcn UI documentation:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; tailwindcss-animate class-variance-authority clsx tailwind-merge&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add icons to your project based on your preferred style of Shadcn UI components. I chose the New York style, so I’m using Radix UI icons:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; @radix-ui/react-icons&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, set up Tailwind CSS in your monorepo. Nx provides a handy generator for this task. Run this command for both the UI lib where Shadcn UI will live and the main app:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; nx g @nx/react:setup-tailwind&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When initializing Tailwind in the UI lib, the Nx generator might complain it can’t find a stylesheet to update. Don’t worry about it. Copy the Tailwind styles from &lt;a href=&quot;https://ui.shadcn.com/docs/installation/manual#configure-styles&quot;&gt;Step 6 in the Shadcn UI documentation&lt;/a&gt; into the &lt;code&gt;global.css&lt;/code&gt; file you created earlier.&lt;/p&gt;
&lt;p&gt;Also, update the Tailwind config in your UI lib based on instructions from &lt;a href=&quot;https://ui.shadcn.com/docs/installation/manual#configure-tailwindconfigjs&quot;&gt;Step 5 in the docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next, update the Tailwind config in your app. Import the Tailwind config from the UI lib and merge it with the app’s config. Here’s how you can do it:&lt;/p&gt;
&lt;pre class=&quot;language-diff-javascript&quot;&gt;&lt;code class=&quot;language-diff-javascript&quot;&gt;const { createGlobPatternsForDependencies } = require(&#39;@nx/react/tailwind&#39;);&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted language-javascript&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; TailwindConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;../../libs/ui/tailwind.config&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;const { join } = require(&#39;path&#39;);&lt;br /&gt;&lt;br /&gt;/** @type {import(&#39;tailwindcss&#39;).Config} */&lt;br /&gt;module.exports = {&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted language-javascript&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;TailwindConfig&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged language-javascript&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted language-javascript&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;TailwindConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged language-javascript&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createGlobPatternsForDependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted language-javascript&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You have to inject styles from &lt;code&gt;global.css&lt;/code&gt; into your app, otherwise Shadcn UI components will have broken styles. I have decided to use path alias for this as well. I inserted this line into &lt;code&gt;paths&lt;/code&gt; property in &lt;code&gt;tsconfig.base.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;@shadcn-test/styles/*&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;libs/ui/src/styles/*&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in your app you will inject it like this:&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@shadcn-test/styles/global.css&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;3.-create-utils-lib-with-cn&quot; tabindex=&quot;-1&quot;&gt;3. Create utils lib with &lt;code&gt;cn&lt;/code&gt;&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/#3.-create-utils-lib-with-cn&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. Create utils lib with cn”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Next step is to create a &lt;code&gt;cn&lt;/code&gt; utility to merge styles in components. For that I have created a new lib in my monorepo called &lt;code&gt;utils&lt;/code&gt; and placed the function here (I have other general purpose utility functions here, so this makes sense for me). In this case I have left the TS path in the &lt;code&gt;tsconfig.base.json&lt;/code&gt; as is: &lt;code&gt;&amp;quot;@shadcn-in-nx/utils&amp;quot;: [&amp;quot;libs/utils/src/index.ts&amp;quot;]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want to generate new lib, run this Nx command: &lt;code&gt;pnpm nx g @nx/js:lib utils&lt;/code&gt; and follow setup instructions. Once generated copy &lt;code&gt;cn&lt;/code&gt; from &lt;a href=&quot;https://ui.shadcn.com/docs/installation/manual#add-a-cn-helper&quot;&gt;Shadcn UI docs&lt;/a&gt; inside.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;4.-initialize-shadcn-ui&quot; tabindex=&quot;-1&quot;&gt;4. Initialize Shadcn UI&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/#4.-initialize-shadcn-ui&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “4. Initialize Shadcn UI”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Last step before we can add components is to create &lt;code&gt;components.json&lt;/code&gt; file into the root of monorepo. Below is example &lt;code&gt;components.json&lt;/code&gt; file for a reference:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://ui.shadcn.com/schema.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;style&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;new-york&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;rsc&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;tsx&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;tailwind&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;libs/ui/tailwind.config.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;css&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;libs/ui/src/styles/global.css&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;baseColor&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;slate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;cssVariables&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;aliases&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;components&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@shadcn-in-nx/ui&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;ui&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@shadcn-in-nx/ui&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;utils&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@shadcn-in-nx/utils&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;ui&lt;/code&gt; alias. This one is important to ensure generated components are placed in correct folder.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;5.-add-components&quot; tabindex=&quot;-1&quot;&gt;5. Add Components&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/#5.-add-components&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “5. Add Components”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Now you’re all set to generate Shadcn UI components from the command line. Use this command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;TS_NODE_PROJECT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;tsconfig.base.json pnpx shadcn@latest &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;optional component name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;aside&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Since we&#39;re in a monorepo, we have to specify &lt;code&gt;TS_NODE_PROJECT&lt;/code&gt; to point to the &lt;code&gt;tsconfig.base.json&lt;/code&gt; file. Shadcn UI will look for tsconfig.json by default, but that is not available in the monorepo root.&lt;/aside&gt;
&lt;p&gt;Your components will be generated in the UI lib and you can easily import them to the main app as well. Import across the components will use path alias as well, which is not optimal in Nx, but I haven’t found any way how to change this. Nevertheless, even if Nx eslint might complain (if you install it) everything will work.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/adding-shadcnui-to-nx-monorepo/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I hope this guide helps you smoothly integrate Shadcn UI into your Nx monorepo. While adding it isn’t overly difficult, there are some Nx-specific steps that can trip you up. By following these instructions, you should be able to get everything working as expected in no time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to do an efficient and valuable code review</title>
      <link>https://pustelto.com/blog/how-to-do-better-code-review/</link>
      <pubDate>Mon, 02 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/how-to-do-better-code-review/</guid>
      <description>&lt;p&gt;Do you like code reviews? Or do you hate them?&lt;/p&gt;
&lt;p&gt;Have you ever received a code review that left you scratching your head? Have you been wondering if the reviewer even looked at your code? Or worse, have you been on the other side, struggling to provide valuable feedback?&lt;/p&gt;
&lt;p&gt;Well, you’re not alone.&lt;/p&gt;
&lt;p&gt;Code reviews are often a pain in the ass. They are a form of asynchronous dialog, which makes them really difficult to nail. Lost information, misunderstandings, and unclear intent. All these things can slow down the review and lead to a ping-pong of messages.&lt;/p&gt;
&lt;p&gt;In this article, I will share some tips that help me provide code reviews.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Be specific. Always say What, Why, and How. Try to help, not show your ego.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;purpose-of-the-code-reviews&quot; tabindex=&quot;-1&quot;&gt;Purpose of the code reviews&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#purpose-of-the-code-reviews&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Purpose of the code reviews”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;If code reviews are so challenging, why bother with them at all? The truth is, that code reviews bring several key benefits to a team:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A fresh perspective on the code:&lt;/strong&gt; When you’re deeply immersed in your work, it’s easy to overlook mistakes or miss important details. A second set of eyes can help catch those issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Knowledge sharing:&lt;/strong&gt; Code reviews allow teammates to learn from each other’s domain expertise and coding patterns. Reviewers can also share their insights and suggest improvements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adding context:&lt;/strong&gt; Senior developers, in particular, may have additional context about the change that can be valuable. The code review process is a great opportunity to share that knowledge.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some might say that code reviews are meant to catch bugs or bad practices. However, I believe these tasks are better handled by tools like linters, formatters, and automated tests — not humans.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: That said, I’m not claiming code reviews are the only way to achieve these goals. Many teams use pair programming to address these needs effectively and are perfectly happy without code reviews. If your team operates this way, congratulations! But if you do rely on code reviews, keep reading.&lt;/p&gt;
&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;how-to-prepare-code-for-a-smooth-review&quot; tabindex=&quot;-1&quot;&gt;How to prepare code for a smooth review&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#how-to-prepare-code-for-a-smooth-review&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “How to prepare code for a smooth review”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;As an author, you play a crucial role in facilitating an effective review process. By following the tips below, you can help reviewers provide valuable feedback, save a lot of time, and streamline the entire process.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;1.-create-smaller%2C-focused-prs&quot; tabindex=&quot;-1&quot;&gt;1. Create smaller, focused PRs&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#1.-create-smaller%2C-focused-prs&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Create smaller, focused PRs”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-320.avif 320w, https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-480.avif 480w, https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-640.avif 640w, https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-320.webp 320w, https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-480.webp 480w, https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-640.webp 640w, https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-320.jpeg 320w, https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-480.jpeg 480w, https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-640.jpeg 640w, https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Review 10 lines of code: find 10 issues, review 500 lines of code: looks good to me.&quot; src=&quot;https://pustelto.com/blog/how-to-do-better-code-review/L9X1R3RQdk-320.jpeg&quot; width=&quot;320&quot; height=&quot;213&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;Breaking down your changes into smaller pull requests can significantly improve the review process. Smaller PRs are easier to read and understand, increasing the likelihood of a quicker review. Use feature flags to merge changes more frequently while keeping new functionality hidden until it’s ready for release.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;&lt;span aria-hidden=&quot;true&quot;&gt;🎓 &lt;/span&gt;&lt;strong&gt;TIP&lt;/strong&gt;: A huge boost to my efforts in making short merge requests was an article with a great tip about &lt;a href=&quot;https://www.codetinkerer.com/2023/10/01/stacked-branches-with-vanilla-git.html&quot;&gt;Git’s native feature to enable ‘stacked branches’&lt;/a&gt; from Michael Kropat. Together with &lt;a href=&quot;https://github.com/jesseduffield/lazygit&quot;&gt;Lazygit&lt;/a&gt;, this is an amazing combo I highly recommend you try.&lt;/p&gt;
&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;2.-provide-good-descriptions&quot; tabindex=&quot;-1&quot;&gt;2. Provide good descriptions&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#2.-provide-good-descriptions&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. Provide good descriptions”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Before you open a book, you always read the summary on the back cover.&lt;/p&gt;
&lt;p&gt;A well-crafted PR description is just like that. It’s essential for context. You should include the what, how, and why of your changes. The description should be the first guide for reviewers, showing them what has changed and what they should focus on.&lt;/p&gt;
&lt;p&gt;Of course, a great PR description depends on the context of the changes. Are you changing the color of a button? Then a one-liner is probably enough. Have you spent 2 days hunting down a nasty bug? Then you should probably write an in-depth description of the bug, your process, and why that one line fixed it.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;&lt;span aria-hidden=&quot;true&quot;&gt;🎓 &lt;/span&gt;&lt;strong&gt;TIP&lt;/strong&gt;: Are you making UI changes? Add screenshots or videos of the changes. This can help reviewers understand the visual impact of your changes and see them without running the code locally. A preview link is helpful too, if you use a service that supports it.&lt;/p&gt;
&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;3.-highlight-specific-areas-for-feedback&quot; tabindex=&quot;-1&quot;&gt;3. Highlight Specific Areas for Feedback&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#3.-highlight-specific-areas-for-feedback&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. Highlight Specific Areas for Feedback”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;You, as an author, can add comments to the code as well. Use this power.&lt;/p&gt;
&lt;p&gt;Otherwise, reviewers might end up nitpicking unimportant things and missing the important parts of the code. Guide your reviewers by pointing out areas where you’d particularly appreciate their input.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br /&gt;
&lt;em&gt;I’ve implemented a new caching mechanism in the UserService class. However, I’m not entirely sure if this is the best way to do it in our codebase, and I don’t like it. But I couldn’t figure out a better way. Any suggestions for improvement would be greatly appreciated.&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;how-to-provide-a-good-code-review&quot; tabindex=&quot;-1&quot;&gt;How to provide a good code review&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#how-to-provide-a-good-code-review&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “How to provide a good code review”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;As a reviewer, your role is to provide constructive feedback that improves code quality and fosters a positive team environment. Here are some guidelines to help you give more effective reviews:&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;1.-does-my-comment-bring-value%3F&quot; tabindex=&quot;-1&quot;&gt;1. Does my comment bring value?&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#1.-does-my-comment-bring-value%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Does my comment bring value?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Before you submit a comment, ask yourself this question: “Does my comment bring any value?”.&lt;/p&gt;
&lt;p&gt;If it doesn’t, consider whether it’s worth mentioning at all. Avoid nitpicking on trivial issues that don’t impact the code’s functionality or readability but are solely your personal preference. I ask this question before each comment and trust me, there is a huge number of comments I have never sent. Just because you would solve the problem differently doesn’t mean the other solution is wrong.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;2.-ask-questions-for-clarity&quot; tabindex=&quot;-1&quot;&gt;2. Ask Questions for Clarity&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#2.-ask-questions-for-clarity&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. Ask Questions for Clarity”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;If something isn’t clear, don’t hesitate to ask for clarification. This can lead to valuable discussions and improvements in the code or documentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br /&gt;
&lt;em&gt;Why are you binding DOM events to the element using its reference, instead of using React’s native handling? Is there any particular reason?&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;3.-provide-context-and-explanations&quot; tabindex=&quot;-1&quot;&gt;3. Provide Context and Explanations&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#3.-provide-context-and-explanations&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. Provide Context and Explanations”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;When suggesting a change, explain the reasoning behind your recommendations. This helps the author understand the “why” and not just the “what.” And don’t forget to suggest “how” as well. What I really hate is when someone says “I don’t like this” without any explanation or suggestion on how to improve it. And I guess I’m not the only one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br /&gt;
&lt;em&gt;This reduce method is too complex and very hard to read. I would suggest either moving it to a separate function covered by tests. Or you can instead use a more functional approach and transform it into several atomic steps using a combination of &lt;code&gt;filter&lt;/code&gt; and &lt;code&gt;flatMap/map&lt;/code&gt; functions.&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;4.-balance-criticism-with-praise&quot; tabindex=&quot;-1&quot;&gt;4. Balance Criticism with Praise&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#4.-balance-criticism-with-praise&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “4. Balance Criticism with Praise”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-320.avif 320w, https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-480.avif 480w, https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-640.avif 640w, https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-960.avif 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-320.webp 320w, https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-480.webp 480w, https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-640.webp 640w, https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-960.webp 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-320.jpeg 320w, https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-480.jpeg 480w, https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-640.jpeg 640w, https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-960.jpeg 960w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Developer trying to say something positive during code review: At least we don&#39;t have to obfuscate the code&quot; src=&quot;https://pustelto.com/blog/how-to-do-better-code-review/paRZj4WA3v-320.jpeg&quot; width=&quot;320&quot; height=&quot;134&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;Acknowledge good practices and clever solutions, especially if you added a bunch of change requests. This creates a positive atmosphere and encourages the author.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;5.-focus-on-code%2C-not-the-coder&quot; tabindex=&quot;-1&quot;&gt;5. Focus on code, Not the coder&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#5.-focus-on-code%2C-not-the-coder&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “5. Focus on code, Not the coder”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Keep your comments focused on the code itself, not the person who wrote it. Instead of saying &lt;em&gt;“You didn’t handle this edge case,”&lt;/em&gt; try &lt;em&gt;“This edge case doesn’t seem to be handled. Consider adding a check for…”&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;6.-prioritize-your-feedback&quot; tabindex=&quot;-1&quot;&gt;6. Prioritize Your Feedback&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#6.-prioritize-your-feedback&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “6. Prioritize Your Feedback”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Distinguish between must-fix issues and nice-to-have improvements. This helps the author focus on critical changes first. You can use labels or prefixes to indicate the importance of your comments, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Critical&lt;/strong&gt;: This method may cause a null pointer exception under certain conditions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suggestion&lt;/strong&gt;: Consider using lazy evaluation for better performance in this function.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nitpick&lt;/strong&gt;: A &lt;code&gt;for...of&lt;/code&gt; loop would probably be better in this case instead of &lt;code&gt;while&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt;
&lt;p&gt;&lt;span aria-hidden=&quot;true&quot;&gt;🎓 &lt;/span&gt;&lt;strong&gt;TIP&lt;/strong&gt;: Netlify published a nice &lt;a href=&quot;https://www.netlify.com/blog/2020/03/05/feedback-ladders-how-we-encode-code-reviews-at-netlify/&quot;&gt;article about their approach to code reviews&lt;/a&gt; some time ago. I highly recommend it as inspiration.&lt;/p&gt;
&lt;/aside&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-to-do-better-code-review/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Code reviews can sometimes take a lot of time before all unclear comments are resolved. By following the guidelines mentioned in this article, you can make the process more efficient, valuable, and enjoyable for everyone involved.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>My 2024 Post-Mortem: Honest review of a year that didn&#39;t go as planned</title>
      <link>https://pustelto.com/blog/2024-post-mortem/</link>
      <pubDate>Tue, 21 Jan 2025 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/2024-post-mortem/</guid>
      <description>&lt;p&gt;Let’s make it clear. My 2024? It wasn’t great.&lt;/p&gt;
&lt;p&gt;In many ways, it felt like a failure. I had systems, habits, and planning routines in place, yet I ended the year feeling that I had lost track of what really mattered.&lt;/p&gt;
&lt;p&gt;At the end of each year, our social media feeds are flooded with “the best year ever” posts. Although it feels good to celebrate wins, these posts can make us feel like failures if we don’t have a six-figure business or thousands of followers.&lt;/p&gt;
&lt;p&gt;That’s why I decided to share this honest review, because I want to show it’s ok to make mistakes, that we all struggle sometimes.&lt;/p&gt;
&lt;p&gt;And that it is important to learn from our mistakes.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;why-didn%E2%80%99t-i-achieve-what-i-wanted%3F&quot; tabindex=&quot;-1&quot;&gt;Why didn’t I achieve what I wanted?&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/2024-post-mortem/#why-didn%E2%80%99t-i-achieve-what-i-wanted%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Why didn’t I achieve what I wanted?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;No matter how perfect your system is, if your planning is not connected to the bigger picture — a dream, vision, purpose, or whatever — it’s useless. You just do the tasks as they come without much impact. The results won’t compound and you will feel like a hamster on a wheel.&lt;/p&gt;
&lt;p&gt;I had that connection, but I lost track of it.&lt;/p&gt;
&lt;p&gt;I use a bullet journal for weekly and monthly goal setting and rely on Notion to organize almost everything else. That was one of the problems. My goals were hidden deep in Notion, which made it hard for me to check them regularly.&lt;/p&gt;
&lt;p&gt;Here is the full list of the issues that held me back:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;I didn’t check my goals regularly.&lt;/strong&gt; My goals were buried in my digital system, so I couldn’t see them easily. The friction was too big.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I set too many goals.&lt;/strong&gt; I focused on some, ignored others, and eventually lost sight of what was most important.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I have too complex, hard-to-track goals.&lt;/strong&gt; I spent too much time and effort to track all the stuff. In the end I didn’t have the willpower to track them at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I struggled with prioritization.&lt;/strong&gt; I spent time on low-impact tasks instead of working on the ones that mattered most. For example, with Clipio, I now see that I should have focused on getting users and releasing the product.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I underestimated my capacity.&lt;/strong&gt; With three kids, I had less energy and time than I thought. I struggled to find time for work and the energy to push myself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My goals were digital, but my planning was analog.&lt;/strong&gt; I love Notion (I’m building a web-clipping tool for it - &lt;a href=&quot;https://clipio.app/&quot;&gt;Clipio&lt;/a&gt;), but I rarely wanted to open Notion to check my goals.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;lessons-learned-for-2025&quot; tabindex=&quot;-1&quot;&gt;Lessons learned for 2025&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/2024-post-mortem/#lessons-learned-for-2025&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Lessons learned for 2025”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;While 2024 was disappointing in terms of goals and achievements (aside from more time with my family), it was also a year of learning. And I learned a lot during that year, especially during my yearly review.&lt;/p&gt;
&lt;p&gt;Moving forward, here’s how I’m changing my approach:&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;1.-fewer-goals%2C-more-focus&quot; tabindex=&quot;-1&quot;&gt;1. Fewer goals, more focus&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/2024-post-mortem/#1.-fewer-goals%2C-more-focus&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Fewer goals, more focus”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Instead of juggling a long list of goals and complex tracking mechanisms, I’m simplifying. For 2025, I have one main theme for the year written on a small card. All tasks and projects I decide to do must fit into this theme. On the back, there are a few bullet points with examples. My yearly goals are written on a small sheet of paper that I keep in my diary so I can see them anywhere. And they are easy to track.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;2.-focus-on-what-i-can-control&quot; tabindex=&quot;-1&quot;&gt;2. Focus on what I can control&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/2024-post-mortem/#2.-focus-on-what-i-can-control&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. Focus on what I can control”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I will focus on what I can control. I can control how often I write, how many calls for papers I submit, when I go to sleep, but I cannot control how many followers I get.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;3.-analog-over-digital-for-planning&quot; tabindex=&quot;-1&quot;&gt;3. Analog over digital for planning&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/2024-post-mortem/#3.-analog-over-digital-for-planning&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. Analog over digital for planning”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;While Notion is great for storing knowledge and writing, my goal planning will now be analog. It is easier to see, review, and act on a paper plan. A simpler, low-friction system means I will check my goals more often.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;4.-build-stronger-systems-and-habits&quot; tabindex=&quot;-1&quot;&gt;4. Build stronger systems and habits&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/2024-post-mortem/#4.-build-stronger-systems-and-habits&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “4. Build stronger systems and habits”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;I set my mornings for exercise or writing before I get my kids ready for school.&lt;/li&gt;
&lt;li&gt;I set time blocks for tasks. For example, instead of a task like “Write a blog post,” I schedule “Write for 30 minutes.” Short, timed tasks feel less hard to start and can fit into any day. Being consistent will help me produce more content.&lt;/li&gt;
&lt;li&gt;I plan to focus more on my health (which sucks ever since I started working from home during Covid) — regular morning exercise, after-lunch walks (even when home), more veggies.&lt;/li&gt;
&lt;li&gt;I will get proper sleep (good, long sleep is the ultimate productivity hack).&lt;/li&gt;
&lt;li&gt;I will regularly review my goals on paper to stay connected to them.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;a-key-idea%3A-it%E2%80%99s-okay-to-fail&quot; tabindex=&quot;-1&quot;&gt;A key idea: it’s okay to fail&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/2024-post-mortem/#a-key-idea%3A-it%E2%80%99s-okay-to-fail&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “A key idea: it’s okay to fail”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;If your 2024 didn’t look like the highlight reels on social media, you are not alone. The internet can make it seem like everyone is winning: building six-figure businesses and reaching huge milestones. But behind the scenes, many of us are just trying to figure things out.&lt;/p&gt;
&lt;p&gt;And that is okay.&lt;/p&gt;
&lt;p&gt;Don’t give up. Keep testing new ideas, keep adjusting, keep learning, and most importantly, &lt;strong&gt;keep moving forward&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here’s to a more intentional 2025.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>4+1 ways how to handle errors in your code</title>
      <link>https://pustelto.com/blog/error-handling-strategies/</link>
      <pubDate>Sat, 29 Mar 2025 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/error-handling-strategies/</guid>
      <description>&lt;p&gt;We, developers, love the “happy path”.&lt;/p&gt;
&lt;p&gt;We create a code that happily assumes everything will go perfectly well. But reality isn’t so kind. Servers crash, APIs fail, developers make mistakes, and users do unexpected things.&lt;/p&gt;
&lt;p&gt;Errors will happen in our apps. So let’s stop ignoring them and instead be ready to deal with them. In this article, I will walk you through 4 + 1 ways how to handle errors in your code.&lt;/p&gt;
&lt;nav class=&quot;toc&quot;&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#the-consequences-of-poor-error-handling&quot;&gt;The Consequences of Poor Error Handling&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#the-many-ways-to-handle-errors&quot;&gt;The many ways to handle errors&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#1.-ignoring-errors-and-hoping-for-the-best&quot;&gt;1. Ignoring errors and hoping for the best&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#2.-returning-weird-values&quot;&gt;2. Returning weird values&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#3.-throwing-errors-without-proper-handling&quot;&gt;3. Throwing errors without proper handling&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#4.-using-try...catch-blocks&quot;&gt;4. Using try...catch blocks&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#bonus%3A-treat-errors-as-data&quot;&gt;Bonus: Treat errors as data&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#why-is-it-better%3F&quot;&gt;Why is it better?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#examples-of-error-as-data-patterns&quot;&gt;Examples of error-as-data patterns&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#object-based-approach-(eg.-used-by-supabase)%3A&quot;&gt;Object-based approach (eg. used by Supabase):&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#result-pattern&quot;&gt;Result pattern&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://pustelto.com/blog/error-handling-strategies/#conclusion%3A-think-about-errors-before-they-happen&quot;&gt;Conclusion: Think about errors before they happen&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;the-consequences-of-poor-error-handling&quot; tabindex=&quot;-1&quot;&gt;The Consequences of Poor Error Handling&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#the-consequences-of-poor-error-handling&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “The Consequences of Poor Error Handling”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;We all know errors in the apps are bad. So, why do we still treat error handling as an afterthought? When we ignore proper error handling, the impact is felt everywhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Terrible UI/UX – Users have no clue what is happening in the system, what went wrong, or how to fix it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;System instability – Critical errors can be silenced and ignored, or tiny issues make the app unusable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Debugging nightmares – Hunting down silent failures is frustrating and time-consuming.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, how can we deal with errors? Let’s break down the common (and sometimes bad) approaches to error handling.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;the-many-ways-to-handle-errors&quot; tabindex=&quot;-1&quot;&gt;The many ways to handle errors&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#the-many-ways-to-handle-errors&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “The many ways to handle errors”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;1.-ignoring-errors-and-hoping-for-the-best&quot; tabindex=&quot;-1&quot;&gt;1. Ignoring errors and hoping for the best&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#1.-ignoring-errors-and-hoping-for-the-best&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Ignoring errors and hoping for the best”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/error-handling-strategies/YRNgiYxjNN-320.avif 320w, https://pustelto.com/blog/error-handling-strategies/YRNgiYxjNN-480.avif 480w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/error-handling-strategies/YRNgiYxjNN-320.webp 320w, https://pustelto.com/blog/error-handling-strategies/YRNgiYxjNN-480.webp 480w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/error-handling-strategies/YRNgiYxjNN-320.jpeg 320w, https://pustelto.com/blog/error-handling-strategies/YRNgiYxjNN-480.jpeg 480w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Meme: dev holding a card with text: Do error handling or draw 25. On the second image, the dev has a handful of cards.&quot; src=&quot;https://pustelto.com/blog/error-handling-strategies/YRNgiYxjNN-320.jpeg&quot; width=&quot;320&quot; height=&quot;315&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;Sure, not all errors are equal.&lt;/p&gt;
&lt;p&gt;Some errors, like failed analytics events, are harmless and can be ignored. But, ignoring critical errors can be catastrophic. Silent failed API calls can lead to users losing data. Generic error messages don’t help users recover, as they don’t know what is happening. They also can’t report the bug properly because they have no extra info.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;2.-returning-weird-values&quot; tabindex=&quot;-1&quot;&gt;2. Returning weird values&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#2.-returning-weird-values&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. Returning weird values”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;A practice I’ve seen more times than I’d like.&lt;/p&gt;
&lt;p&gt;Some developers return &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;-1&lt;/code&gt; to indicate failure instead of using proper error handling. This creates confusion because the caller has no idea what &lt;code&gt;false&lt;/code&gt; means in that context.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Is the result &lt;code&gt;false&lt;/code&gt;?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Or did the function fail?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If it failed, what went wrong?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This approach produces a bunch of questions and confusion. If something goes wrong, be explicit. This also makes type-checking a nightmare when success returns structured data and failure returns a random value.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;3.-throwing-errors-without-proper-handling&quot; tabindex=&quot;-1&quot;&gt;3. Throwing errors without proper handling&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#3.-throwing-errors-without-proper-handling&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. Throwing errors without proper handling”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Ok, so ignoring errors or returning weird values is bad. Let’s just throw an error and move on, right?&lt;/p&gt;
&lt;p&gt;Well, not really.&lt;/p&gt;
&lt;p&gt;Throwing an error isn’t bad by itself. But without solid error handling in the app, you’re just throwing errors randomly. Be careful with that.&lt;/p&gt;
&lt;p&gt;Especially if your code is used by others. IDEs won’t show if a function might throw—they only show the return value (the happy path). Developers can miss that the function might throw and skip the error handling (unless they know the internal logic).&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;4.-using-try...catch-blocks&quot; tabindex=&quot;-1&quot;&gt;4. Using &lt;code&gt;try...catch&lt;/code&gt; blocks&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#4.-using-try...catch-blocks&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “4. Using try...catch blocks”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Using &lt;code&gt;try...catch&lt;/code&gt; around code that might fail is a good start. But the question is: What happens after catching the error?&lt;/p&gt;
&lt;p&gt;Options vary based on context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Handle it immediately – Show a friendly message to the users, retry a request, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rethrow the error – Useful if a higher-level handler should deal with it. But make sure such a handler exists.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Return structured error objects – Help us pass errors along without breaking function contracts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt;&lt;strong&gt;Note about Promises:&lt;/strong&gt; I&#39;m not giving Promises a standalone section since they are similar to &lt;code&gt;try...catch&lt;/code&gt;. While more explicit, we know a promise can be rejected, errors can still be ignored (and they often are).&lt;/aside&gt;
&lt;p&gt;Let’s stop at the last point: Return structured error objects and have a closer look. We can actually treat errors like — well — not errors.&lt;/p&gt;
&lt;p&gt;Instead of throwing and spreading panic, we can treat them as normal data. Like returning a string or an object from a function.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;bonus%3A-treat-errors-as-data&quot; tabindex=&quot;-1&quot;&gt;Bonus: Treat errors as data&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#bonus%3A-treat-errors-as-data&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Bonus: Treat errors as data”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Throwing is a side effect that breaks function purity. A better approach is to treat errors as data. Acknowledge that something can go wrong and describe it. This way, a function always returns something predictable and prevents crashes.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h4 id=&quot;why-is-it-better%3F&quot; tabindex=&quot;-1&quot;&gt;Why is it better?&lt;/h4&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#why-is-it-better%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Why is it better?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Humans love certainty. We tend to ignore the unknown.&lt;/p&gt;
&lt;p&gt;By returning an error as a valid value, we force devs to deal with it or consciously ignore it. Of course, some errors are unpredictable (like server down or out of memory), but most can be caught with handlers in the right spots.&lt;/p&gt;
&lt;p&gt;This approach has several pros: It’s explicit, transparent, and eliminates surprises. Other devs see both success and error as possible return values. It’s clear that the code can fail and how.&lt;/p&gt;
&lt;p&gt;This helps us write better, more robust code.&lt;/p&gt;
&lt;p&gt;Here is an example from my work. I had to handle an API response, nothing fancy, just showing the data or the error state. The problem was that I had to use a different UI for various error states and, in some cases, make another request. But I had no clue what errors could be returned from the API. I had to manually simulate various conditions to see possible values. And I’m not sure that I have handled all the cases. If the error as a data approach was used, I would see what errors are possible and how to handle them.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h4 id=&quot;examples-of-error-as-data-patterns&quot; tabindex=&quot;-1&quot;&gt;Examples of error-as-data patterns&lt;/h4&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#examples-of-error-as-data-patterns&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Examples of error-as-data patterns”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h5 id=&quot;object-based-approach-(eg.-used-by-supabase)%3A&quot; tabindex=&quot;-1&quot;&gt;Object-based approach (eg. used by Supabase):&lt;/h5&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#object-based-approach-(eg.-used-by-supabase)%3A&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Object-based approach (eg. used by Supabase):”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;A function always returns an object with optional &lt;code&gt;error&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt; properties. In case of failure, the function populates the &lt;code&gt;error&lt;/code&gt; property instead of throwing. The caller checks for &lt;code&gt;error&lt;/code&gt; first.&lt;/p&gt;
&lt;p&gt;If there is no error, we can safely use &lt;code&gt;data&lt;/code&gt;. Otherwise, we handle the error our way.&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchUsers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apiClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/users/&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;handleError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This also works well with TypeScript, which forces us to handle possible errors.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h5 id=&quot;result-pattern&quot; tabindex=&quot;-1&quot;&gt;Result pattern&lt;/h5&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#result-pattern&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Result pattern”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Instead of a plain object, we return a class (usually called &lt;code&gt;Result&lt;/code&gt; or &lt;code&gt;Either&lt;/code&gt;). It works in a similar fashion as the object-based approach mentioned above. The class has a property that is used to check if the result of the function is an expected value or an error. But since it’s a class, it often has extra utility functions baked in.&lt;/p&gt;
&lt;p&gt;This pattern has several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Composition – You can chain functions that return/accept the &lt;code&gt;Result&lt;/code&gt; class.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Safety – You can pass the Result around safely, not needing to worry about errors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Helpers – Since the Result is a class, it often comes with various utility helpers. Methods like &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;unwrap&lt;/code&gt;, etc. so you can work with the result in a more functional way.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;invalid path&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;existsSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;invalid path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;test.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isOk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// OR using helpers&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;val &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;firstLettersToUppercase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;val&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;val &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; val&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;_&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// If the reading of a file was successful, we can map over the result&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// and transform it with the provided function.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// In case of an error, the map will be ignored. We can chain and compose multiple steps&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// like this and handle the potential error state only at the end.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some languages have this built-in (like &lt;a href=&quot;https://pustelto.com/blog/my-experience-with-rust/#4.-option-and-result-types&quot;&gt;Rust&lt;/a&gt;). And &lt;a href=&quot;https://github.com/arthurfiorette/proposal-try-operator&quot;&gt;hopefully, JavaScript will have this too one day&lt;/a&gt;. Meanwhile, we can use polyfills or libraries like &lt;a href=&quot;https://github.com/lune-climate/ts-results-es#readme&quot;&gt;ts-result-es&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The downside? More boilerplate. But unless your code is very small, the pros outweigh the cons.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion%3A-think-about-errors-before-they-happen&quot; tabindex=&quot;-1&quot;&gt;Conclusion: Think about errors before they happen&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/error-handling-strategies/#conclusion%3A-think-about-errors-before-they-happen&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion: Think about errors before they happen”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Something will go wrong. Maybe not today. Maybe not in your dev environment. But one day it will.&lt;/p&gt;
&lt;p&gt;How you handle that defines your system’s robustness.&lt;/p&gt;
&lt;p&gt;Don’t push problems up the call stack or pretend they don’t exist. Handle errors intentionally.&lt;/p&gt;
&lt;p&gt;When you model errors as data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Your code is more predictable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your system is easier to debug.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your team shares a clear idea of what failure looks like.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So next time you’re choosing how to handle failure, remember: a bit of verbosity beats a silent crash.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Reflecting on my indiehacking journey building Clipio</title>
      <link>https://pustelto.com/blog/building-clipio-reflection/</link>
      <pubDate>Tue, 03 Jun 2025 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/building-clipio-reflection/</guid>
      <description>&lt;p&gt;After over a year of development, I finally launched &lt;a href=&quot;https://clipio.app/&quot;&gt;Clipio&lt;/a&gt; — a browser extension that lets you save web content effortlessly into Notion. This article is a reflection about my journey with highs and lows and lessons I have learned the hard way.&lt;/p&gt;
&lt;p&gt;First some numbers:&lt;/p&gt;
&lt;p&gt;Over 1 year of development, 18 upvotes on &lt;a href=&quot;https://www.producthunt.com/products/clipio&quot;&gt;ProductHunt&lt;/a&gt; and #33 product of the day, made exactly 1 sale.&lt;/p&gt;
&lt;p&gt;Not an impressive numbers.&lt;/p&gt;
&lt;p&gt;But here’s the thing: I don’t count it as a total failure either, because I have learner a ton of things I wouldn’t otherwise.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;the-good-things&quot; tabindex=&quot;-1&quot;&gt;The good things&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/building-clipio-reflection/#the-good-things&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “The good things”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Let’s be real: the money didn’t pour in. But Clipio still gave me something more important — &lt;strong&gt;validation, exprience and connections&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;That first sale was an important validation for me that I can make money online (but probably not with Clipio).&lt;/li&gt;
&lt;li&gt;I use the product myself — and it solves a problem I actually care about.&lt;/li&gt;
&lt;li&gt;I walked away with a &lt;strong&gt;huge list of blog and content ideas&lt;/strong&gt; - both technical and non-technical.&lt;/li&gt;
&lt;li&gt;I learned a lot knowledge in backend, software architecture, product management, sales and marketing.&lt;/li&gt;
&lt;li&gt;And the most importantly, I’ve got few concrete ideas for what to build next — and how to do it better.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;the-bad-things&quot; tabindex=&quot;-1&quot;&gt;The bad things&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/building-clipio-reflection/#the-bad-things&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “The bad things”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Not everything went smoothly. Here’s a breakdown of the biggest mistakes — and what I learned from each.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;1.-the-producthunt-launch&quot; tabindex=&quot;-1&quot;&gt;1. The ProductHunt launch&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/building-clipio-reflection/#1.-the-producthunt-launch&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. The ProductHunt launch”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I put in the effort, tried to stir some buzz. But in the end it didn’t worked.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;18 upvotes. Very small traction.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;These days all &lt;strong&gt;the top products were all AI-based&lt;/strong&gt; — it almost seems like if you don’t have AI, you have no chance.&lt;/li&gt;
&lt;li&gt;Without being featured or a huge following before the launch, you will most likely don’t get much upvotes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Be better prepared. Build a following &lt;em&gt;before&lt;/em&gt; the launch. And yes, AI-first products are dominating PH right now.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;2.-i-suck-at-marketing&quot; tabindex=&quot;-1&quot;&gt;2. I suck at marketing&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/building-clipio-reflection/#2.-i-suck-at-marketing&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. I suck at marketing”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I’ll admit it: I struggle with marketing.&lt;/p&gt;
&lt;p&gt;It feels awkward and is heavily outside of my comfort zone. I tried with things, but nothing really worked.&lt;/p&gt;
&lt;p&gt;Aside from that I had a hard time putting together a good value proposition to actually get the interest and explain the value to the potential users. I fully realized this when preparing for ProductHunt launch when I spend unreasonable amount of time creating description for the launch. This can signal that the product is not a good fit as well and might need some pivot.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Think about value proposition first and validate that. Nex time I will try to create fake ProductHunt lunch () to realize what am I actually offering and selling.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;3.-i-have-built-a-product-for-bad-audience&quot; tabindex=&quot;-1&quot;&gt;3. I have built a product for bad audience&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/building-clipio-reflection/#3.-i-have-built-a-product-for-bad-audience&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. I have built a product for bad audience”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I use Clipio and it solves my own problem. That’s the first step to build good and successful product. But you still need to sell it. Pair that with my marketing skills and that’s where I met some troubles.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My target audience was almost completely outside my network.&lt;/li&gt;
&lt;li&gt;I mostly know developers — not exactly heavy Notion users.&lt;/li&gt;
&lt;li&gt;I failed to generate interest or even validate interest &lt;em&gt;early&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having poor overlap between my audience and my target group didn’t really help with marketing the product. And I also had a problem deciding whether I want to pivot and start writing for Notion users or stick with my current audience and not market my product very well.&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-320.avif 320w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-480.avif 480w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-640.avif 640w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-960.avif 960w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-320.webp 320w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-480.webp 480w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-640.webp 640w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-960.webp 960w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-320.jpeg 320w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-480.jpeg 480w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-640.jpeg 640w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-960.jpeg 960w, https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Diagram with two circles, one representing my network, second representing target audience for Clipio. There is only small overlap between those two.&quot; src=&quot;https://pustelto.com/blog/building-clipio-reflection/zfr72-_8GI-320.jpeg&quot; width=&quot;320&quot; height=&quot;151&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;I had only small overlap between my network and Clipio target audience, making marketing harder.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Don’t get me wrong. I totally believe you can build a great product even for group outside of your network. But it will be harder.&lt;/p&gt;
&lt;p&gt;This is called “Founder-Market Fit” and you can read more about it for example from &lt;a href=&quot;https://www.linkedin.com/posts/jorgemedinacastillo_thinking-about-%F0%9D%98%80%F0%9D%98%81%F0%9D%97%AE%F0%9D%97%BF%F0%9D%98%81%F0%9D%97%B6%F0%9D%97%BB%F0%9D%97%B4-%F0%9D%97%AE-%F0%9D%97%AF%F0%9D%98%82%F0%9D%98%80-activity-7321873198873034752-RNjS/?rcm=ACoAABJRhnkBqjGwphSX24AsE3v-69hPzMh__qE&quot;&gt;Jorge Medina in his post on LinkedIn&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Build for an audience you already understand and have access to. For me, that means &lt;strong&gt;devs, indie hackers, or small business owners&lt;/strong&gt; — groups where I already have reach. It will make marketing and selling much easier.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;4.-i-lost-motivation-as-the-development-dragged-on&quot; tabindex=&quot;-1&quot;&gt;4. I lost motivation as the development dragged on&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/building-clipio-reflection/#4.-i-lost-motivation-as-the-development-dragged-on&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “4. I lost motivation as the development dragged on”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I underestimated how much time it will take me to build a Clipio. Initially I thought I will build a first good version in 3 months. But slowly this become a year.&lt;/p&gt;
&lt;p&gt;As development dragged on, my updates slowed. Initially I posted daily or weekly about my progress on social media and send a monthly update to the people on the waitlist. But as the time progressed my frequency dropped. In the end it almost stopped.&lt;/p&gt;
&lt;p&gt;This was just another proof that my motivation dropped a lot over the long year development.&lt;/p&gt;
&lt;p&gt;You should really try to ship some thing as fast as possible and get feedback/validation from the users. Without it, your energy will slowly vanish (and you might just waste your time).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Ship fast while you’re still excited. Consider validating something in a &lt;strong&gt;week or two&lt;/strong&gt;, then double down or move on. Momentum matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; You’ll always underestimate how long it takes. Be ready to cut scope and compromise. Use existing tools, even if imperfect.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;5.-i-got-stuck-on-tooling&quot; tabindex=&quot;-1&quot;&gt;5. I Got Stuck on Tooling&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/building-clipio-reflection/#5.-i-got-stuck-on-tooling&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “5. I Got Stuck on Tooling”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;At one point, I spent hours debugging weird build issues — Nx configs, Tailwind quirks, Vite version mismatches, etc. So instead of a productive day I was fighting issues without any value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Stick to battle-tested stacks. Avoid unnecessary custom configs. &lt;strong&gt;Don’t build what you can buy&lt;/strong&gt; — unless it’s your core value prop.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/building-clipio-reflection/#conclusion&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Conclusion”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Building a Clipio was a great experience and I have learned a lot. Especially today, with the AI hype this was a great way to broaden my skills and validate that I can build something people will want to use.&lt;/p&gt;
&lt;p&gt;Of course I have made a mistakes along the way, but I have learned a valuable lessons thanks to that. I have wrote this article to reflect on this journey and share those lessons with you so you can learn from my mistakes.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Reverse engineering Linear - part 1: Header</title>
      <link>https://pustelto.com/blog/reverse-engineer-linear-1-header/</link>
      <pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/reverse-engineer-linear-1-header/</guid>
      <description>&lt;p&gt;With the advancement of AI, I’ve decided to try something different from building. Something that was previously very hard. I threw an AI at production, minified JavaScript code, and reverse engineered it back to readable JSX files and analyzed how it’s built.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The goal&lt;/em&gt;: Learn from top-tier software engineers and share valuable lessons with others.&lt;br /&gt;
&lt;em&gt;Target&lt;/em&gt;: &lt;a href=&quot;https://linear.app/&quot;&gt;Linear&lt;/a&gt; - software I deeply admire for its engineering and UX quality.&lt;/p&gt;
&lt;p&gt;This article is just the first in a series of articles I plan to write about Linear — as what started as a simple investigation turned into a deep rabbit hole.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;why-header%3F&quot; tabindex=&quot;-1&quot;&gt;Why Header?&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/#why-header%3F&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Why Header?”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;There is a lot I want to explore in the Linear codebase. Initially, my plan was to dive into Linear’s actions and keyboard navigation. However, while digging into the UI, I realized that even something as ordinary as the header has a few interesting ideas to uncover.&lt;/p&gt;
&lt;p&gt;So I have decided to start small and with something easier to digest before we dive into the more complex topics.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;anatomy-of-the-header&quot; tabindex=&quot;-1&quot;&gt;Anatomy of the Header&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/#anatomy-of-the-header&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Anatomy of the Header”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Let’s first have a look at the header and key components, so we know what we are dealing with in the rest of the article.&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-320.avif 320w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-480.avif 480w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-640.avif 640w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-960.avif 960w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-320.webp 320w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-480.webp 480w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-640.webp 640w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-960.webp 960w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-320.jpeg 320w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-480.jpeg 480w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-640.jpeg 640w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-960.jpeg 960w, https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Description of main components of header in Linear app: Breadcrumbs, Tabs, Side actions and facets&quot; src=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/YpMxSVtHdU-320.jpeg&quot; width=&quot;320&quot; height=&quot;86&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;p&gt;The header has 4 main parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Title&lt;/strong&gt; - children in React so it can be almost anything, most of the time it contains context-aware breadcrumbs showing the path from top view to the current location.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tabs&lt;/strong&gt; - some views can have tabs, which usually contain different custom views, filters, etc. This section will break to next line on smaller screens.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Side&lt;/strong&gt; - group of components on the right side of the header, usually icon buttons with some quick actions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subheader&lt;/strong&gt; - not always present, usually contains filters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some more variants of the header:&lt;/p&gt;
&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-320.avif 320w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-480.avif 480w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-640.avif 640w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-960.avif 960w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-320.webp 320w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-480.webp 480w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-640.webp 640w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-960.webp 960w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-320.jpeg 320w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-480.jpeg 480w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-640.jpeg 640w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-960.jpeg 960w, https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;Single line header from project detail and header without title from Issues view.&quot; src=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/35kXoELIPP-320.jpeg&quot; width=&quot;320&quot; height=&quot;73&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-have-i-found-interesting&quot; tabindex=&quot;-1&quot;&gt;What have I found interesting&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/#what-have-i-found-interesting&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What have I found interesting”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;1.-next-level-of-responsive-design&quot; tabindex=&quot;-1&quot;&gt;1. Next level of responsive design&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/#1.-next-level-of-responsive-design&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “1. Next level of responsive design”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Linear app works and looks great anywhere — from smartphones to large screens. Most of it is achieved through traditional CSS media queries. Either used directly in CSS or via React hooks (using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia&quot;&gt;matchMedia&lt;/a&gt; DOM API in the background).&lt;/p&gt;
&lt;iframe src=&quot;https://player.cloudinary.com/embed/?cloud_name=dtncnogka&amp;public_id=linear-header-video_pszzhf&amp;profile=custom&quot; width=&quot;1280&quot; style=&quot;height: auto; width: 100%; aspect-ratio: 2082 / 380; border-radius: 0.3rem; margin-bottom: 1.65rem&quot; allow=&quot;autoplay; fullscreen; encrypted-media; picture-in-picture&quot; allowfullscreen=&quot;&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;But Linear goes beyond traditional CSS breakpoints.&lt;/p&gt;
&lt;p&gt;There is a &lt;code&gt;ResponsiveSlot&lt;/code&gt; component used in the header (in the &lt;em&gt;Side&lt;/em&gt; slot). The purpose of this component is to hide its content based on available space in the wrapping &lt;code&gt;ResponsiveSlotContainer&lt;/code&gt; component.&lt;/p&gt;
&lt;p&gt;It doesn’t use breakpoints. Instead, it calculates available space using a system of resize observers and hides the content of the slot based on its priority.&lt;/p&gt;
&lt;p&gt;Here is how it works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;priority&lt;/code&gt; prop defines how important that slot (its content) is for the app. Lower priority slots are hidden first when there is not enough free space.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each slot registers itself via React context to a manager (MobX store) with all necessary info about the slot, see code below.&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;registerSlot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; priority&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; element &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ref&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;element &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; manager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slots&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Each slot has a resize observer attached to it.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; resizeObserver &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResizeObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entries&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inlineSize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;borderBoxSize&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;inlineSize &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inlineSize &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; slot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; manager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slots&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slot &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; slot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; inlineSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;runInAction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          slot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; inlineSize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token function&quot;&gt;calculateVisibility&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;manager&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  resizeObserver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;box&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;border-box&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Details of the slot are stored in the manager as well for later use&lt;/span&gt;&lt;br /&gt;  manager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slots&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    priority&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    ref&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;observer&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; resizeObserver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;offsetWidth&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;visible&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;insertSlotByPriority&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;manager&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; priority&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Resize observers are also attached to the containers. There are actually 2 nested containers to detect when the content actually overflows the available space (this is done with a bit of CSS flexbox magic).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If there is a change in conditions — resize event detected by observer, change of priority or registering of new slot, etc. — the manager will recalculate available space. And if needed, starts hiding lower-priority items dynamically (renders &lt;code&gt;null&lt;/code&gt;), independent of breakpoint or position across all the header elements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This avoids rigid media queries (works more like container query) and ensures that essential controls are always visible.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;iframe src=&quot;https://player.cloudinary.com/embed/?cloud_name=dtncnogka&amp;public_id=linear-responsive-slot-demo_ajsoce&amp;profile=custom&quot; width=&quot;1280&quot; style=&quot;height: auto; width: 100%; aspect-ratio: 2340 / 718; border-radius: 0.3rem; margin-bottom: 1.65rem&quot; allow=&quot;autoplay; fullscreen; encrypted-media; picture-in-picture&quot; allowfullscreen=&quot;&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;a href=&quot;https://codesandbox.io/embed/fvsqm7?view=preview&amp;amp;hidenavigation=1&quot;&gt;Play with this demo in the CodeSandbox.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Over-engineered?&lt;/p&gt;
&lt;p&gt;Maybe. But definitely clever and beautiful. It shows Linear’s attention to detail and design. Ensuring that relevant stuff is visible and less important goes away if there is not enough room.&lt;/p&gt;
&lt;p&gt;Sadly, I found only a few usages across the app in the code. And after careful analysis, I believe those cases are not working due to the CSS setup. There is a missing &lt;code&gt;min-width: 0&lt;/code&gt; on the top container, which forbids it to shrink below the size of the inner one. Effectively never triggering the hiding.&lt;/p&gt;
&lt;p&gt;Not sure if this is a bug or if this is intentional (if someone from Linear reads this, please let me know). So I had to make a few adjustments to make it actually work as it is supposed to in my demo.&lt;/p&gt;
&lt;p&gt;Nevertheless, it’s a &lt;strong&gt;great example of React composition&lt;/strong&gt; on a bigger scale. It works out of the box, you just wrap some component in the header with &lt;code&gt;ResponsiveSlot&lt;/code&gt; and that’s it. No props drilling or complex state management. And since the context is initialized in the Header component, you don’t have to deal with extra providers and similar. You just use the &lt;code&gt;ResponsiveSlot&lt;/code&gt; as any other component.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;2.-local-state-with-mobx&quot; tabindex=&quot;-1&quot;&gt;2. Local State with MobX&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/#2.-local-state-with-mobx&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “2. Local State with MobX”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Linear is using MobX as a state management tool (I will definitely cover state management in some later article). I have seen and even built a bunch of apps with MobX. Most of them had 1 or more large global states that could be consumed anywhere and usually it became quite a mess very soon (and a testing hell).&lt;/p&gt;
&lt;p&gt;Remember the registry for &lt;code&gt;ResponsiveSlot&lt;/code&gt; in the previous section? It is small MobX store. This store is created via a factory function when the Header component renders and is then saved in the React context to make it available to other components.&lt;/p&gt;
&lt;p&gt;This has several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No state leakage - the store is private and used only in the Header component.&lt;/li&gt;
&lt;li&gt;Small, purpose-specific state - which makes it easy to reason about.&lt;/li&gt;
&lt;li&gt;Still gives you the benefits of MobX - reactive updates only when truly needed, which is great for performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a clever pattern. It blends the simplicity of local state with MobX’s reactivity. In Clipio, I use small state that I inject into the components to avoid usage of global objects. But having it like this directly in React never crossed my mind.&lt;/p&gt;
&lt;p&gt;Here is a simplified example:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Factory function to create MobX store with properties and&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// methods to change them.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createResponsiveSlotManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; manager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;slots&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;visibility&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;isVisible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; slot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; manager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slots&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; slot&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;visible&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// ... other methods&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Wrap store in MobX&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeObservable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;manager&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; observable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;slots&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; observable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deep&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;visibility&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; observable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;updateContainerWidths&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; action&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// ... other methods&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Define React context provider and inject the store&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ResponsiveSlotProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; children &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; responsiveSlotManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useMemo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createResponsiveSlotManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// ... code left out for brevity&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ResponsiveSlotContext.Provider&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;responsiveSlotManager&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ResponsiveSlotContext.Provider&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h3 id=&quot;3.-dynamic-hiding-of-the-tabs&quot; tabindex=&quot;-1&quot;&gt;3. Dynamic hiding of the tabs&lt;/h3&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/#3.-dynamic-hiding-of-the-tabs&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “3. Dynamic hiding of the tabs”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Tabs in the header don’t look special at first glance, but then the fun starts when you have too many to fit into the view. The tabs that won’t fit are hidden and instead a button to open a popover with the complete list of tabs appears.&lt;/p&gt;
&lt;iframe src=&quot;https://player.cloudinary.com/embed/?cloud_name=dtncnogka&amp;public_id=linear-tabs_b4fsmq&amp;profile=custom&quot; width=&quot;1280&quot; style=&quot;height: auto; width: 100%; aspect-ratio: 1392 / 397; border-radius: 0.3rem; margin-bottom: 1.65rem&quot; allow=&quot;autoplay; fullscreen; encrypted-media; picture-in-picture&quot; allowfullscreen=&quot;&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;There are a few details I really like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The trigger shows the number of hidden tabs, but if the active tab is one of the hidden ones, it will be visible as a label instead.&lt;/li&gt;
&lt;li&gt;In the popover you can see all tabs and you can easily reorder them using drag and drop (btw Linear uses &lt;a href=&quot;https://dndkit.com/&quot;&gt;DnD kit&lt;/a&gt; for drag and drop interactions).&lt;/li&gt;
&lt;li&gt;You can also reorder the tabs inline - in such case all tabs will appear and you can drag and drop them in the list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have seen and even built dynamic hiding of the list based on available size of the container. Most of the time the solutions weren’t perfect and at certain edge cases they broke. But I didn’t manage to break it even once in Linear (I really tried). And the solution is very performance-effective as well.&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;
        &lt;source type=&quot;image/avif&quot; srcset=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-320.avif 320w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-480.avif 480w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-640.avif 640w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-960.avif 960w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-1440.avif 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/webp&quot; srcset=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-320.webp 320w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-480.webp 480w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-640.webp 640w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-960.webp 960w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-1440.webp 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
  &lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-320.jpeg 320w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-480.jpeg 480w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-640.jpeg 640w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-960.jpeg 960w, https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 39.4375rem) 100vw, 608px&quot; /&gt;
        &lt;img alt=&quot;At first all tabs fit in, then container shrinks and overflowing tabs have visibility set to hidden. At last a &#39;more&#39; button is positioned absolutely after last visible item.&quot; src=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/btPGcTQjbU-320.jpeg&quot; width=&quot;320&quot; height=&quot;167&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;
      &lt;/picture&gt;&lt;figcaption&gt;Schema of how the tabs hiding work&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;Tabs are hidden using CSS &lt;code&gt;visibility: hidden&lt;/code&gt; to make the extra tabs invisible, but they are still in the DOM and occupy the exact same spot (overflow is set to hidden).
&lt;ul&gt;
&lt;li&gt;This avoids extra layout flickering that might appear in solutions that really remove the items from the DOM (or use &lt;code&gt;display: none&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;It also makes it easy to enable drag and drop and show entire list for reordering.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The popover trigger (that &lt;strong&gt;2 more&lt;/strong&gt; button) is then absolutely positioned after the last visible item, and its size is taken into the calculations when the app decides if another tab should be hidden or shown.&lt;/li&gt;
&lt;li&gt;The width calculation happens when the active tab or the actual tabs change (and it happens in the &lt;code&gt;useLayoutEffect&lt;/code&gt; hook). And then when the size of the tabs container changes (using a resize observer for that).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Below is simplified version of the code that calculates which tabs should be hidden and what should be the position and size of the popover trigger button at the end:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculateTabLayout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;containerRef&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; activeFacetIndex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; containerRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; containerWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;offsetWidth&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; moreButtonWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TAB_GAP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;MORE_BUTTON_WIDTH&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NEW_VIEW_BUTTON_WIDTH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Special handling for active tab - ensure it&#39;s always visible&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; activeTab &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;activeFacetIndex&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; activeTabWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; activeTab&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;offsetWidth &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TAB_GAP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DROPDOWN_WIDTH&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NEW_VIEW_BUTTON_WIDTH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// We pick the smaller of the 2 - available space or active tab width&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; maxActiveWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;activeTabWidth&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; containerWidth&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; currentWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Iterate over all tabs except the last one (that&#39;s why subtracting -2 - for a new view button)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; max &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; child &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isLast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; childWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; child&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;offsetWidth&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// The clever part: dynamically calculate required space&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Default: reserve space for &quot;more&quot; button&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; requiredSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; moreButtonWidth&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isLast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Last tab only needs &quot;new view&quot; button&lt;/span&gt;&lt;br /&gt;        requiredSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NEW_VIEW_BUTTON_WIDTH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; activeFacetIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Before active tab: ensure active tab fits&lt;/span&gt;&lt;br /&gt;        requiredSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; maxActiveWidth&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Decision point: hide this tab if it won&#39;t fit with required space&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentWidth &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; childWidth &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; requiredSpace &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; containerWidth&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;hideAfterIndex&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;buttonLeft&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentWidth&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;buttonMaxWidth&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; activeFacetIndex &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; maxActiveWidth &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; moreButtonWidth&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      currentWidth &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; childWidth &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TAB_GAP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;hideAfterIndex&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// All tabs fit&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-i-have-learned&quot; tabindex=&quot;-1&quot;&gt;What I have learned&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/#what-i-have-learned&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What I have learned”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I have picked up a bunch of interesting ideas and inspiration just from the header that I will definitely use in the future (and some I have already used).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Dynamic hiding of components based on available space - smart usage of CSS instead of relying too much on JS.&lt;/li&gt;
&lt;li&gt;An example that a MobX state doesn’t always have to be global, but you can encapsulate it in context (which makes testing setup so much easier)&lt;/li&gt;
&lt;li&gt;Composition is one of the key principles in React and Linear does it really well:
&lt;ul&gt;
&lt;li&gt;Small utility hooks (for measuring element size, checking visibility etc.)&lt;/li&gt;
&lt;li&gt;Components to drive animations or handle heavy-lifting for menus and actions.&lt;/li&gt;
&lt;li&gt;But also bigger components that create logical blocks on the page (like header in this article) and are often bound together by small focused React context to carry state (it is used a lot from what I have seen so far).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;coming-next&quot; tabindex=&quot;-1&quot;&gt;Coming next&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/reverse-engineer-linear-1-header/#coming-next&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Coming next”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Digging into Linear’s codebase is really fun and a great source of learning. But this is just a beginning so stay tuned for part 2, where I will dig into their context-aware actions and amazing keyboard support.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How I plan my year (after ruthlessly simplifying the process for 5 years)</title>
      <link>https://pustelto.com/blog/how-i-plan-year/</link>
      <pubDate>Mon, 19 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://pustelto.com/blog/how-i-plan-year/</guid>
      <description>&lt;p&gt;Every January, I have a tradition. I rent a small cottage in the middle of the Czech Republic (&lt;a href=&quot;https://www.hotelkouty.cz/&quot;&gt;hotel Kouty&lt;/a&gt;, absolutely amazing place), disconnect from everything, and spend a weekend reflecting on the past year and thinking about the year ahead.&lt;/p&gt;
&lt;p&gt;This year was my fifth time doing it. And I’ve finally found a system that works.&lt;/p&gt;
&lt;p&gt;In the past, I always thought I had to get the most out of the weekend. I came prepared with pages of questions, various planning frameworks and templates. I would spend hours filling them out, setting quarterly goals, and creating elaborate tracking systems. I came back home with a plan and felt productive, but also tired and overwhelmed. Most of it was abandoned by February.&lt;/p&gt;
&lt;p&gt;So each year, I simplified.&lt;/p&gt;
&lt;p&gt;I ruthlessly cut what didn’t work and intentionally did less. And after 5 years, I ended up with something I can actually stick to. A process that’s more about &lt;strong&gt;thinking&lt;/strong&gt; than &lt;strong&gt;filling out templates&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This year, I went with 3 priorities for the weekend:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Reflect&lt;/strong&gt; – Look back at the past year. What worked and what didn’t? What gave me energy?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Relax&lt;/strong&gt; – Rest and chill. I deliberately scheduled most of my Saturday to do nothing. But as you’ll see, this is the most productive part of the entire process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Realign&lt;/strong&gt; – Check my direction. See what’s important, where I’m heading. Return home with energy and clarity.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;reflect&quot; tabindex=&quot;-1&quot;&gt;Reflect&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-i-plan-year/#reflect&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Reflect”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Reflection is where the weekend starts, though this time I started earlier during Christmas vacation. I went over my notes from the year to see what resonated with me — wins and failures, repeating patterns, etc. By the time I arrived at the offsite on Friday evening, I just needed to wrap it up.&lt;/p&gt;
&lt;p&gt;I use &lt;a href=&quot;https://bulletjournal.com/&quot;&gt;Bullet Journal&lt;/a&gt; as the main source (previously I tracked most of this in Notion). Throughout the year, I capture events, tasks, and notes. At the end of each month, I review and write down the key stories, ideas, and lessons. This ongoing practice makes the yearly reflection much easier — I’m not trying to remember twelve months of life from scratch.&lt;/p&gt;
&lt;p&gt;Some of the questions I ask myself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What were my biggest achievements and key events?&lt;/li&gt;
&lt;li&gt;What went well this year?&lt;/li&gt;
&lt;li&gt;What didn’t work?&lt;/li&gt;
&lt;li&gt;What gave me energy?&lt;/li&gt;
&lt;li&gt;What drained me?&lt;/li&gt;
&lt;li&gt;What am I grateful for?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don’t overthink this part. The goal is to get an honest picture of the year, not to write down every single detail. You can see the complete list of questions in &lt;a href=&quot;https://pustelto.notion.site/Yearly-review-and-planning-71add4b17413401798e4b4f079b51776?source=blog&quot;&gt;my Notion template&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;relax-and-realign&quot; tabindex=&quot;-1&quot;&gt;Relax and Realign&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-i-plan-year/#relax-and-realign&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Relax and Realign”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;On Saturday morning, I thought about the year ahead. Not about specific goals, but the general direction. What do I want to do (in my career or as a hobby)? What changes would make my life better? How can I support my family? Where do I want to steer my career? I wrote whatever came to mind. Questions, ideas, half-formed thoughts. I didn’t try to structure it too much at this point, just bullet points.&lt;/p&gt;
&lt;p&gt;I also did a personal &lt;a href=&quot;https://en.wikipedia.org/wiki/SWOT_analysis&quot;&gt;SWOT analysis&lt;/a&gt;. I don’t do it every year, but it’s useful when I feel stuck or when I’m considering bigger changes (like AI taking over the world). Another great tool for this purpose is &lt;a href=&quot;https://en.wikipedia.org/wiki/Ikigai&quot;&gt;Ikigai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That was it for most of the day.&lt;/p&gt;
&lt;p&gt;The relaxing part started with a late and long breakfast (hotel breakfasts are the best). I didn’t have any specific plans. A walk in the snowy countryside. Sat on the couch, enjoyed the silence, the view, and good wine. Sauna in the afternoon.&lt;/p&gt;
&lt;p&gt;Doesn’t sound like hard work, right? At first I just relaxed and enjoyed myself. It didn’t take long for my mind to get calm and bored. And silence and a bored mind are the best tools for thinking. I tried calm music on Friday but got irritated and turned it off — I enjoyed the silence more.&lt;/p&gt;
&lt;p&gt;I let my mind wander across the topics I had set in the morning. This sounds almost too simple, but it’s the part that matters most. Things that felt unclear suddenly made sense. A lot of really cool ideas came to me during this time — things I’m keen on trying out during the year. Interesting connections, realizations about my life, possible directions, and more. And I took notes.&lt;/p&gt;
&lt;p&gt;A lot of notes.&lt;/p&gt;
&lt;p&gt;In the evening, I sat down again and wrote more concrete things. Not detailed goals, but themes and priorities. A few specific projects I wanted to tackle. Systems I wanted to put in place.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;from-goals-to-systems&quot; tabindex=&quot;-1&quot;&gt;From goals to systems&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-i-plan-year/#from-goals-to-systems&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “From goals to systems”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Sunday morning was for wrapping up. I reviewed what I wrote on Saturday, what I wanted to focus on this year, and created specific goals from that.&lt;/p&gt;
&lt;p&gt;One of the biggest changes in my approach was shifting from specific goals to systems and processes.&lt;/p&gt;
&lt;p&gt;A few years ago, I would set goals like &lt;em&gt;“Write 8 blog posts this year.”&lt;/em&gt; Sounds concrete and measurable (and it is). But goals like this often don’t work. At least they don’t for me. By March, I’d be behind schedule and feeling guilty about it.&lt;/p&gt;
&lt;p&gt;Now I think about it differently. Instead of &lt;em&gt;“Write 8 blog posts”&lt;/em&gt;, I commit to a process: &lt;strong&gt;two hours a week dedicated to writing&lt;/strong&gt;. The output takes care of itself. Some weeks I make great progress, some weeks I don’t. But the system keeps me moving forward. I focus on &lt;em&gt;how&lt;/em&gt; to make progress, not just &lt;em&gt;what&lt;/em&gt; to achieve.&lt;/p&gt;
&lt;p&gt;Setting goals is the easy part. The hard part is figuring out how to actually make progress (I do admit that years of experience as a scout leader and leadership instructor help).&lt;/p&gt;
&lt;p&gt;Most people (and companies) get stuck there. They write down what they want to achieve, attach unrealistic deadlines, and call it a yearly plan.&lt;/p&gt;
&lt;p&gt;That’s where systems come in. It’s not enough to say “I will write more.” You need to figure out &lt;em&gt;how&lt;/em&gt; to make it happen. And that’s where I focus most of my energy during the planning weekend.&lt;/p&gt;
&lt;p&gt;What does a system look like in practice?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A fixed schedule for writing (two hours every Saturday morning, or in my case 30 minutes in the morning before kids wake up)&lt;/li&gt;
&lt;li&gt;Arranging time with my wife so I can focus on side projects&lt;/li&gt;
&lt;li&gt;Rules I commit to follow (no computers or phones after 21:30)&lt;/li&gt;
&lt;li&gt;A process for tracking interesting conferences and submitting talks to each one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This shift made planning feel lighter. I’m not setting myself up for failure with arbitrary targets. I’m building habits that compound over time.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;tools-i-use&quot; tabindex=&quot;-1&quot;&gt;Tools I use&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-i-plan-year/#tools-i-use&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Tools I use”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Over the years, I’ve tried many tools. Here’s what works (or has worked) well for me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pustelto.notion.site/Yearly-review-and-planning-71add4b17413401798e4b4f079b51776?source=blog&quot;&gt;My yearly review Notion template&lt;/a&gt; – This is what I use these days (last 2 years). Year Compass is a great tool, but too verbose for me. So I have created my own condensed version. Just the essential and relevant questions. It fits on a few pages, which makes it easy to review throughout the year. I also added a few questions from other sources.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yearcompass.com/&quot;&gt;Year Compass&lt;/a&gt; – I used this for the first few years. It’s a free booklet with reflection questions and planning prompts. Well-designed, but I found it too long and verbose. And some questions weren’t that relevant to me.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/posts/danielgamrot_gamrotovaproduktivita-activity-7279388240510377984-sU_4/&quot;&gt;Set of questions from Daniel Gamrot&lt;/a&gt; – These are great questions when thinking about your life and where to steer it. It’s in Czech, but translating it with AI shouldn’t be a problem these days.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://theyearlyreview.com/&quot;&gt;Yearly review from Dickie Bush and Nicolas Cole&lt;/a&gt; – You have to add your email to get access, but it’s worth it. I took a bunch of questions from there for my review as well.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fs.blog/annual-review/&quot;&gt;Questions from FS.blog&lt;/a&gt; – Another source of great questions if you want to dive deep. Same as above, you will need to provide your email for this one (but you should read the FS.blog newsletter anyway).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bulletjournal.com/&quot;&gt;Bullet Journal&lt;/a&gt; – For tracking daily and weekly progress. Nothing fancy — just a simple notebook where I capture events, tasks, and notes. At the end of each month, I review and synthesize the key stories. This is the foundation for my yearly reflection.&lt;/li&gt;
&lt;li&gt;Pen and paper – For the weekend itself, I prefer writing by hand. It’s slower, which is good. It forces me to think before I write.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;what-i-cut-over-the-years&quot; tabindex=&quot;-1&quot;&gt;What I cut over the years&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-i-plan-year/#what-i-cut-over-the-years&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “What I cut over the years”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Equally important is what I stopped doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Questions that weren’t relevant&lt;/li&gt;
&lt;li&gt;Too many goals – we have a tendency to overestimate our free time and underestimate how long something takes. This is another common mistake when planning. I used to have many goals. Now I usually stick to 1-3 big yearly goals.&lt;/li&gt;
&lt;li&gt;Detailed quarterly goals – Too rigid. Life changes too fast.&lt;/li&gt;
&lt;li&gt;Elaborate tracking systems – I spent more time maintaining them than benefiting from them. I now only track what really brings me value.&lt;/li&gt;
&lt;li&gt;Multiple planning frameworks – Combining different methodologies just created confusion.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pattern is clear: anything that required ongoing maintenance got cut. The best system is one you actually use.&lt;/p&gt;
&lt;div class=&quot;headingAnchor__wrapper&quot;&gt;
&lt;h2 id=&quot;making-it-work-for-you&quot; tabindex=&quot;-1&quot;&gt;Making it work for you&lt;/h2&gt;
&lt;a class=&quot;headingAnchor__link&quot; href=&quot;https://pustelto.com/blog/how-i-plan-year/#making-it-work-for-you&quot;&gt;&lt;span class=&quot;visually-hidden&quot;&gt;Permalink to “Making it work for you”&lt;/span&gt; &lt;span aria-hidden=&quot;true&quot;&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I’m not saying my approach is the right one for everyone. But if you’ve struggled with yearly planning — if you’ve set goals in January that felt irrelevant by March — maybe the answer isn’t a better framework. Maybe it’s simplification.&lt;/p&gt;
&lt;p&gt;Find a way to give yourself uninterrupted time to think. Remove the inputs. Let your mind wander. See what surfaces.&lt;/p&gt;
&lt;p&gt;You might be surprised how much clarity comes from doing less.&lt;/p&gt;
</description>
    </item>
</channel>
</rss>
