<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maximilien Monteil </title>
    <description>The latest articles on DEV Community by Maximilien Monteil  (@maxmonteil).</description>
    <link>https://dev.to/maxmonteil</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F253883%2F3cf62fa3-d90f-4b26-884b-5723ea7cc2c0.jpeg</url>
      <title>DEV Community: Maximilien Monteil </title>
      <link>https://dev.to/maxmonteil</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maxmonteil"/>
    <language>en</language>
    <item>
      <title>Firebase + Algolia: How to set up transform functions</title>
      <dc:creator>Maximilien Monteil </dc:creator>
      <pubDate>Tue, 07 Sep 2021 05:55:47 +0000</pubDate>
      <link>https://dev.to/maxmonteil/firebase-algolia-how-to-set-up-transform-functions-35d</link>
      <guid>https://dev.to/maxmonteil/firebase-algolia-how-to-set-up-transform-functions-35d</guid>
      <description>&lt;p&gt;Firebase is an incredible service, especially their Firestore database which I use to handle data in &lt;a href="https://www.mylo.fit/?utm_source=devto+transformfunc&amp;amp;utm_medium=Referral"&gt;Mylo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recently I updated the website with a page to show all public workouts. Implementing that is just a read operation but then I thought to add search capability and things quickly got more involved.&lt;/p&gt;

&lt;p&gt;Firestore has no native full-text search support and trying to make your own on top of Firebase is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expensive&lt;/li&gt;
&lt;li&gt;impractical&lt;/li&gt;
&lt;li&gt;not what I set out to do&lt;/li&gt;
&lt;li&gt;leaves me with more software to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Luckily Firebase is aware that this is a common use case, so their own docs recommend using a dedicated third-party search service. I decided to go with &lt;a href="https://www.algolia.com"&gt;Algolia&lt;/a&gt; because they were the service I had heard of the most.&lt;/p&gt;

&lt;p&gt;The official guides do a great job of walking you through the setup process so I won't parrot them here. Instead this article goes over transform function and how to set them up. I ran into a few pitfalls doing this and decided to write the guide I wish I had.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/docs/firestore/solutions/search?provider=algolia"&gt;Full-text Search with Algolia - Firebase Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.algolia.com/developers/firebase-search-extension/"&gt;Firestore search extension - Algolia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Heads up: you can access nested fields with dot notation &lt;code&gt;"data.book.name"&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a transform function
&lt;/h2&gt;

&lt;p&gt;When you install the Algolia extension and point it to a collection to search through, all the documents there and the fields you listed get used to create a search index. A search index is essentially a list that Algolia formats and organizes in a way that is better for searching.&lt;/p&gt;

&lt;p&gt;Each uploaded document is referred to as a record and a good practice is keep these small. The record data is also what is returned with search results.&lt;/p&gt;

&lt;p&gt;This leads to a balancing act, you want small records that are faster to search through but you also want records to have all the data you need to display results (otherwise you have to request each document again for the rest of the data).&lt;/p&gt;

&lt;p&gt;As a concrete example, I wanted to show the total number of exercises within each workout. To do this with the Firestore documents I do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numberOfExercises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exercises&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fine since I've already requested and received the whole document. But the &lt;code&gt;exercises&lt;/code&gt; object is actually quite large and I definitely don't want to have it in a record.&lt;/p&gt;

&lt;p&gt;That's where the transform function comes in. Before sending a document to be indexed, it will go through the transform function where I can do what I need to reduce the size or shape of the object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transformWorkout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;exercises&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exercises&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to add a transform function to Algolia Firebase
&lt;/h2&gt;

&lt;p&gt;After (or during) the setup of the Algolia extension, you can add the name of your transform function (same as the actual function) in the &lt;code&gt;Transform Function Name (Experimental) (Optional)&lt;/code&gt; configuration field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/algolia/firestore-algolia-search/tree/main"&gt;Firestore Algolia Search extension - Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that you can access nested fields with dot notation &lt;code&gt;"data.exercise.name"&lt;/code&gt;. That's also the key you have to use to access the data in your transform function and your search results (records). You can rename them by returning an object with the key name you want instead (&lt;code&gt;name: req.body.data["data.exercise.name"]&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating
&lt;/h3&gt;

&lt;p&gt;First, set up Firebase functions and the tools needed as per Google's own guide:&lt;br&gt;
&lt;a href="https://firebase.google.com/docs/functions/get-started"&gt;Get Started - Firebase Functions Docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then create your function in the &lt;code&gt;functions/index.js&lt;/code&gt; folder that was made during the init process.&lt;/p&gt;

&lt;p&gt;Now, contrary to what the extension docs say (at the time of writing), you should make your function callable via HTTP as described &lt;a href="https://firebase.google.com/docs/functions/http-events"&gt;here&lt;/a&gt; and &lt;em&gt;not&lt;/em&gt; one callable from your app.&lt;/p&gt;

&lt;p&gt;This means using &lt;code&gt;functions.https.onRequest&lt;/code&gt; instead of &lt;code&gt;functions.https.onCall&lt;/code&gt;. This is because the extension calls your function by its URL and not with the Firebase SDK.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/algolia/firestore-algolia-search/blob/f29a83e6faf7b9fb6632e0558c314e3f8a4f395b/functions/src/transform.ts#L25"&gt;Where the function is invoked - Extension Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/docs/functions/http-events#invoke_an_http_function"&gt;How to Invoke an HTTP Function - Firebase Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another important thing is the geographical location of your function. It needs to be in the same region as the extension to work. So if your extension is located in &lt;code&gt;europe-west1&lt;/code&gt; make sure your function is too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/functions/docs/locations"&gt;Cloud Functions Location - Google Cloud&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firebase-functions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transformPayloadForSearch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;
  &lt;span class="c1"&gt;// this needs to be the same location as the extension&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;europe-west1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onRequest&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// extract the actual data payload&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// the returned object MUST be wrapped within "result"&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// the `objectID` property is required, don't forget it!&lt;/span&gt;
        &lt;span class="na"&gt;objectID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;objectID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="c1"&gt;// the object keys are the ones you put in the comma separated list when setting up the extension&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data.exercise.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;transformPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// You can send logs this way but be aware of quotas or you'll get a surprise in your bill&lt;/span&gt;
    &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;There was an error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Make sure to call `.send()`, `.end()`, or `.redirect()` on `res`&lt;/span&gt;
    &lt;span class="c1"&gt;// so that your function doesn't run longer than needed&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  About nested fields
&lt;/h4&gt;

&lt;p&gt;When configuring the extension, you can optionally pass a comma separated list of Indexable Fields. This is the key the extension will use to access the fields of your Firestore documents and is also the name under which they will appear within your Algolia index.&lt;/p&gt;

&lt;p&gt;You can access nested fields by using dot notation, for example &lt;code&gt;"data.exercise.name"&lt;/code&gt;. The annoying thing is this name will also trickle down to the front-end as you get your search results. You will have to access everything with these long String keys.&lt;/p&gt;

&lt;p&gt;Luckily, the keys you return in your transform function is what Algolia will use. So a transform function can also be used to avoid the key hassle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Re-indexing
&lt;/h3&gt;

&lt;p&gt;Now you can deploy your function with this command (replace &lt;code&gt;transformPayloadForSearch&lt;/code&gt; with the name of your function):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;firebase deploy --only functions:transformPayloadForSearch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make Algolia update its index, run the script with the proper env variables set, you can find it in your Firebase console for the project where you installed Algolia.&lt;/p&gt;

&lt;p&gt;Extensions &amp;gt; Search with Algolia &amp;gt; How this extension works&lt;/p&gt;

&lt;p&gt;Note that you might need to create a service account and generate a key. Make sure to place that key in a safe place but not in version control (git).&lt;/p&gt;

&lt;p&gt;You can paste the script into your terminal to run it but if you end up running it a lot (like I did), create a bash script to help you out (also keep it out of git).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Re-index the Algolia index&lt;/span&gt;
&lt;span class="c"&gt;# ----------------------------&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LOCATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;add-yours&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;add-yours&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ALGOLIA_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;add-yours&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ALGOLIA_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;add-yours&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ALGOLIA_INDEX_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;add-yours&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;COLLECTION_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;add-yours&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FIELDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;add-yours&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TRANSFORM_FUNCTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;add-yours&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;add-yours&amp;gt;

npx firestore-algolia-search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run this command to make your script executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod +x reindexAlgolia.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally use this while in the same directory to run it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./reindexAlgolia.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;That about covers the process. I myself ran into quite a few roadblocks trying to set it up and couldn't find more information on the topic. I hope this can help you out and save you some headaches!&lt;/p&gt;

&lt;p&gt;Feel free to share any feedback or ask any questions 😁&lt;/p&gt;

&lt;p&gt;You can reach me on Twitter &lt;a href="https://twitter.com/maxmonteil"&gt;@MaxMonteil&lt;/a&gt;&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Add Typescript to your JS project in 1 line</title>
      <dc:creator>Maximilien Monteil </dc:creator>
      <pubDate>Sat, 06 Mar 2021 10:16:22 +0000</pubDate>
      <link>https://dev.to/maxmonteil/add-typescript-to-your-js-project-in-1-line-2m85</link>
      <guid>https://dev.to/maxmonteil/add-typescript-to-your-js-project-in-1-line-2m85</guid>
      <description>&lt;p&gt;If you've been spending any time in the JS world, you might have noticed Typescript becoming all the rage. But if you didn't start off with it, adding Typescript can be a pain.&lt;/p&gt;

&lt;p&gt;Well it doesn't have to be, in 5 lines you can have your cake and eat it too!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Typescript in the first place?
&lt;/h2&gt;

&lt;p&gt;The argument is that your Javascript code is bound to start getting errors that Typescript's typing could have avoided, especially as your project gets bigger.&lt;/p&gt;

&lt;p&gt;According to Kent C. Dodds, adding a type system is also the first step to getting into testing if you don't already have it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kentcdodds.com/blog/how-to-add-testing-to-an-existing-project" rel="noopener noreferrer"&gt;How to add testing to an existing project&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everyone wants to catch bugs that are obvious like passing a &lt;code&gt;string&lt;/code&gt; where a &lt;code&gt;number&lt;/code&gt; is expected, getting that sweet IDE auto-completion, and all around being more confident when you make changes.&lt;/p&gt;

&lt;p&gt;Maybe you're convinced, but you're already knee deep in your pure JS project and adding Typescript seems like a huge hassle. Well there exists a beautiful solution that requires literally 1 line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @param {number} value */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onlyGiveMeNumbers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom! Full on typing thanks to special JSDoc comments. You just need to make sure to use 2 * to start the multi-line comment.&lt;/p&gt;

&lt;p&gt;If it doesn't work right away you have three options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;add &lt;code&gt;// @ts-check&lt;/code&gt; to the top of each file&lt;/li&gt;
&lt;li&gt;If you use VS Code there's a &lt;code&gt;checkJs&lt;/code&gt; option&lt;/li&gt;
&lt;li&gt;create &lt;code&gt;tsconfig.json&lt;/code&gt; or &lt;code&gt;jsconfig.json&lt;/code&gt; in your project root
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;jsconfig.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tsconfig.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"checkJs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es2020"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es2020"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es2019"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checkJs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noEmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because a lot of IDEs use the TS language server to check your code even if it is in Javascript.&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;checkJs&lt;/code&gt; to true takes advantage of what's already there, but instead of getting types as &lt;code&gt;any&lt;/code&gt; everywhere, JSDoc lets you give your editor the information it needs.&lt;/p&gt;

&lt;p&gt;This has some advantages over TS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No compilation step&lt;/li&gt;
&lt;li&gt;Hyper simple setup&lt;/li&gt;
&lt;li&gt;No impact on final bundle size&lt;/li&gt;
&lt;li&gt;Self documenting&lt;/li&gt;
&lt;li&gt;Almost as complete as full on TS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is where you might have some second thoughts about going in on JSDoc. Standard JSDoc is not at feature parity with Typescript.&lt;/p&gt;

&lt;p&gt;If you follow the official &lt;a href="https://jsdoc.app/" rel="noopener noreferrer"&gt;JSDoc documentation&lt;/a&gt; (JSDoc Docs if you will), there are some things that you either can't do or are a huge hassle to set up (compared to TS), but this might not even affect you.&lt;/p&gt;

&lt;p&gt;You see, I think there are 2 kinds of typing worlds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Application logic&lt;/li&gt;
&lt;li&gt;Library land&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In application logic, typing is generally pretty straight forward (compared to 2), you mainly need to make sure you are consistent and thorough.&lt;/p&gt;

&lt;p&gt;For example, say you have an application that deals managing quests, you would first define the type for your core domains and then make sure every function and method expecting these domains is typed as such.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define all your domains.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * @typedef Quest
 * @prop {string} target What you should hunt
 * @prop {boolean} completed Whether the quest is completed or not
 */&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @typedef QuestBoard
 * @prop {string} name The board's name
 * @prop {Quest[]} quests All the quests on this board
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type all the places that would expect these items.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * @param {string} task
 * @return {Quest}
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createQuest&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Given a name and list of quests, create a new Quest Board.
 *
 * @param {string} name Name for the quest board.
 * @param {Quest[]=} quests optional list of quests.
 * @return {QuestBoard}
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createQuestBoard&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quests&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If most of your typing is going to be in the Application realm then JSDoc will serve you admirably. But when you enter Library Land things can get a little murkier, mainly because of &lt;strong&gt;Generics&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a quick explanation, Generics, are a placeholder type. You tell your code that it will receive a "something", but whatever it is, here is how you should handle it. Without Generics you might have to duplicate functions for each kind of type you expect it to receive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When making libraries that will be used by others, you can't predict what people will send so you need to be ready for anything, I'm not a typing expert but I've seen some frightening Library Land typing that JSDoc might not be able to handle (or maybe it might?).&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1363992092498747392-288" src="https://platform.twitter.com/embed/Tweet.html?id=1363992092498747392"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1363992092498747392-288');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1363992092498747392&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Unless you have such requirements, JSDoc still handles itself pretty well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * @template SomeGenericType
 *
 * @typedef WrappedData
 * @prop {SomeGenericType} data
 * @prop {Object} meta
 * @prop {number} meta.createdAt
 */&lt;/span&gt;

&lt;span class="cm"&gt;/** @template DataType */&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataWrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/** @param {DataType} data */&lt;/span&gt;
    &lt;span class="nf"&gt;constructor &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wrapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;data &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * @private
     * @param {DataType} data
     * @return {WrappedData&amp;lt;DataType&amp;gt;}
     */&lt;/span&gt;
    &lt;span class="nf"&gt;wrap &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/** @private */&lt;/span&gt;
    &lt;span class="nf"&gt;unwrap &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// A generic wrapper that will work with any random thing&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messageWrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DataWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cm"&gt;/** @extends {DataWrapper&amp;lt;Quest&amp;gt;} */&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuestWrapper&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DataWrapper&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createQuest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Capture a Shogun Ceanator!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// This wrapper will only accept Quest type things&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;questWrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QuestWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As with most examples dealing with Generics, this is a little contrived and not all that useful, but even then, JSDoc manages to get through.&lt;/p&gt;

&lt;p&gt;But what can you do about the things JSDoc just can't doc?&lt;/p&gt;

&lt;p&gt;Well there are 2 tricks that can get you almost all the way to complete feature parity with Typescript:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your editor's sneaky little secret&lt;/li&gt;
&lt;li&gt;Good old &lt;code&gt;*.d.ts&lt;/code&gt; files&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Your editor's sneaky little secret
&lt;/h2&gt;

&lt;p&gt;I said earlier that your editor (probably VS Code) uses a Typescript language server to parse and understand your code. Even in Vim I'm using the same language server to check my code (Neovim ftw).&lt;/p&gt;

&lt;p&gt;That's the secret!&lt;/p&gt;

&lt;p&gt;What do I mean? It is a &lt;em&gt;Typescript&lt;/em&gt; Language Server, not a &lt;em&gt;JSDoc&lt;/em&gt; Language Server (if that makes sense).&lt;/p&gt;

&lt;p&gt;When your editor goes through your code trying to understand it, it does so with a Typescript manual, this means it understands all the JSDoc stuff but also all the Typescript stuff. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Quest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuestMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/** @param {ReturnType&amp;lt;QuestMap.toPersistence&amp;gt;} raw */&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;toDomain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Quest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="cm"&gt;/** @param {Quest} quest */&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;toPersistence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you look at this line:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/** @param {ReturnType&amp;lt;QuestMap.toPersistence&amp;gt;} raw */&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;You'll see a Typescript only feature &lt;code&gt;ReturnType&lt;/code&gt; that still works because your editor is checking things through a Typescript lens.&lt;/p&gt;

&lt;p&gt;I haven't done extensive tests with this but it should generally work for any Typescript feature that you're able to write out in JSDoc syntax.&lt;/p&gt;

&lt;p&gt;It does have it's limits, for example, I wasn't able to get this to work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// some function that returns an arbitrary number of Promise&amp;lt;boolean&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getBools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tryToTypeThis&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;getBools&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;jsDocPlease&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nf"&gt;getBools&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

    &lt;span class="c1"&gt;// ???&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="cm"&gt;/** @type {PromiseConstructor['all']&amp;lt;boolean | string&amp;gt;} */&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getBools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canTypeThis&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;getBools&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is another set of contrived examples and I don't think you should write code like this, but it serves the purpose of showing where JSDoc reaches it's limits.&lt;/p&gt;

&lt;p&gt;But there's a solution even to that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good old &lt;code&gt;*.d.ts&lt;/code&gt; files
&lt;/h2&gt;

&lt;p&gt;In our config earlier we had to set &lt;code&gt;checkJs&lt;/code&gt; to true, that's because your editor type checks &lt;code&gt;.ts&lt;/code&gt; files by default which your definition files fall under.&lt;/p&gt;

&lt;p&gt;You might think what's the point of writing definition files, might as well go full Typescript.&lt;/p&gt;

&lt;p&gt;To that I say, even in Typescript you'd end up writing some definition files, and using them still gives you all the advantages of only using JSDoc.&lt;/p&gt;

&lt;p&gt;With definition files, you get the full feature set of Typescript, but once again you won't need a compile step and during build they get ignored since your project is a JS one (not 100% sure on this, please correct me if I'm wrong).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @typedef {import('./types.d.ts').ComplexType} ComplexType */&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {ComplexType} */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;complexVariable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  So should you use JSDoc?
&lt;/h2&gt;

&lt;p&gt;If you are in a situation where your project is almost all JS and you're tempted to switch TS but the cost is just too high, this could be an option to consider.&lt;/p&gt;

&lt;p&gt;It even has the advantage that if you do make the switch to TS, things will already be typed and documented.&lt;/p&gt;

&lt;p&gt;Now of course JSDoc isn't perfect, it's quite a lot more verbose than the equivalent TS and it can sometimes be difficult to find answers to some problems.&lt;/p&gt;

&lt;p&gt;In the end it's up to you to evaluate your options and make the choice that's best for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Helpful resources
&lt;/h2&gt;

&lt;p&gt;When writing JSDoc there are actually 2 syntaxes that you can use, the one described on the official JSDoc site and the Closure Compiler Syntax.&lt;/p&gt;

&lt;p&gt;CCS has a few extra features that might only be understood by the Closure Compiler but I have used some of them in JSDoc so your mileage may vary.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/google/closure-compiler/wiki#typing" rel="noopener noreferrer"&gt;Closure Compiler Syntax&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since we're relying on the TS language server to check our JSDoc comments, it's helpful to look at Typescript's own JSDoc reference for what's supported.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html" rel="noopener noreferrer"&gt;Typescript's JSDoc reference&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://jsdoc.app/index.html" rel="noopener noreferrer"&gt;Official JSDoc site&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devhints.io/jsdoc" rel="noopener noreferrer"&gt;A basic Cheatsheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.joshuatz.com/cheatsheets/js/jsdoc/" rel="noopener noreferrer"&gt;Joshua's JSDoc Cheatsheet (a lot more complete)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you liked this article you can follow me &lt;a href="https://www.twitter.com/maxmonteil" rel="noopener noreferrer"&gt;@MaxMonteil&lt;/a&gt; for more :D&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>testing</category>
      <category>todayisearched</category>
    </item>
    <item>
      <title>Is your app online? Here's how to reliably know in just 10 lines of JS [Guide]</title>
      <dc:creator>Maximilien Monteil </dc:creator>
      <pubDate>Sun, 06 Sep 2020 12:34:21 +0000</pubDate>
      <link>https://dev.to/maxmonteil/is-your-app-online-here-s-how-to-reliably-know-in-just-10-lines-of-js-guide-3in7</link>
      <guid>https://dev.to/maxmonteil/is-your-app-online-here-s-how-to-reliably-know-in-just-10-lines-of-js-guide-3in7</guid>
      <description>&lt;p&gt;We usually expect our web apps to be online but that ignores reality.&lt;/p&gt;

&lt;p&gt;People go on planes, enter tunnels, have bad internet, or just decide to go offline. Depending on your user's expectations, should your app stop working?&lt;/p&gt;

&lt;p&gt;If not, you'll need a reliable way to detect if your app is offline in order to offer the proper experience.&lt;/p&gt;

&lt;p&gt;Here's how in just 10 lines of JS.&lt;/p&gt;

&lt;p&gt;TL;DR Code is at the bottom for your copy/pasting pleasure!&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Navigator
&lt;/h2&gt;

&lt;p&gt;Before coding, let's look at the lay of the land.&lt;/p&gt;

&lt;p&gt;Browsers come with the &lt;code&gt;navigator.onLine&lt;/code&gt; property. This straight up returns &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; based on the browser state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isOnline&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onLine&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So are we done? Well, because of how it works, you can only trust &lt;code&gt;false&lt;/code&gt; to mean offline. &lt;code&gt;true&lt;/code&gt; could be more varied.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine"&gt;MDN - Navigator.onLine&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So how to tell if you also have access to the internet?
&lt;/h2&gt;

&lt;p&gt;Because of the way navigator works, we know when we're offline but online is a little murky.&lt;/p&gt;

&lt;p&gt;Navigator returns &lt;code&gt;true&lt;/code&gt; when the device is connected to a network but that doesn't mean you are also connected to the internet which are 2 very different things.&lt;/p&gt;

&lt;p&gt;Your first instinct might be to make a request to some random site and seeing if you get a success or an error.&lt;/p&gt;

&lt;p&gt;But what kind of request? And to which resource? 🤔&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending the perfect request ✨
&lt;/h3&gt;

&lt;p&gt;Checking the network status might happen often so ideally our request response should be as small as possible. This will make it faster and it will consume less bandwidth.&lt;/p&gt;

&lt;p&gt;To figure what kind of requests are available, we can look at the different HTTP methods and the HEAD method stands out as the best (TRACE might actually be better but isn't supported by fetch).&lt;/p&gt;

&lt;p&gt;A HEAD request is almost exactly like a GET request except we get no response data, only the HEADers. This works out great since our goal is to check if the request was successful or not, we don't actually care about any data returned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where should you send the request?
&lt;/h3&gt;

&lt;p&gt;We have the perfect request but where should it go?&lt;/p&gt;

&lt;p&gt;Your first instinct might be to send it to some service or site that is always active. Maybe google.com? But try that and you will be greeted by CORS errors.&lt;/p&gt;

&lt;p&gt;This makes sense, Google (and every other site by default) won't accept requests from random sites.&lt;br&gt;
The next option is to make your own server or cloud function that would accept requests exclusively from your application!&lt;/p&gt;

&lt;p&gt;But that's far too much work for a simple network check and a good developer is a lazy developer.&lt;/p&gt;

&lt;p&gt;So back to square one, CORS errors.&lt;/p&gt;

&lt;p&gt;Their goal is prevent security issues on requests coming from a different origin. Then wouldn't it be possible send the request to your own origin?&lt;/p&gt;

&lt;p&gt;The answer is yes! And you can automatically get your origin with &lt;code&gt;window.location.origin&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isOnline&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can ping your own site and wait for a response, but the problem is since we always send the same request to the same URL, your browser will waste no time caching the result making our function useless.&lt;/p&gt;

&lt;p&gt;So the final trick is to send our request with a randomized query parameter!&lt;br&gt;
This will have no impact on the result and will prevent your browser from caching the response since it goes to a different URL each time.&lt;/p&gt;

&lt;p&gt;And thanks to the built-in URL class, we don't even need to manually manipulate strings.&lt;/p&gt;

&lt;p&gt;Here is the final code along with some extra error handling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;getRandomString&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isOnline&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="c1"&gt;// avoid CORS errors with a request to your own origin&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// random value to prevent cached responses&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getRandomString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us a more reliable check on the network's status but it is missing some configuration options.&lt;/p&gt;

&lt;p&gt;Notably we always check with the same URL. This could be fine but what if you would prefer to ping your own server or just something closer to reduce latency?&lt;/p&gt;

&lt;p&gt;Additionally this runs only on call, it might be useful to be able to pass a callback, or have some kind of observer.&lt;/p&gt;

&lt;p&gt;You do get event listeners when the network status changes...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;online&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;online&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final result here is very simple and I leave it up to you to expand this to fit your needs!&lt;/p&gt;




&lt;p&gt;Thanks for reading this article! Let me know what you think in a comment or message me directly on twitter &lt;a href="https://twitter.com/maxmonteil"&gt;@MaxMonteil&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>Quickly build a performant Landing page with Nuxt</title>
      <dc:creator>Maximilien Monteil </dc:creator>
      <pubDate>Wed, 15 Jul 2020 06:48:36 +0000</pubDate>
      <link>https://dev.to/maxmonteil/quickly-build-a-performant-landing-page-with-nuxt-289o</link>
      <guid>https://dev.to/maxmonteil/quickly-build-a-performant-landing-page-with-nuxt-289o</guid>
      <description>&lt;p&gt;I've been building &lt;a href="https://www.mylo.fit" rel="noopener noreferrer"&gt;Mylo&lt;/a&gt; (a more intelligent workout assistant) but contrary to popular advice, I started building the app and completely ignored making a landing page. I should have taken that advice 😔&lt;/p&gt;

&lt;p&gt;There are all the usual reasons like letting you build hype and gather email addresses, but the biggest impact I've noticed is how 'real' it makes the project. It's no longer an idea in your head but a landmark that others can see giving them the chance to understand you.&lt;/p&gt;

&lt;p&gt;Determined to make a landing page, I decided to learn Nuxt, let me share what I learned to help you make your own page and get amazing scores in Lighthouse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Setting up your nuxt project is pretty straightforward, as per nuxt's getting started guide, run this command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ npx create-nuxt-app &amp;lt;project-name&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When you go through the setup cli make sure to select 'static' as the target, it's a brand new option in Nuxt that will let you generate a fully static version of your site. This will make it load much faster, and you can benefit from free hosting on most CDNs, like Netlify.&lt;/p&gt;

&lt;p&gt;Add the generate command to your package.json:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"generate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nuxt build &amp;amp;&amp;amp; nuxt export"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ cd &amp;lt;project-name&amp;gt;
$ npm run dev


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After that you're pretty much dealing with standard Vue. I won't go into all the details of Nuxt because you won't need most of them for a simple landing page like this one, but here is a brief primer of what you do need:&lt;/p&gt;

&lt;h3&gt;
  
  
  Directory structure
&lt;/h3&gt;

&lt;p&gt;Unlike Vue, which essentially dumps you in the desert with nothing but a &lt;code&gt;src&lt;/code&gt; folder, Nuxt leaves you by the oasis 🌴&lt;br&gt;
You're given a few folders to help you get started, here are the ones you will definitely need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;assets - static assets that you want Nuxt to bundle/transform for you (images, SVG, CSS)&lt;/li&gt;
&lt;li&gt;components - your usual components like buttons, modals, forms, etc.&lt;/li&gt;
&lt;li&gt;layouts - these are also components but they handle full page elements like a header, footer, sidebar, etc.&lt;/li&gt;
&lt;li&gt;pages - each file/folder here will get mapped to a URL of your site

&lt;ul&gt;
&lt;li&gt;index.vue -&amp;gt; yoursite.com/&lt;/li&gt;
&lt;li&gt;about.vue -&amp;gt; yoursite.com/about&lt;/li&gt;
&lt;li&gt;blog/welcome.vue -&amp;gt; yoursite.com/blog/welcome&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Generally a landing page is rather light in interactivity so you might find yourself writing very little JS, and might not use the other Nuxt features like &lt;code&gt;asyncData&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to check out the code behind Mylo's homepage, I've open-sourced it here: &lt;a href="https://github.com/MaxMonteil/MyloWebsite" rel="noopener noreferrer"&gt;Github - MyloWebsite&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving Lighthouse
&lt;/h2&gt;

&lt;p&gt;Making the site itself is only the first step though, we need to make sure performance is great, a slow site doesn't make for a great first impression of your product or service.&lt;/p&gt;

&lt;p&gt;So first, go ahead and run a Lighthouse audit to get an idea of where things stand.&lt;/p&gt;

&lt;p&gt;Before optimizing 😰&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8ilngopl6sacuio5q2ku.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8ilngopl6sacuio5q2ku.jpg" alt="Lighthouse scores before optimization (performance 13, accessibility 79, best practices 100, SEO 100)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These scores aren't all that great, the performance one is downright horrible. But here's where a few changes get me 😎&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frrhcnfhexxmuhn78w2kd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frrhcnfhexxmuhn78w2kd.jpg" alt="Lighthouse scores after optimization (performance 91, accessibility 100, best practices 93, SEO 100)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;The biggest issue I was running into were caused by SVGs.&lt;/p&gt;

&lt;p&gt;When it comes to icons I generally prefer to use SVGs over images, they often result in smaller file sizes, can be modified with CSS, and can be resized without any loss of quality.&lt;/p&gt;

&lt;p&gt;Now there are many ways to embed them into your site depending on what you need. I needed to be able to modify their color with CSS so I inlined them.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;functional&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt;
    &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;
    &lt;span class="na"&gt;:width=&lt;/span&gt;&lt;span class="s"&gt;"props.size"&lt;/span&gt;
    &lt;span class="na"&gt;:height=&lt;/span&gt;&lt;span class="s"&gt;"props.size"&lt;/span&gt;
    &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 16 16"&lt;/span&gt;
    &lt;span class="na"&gt;:aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"props.labelledby"&lt;/span&gt;
    &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"presentation"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;logo icon&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    ...
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This works great for simpler SVGs like the logo. But when I inlined a more complex one (multiple times) it made the DOM huge, filling it with giant SVG tags that the browser had to parse through, giving me that performance score of 13.&lt;/p&gt;

&lt;p&gt;The solution then was to embed them in an &lt;code&gt;&amp;lt;img /&amp;gt;&lt;/code&gt; tag. This way you avoid adding thousands of DOM elements while still benefiting from caching, but you lose the ability to color them with CSS...&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"require('~/assets/images/sports_icons.svg')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Or do you?&lt;br&gt;
&lt;/h4&gt;

&lt;p&gt;Turns out there are a couple of ways to change the color of an SVG even if embedded this way. They involve using combinations of CSS filters to get the color you want.&lt;/p&gt;

&lt;p&gt;You can find more information on how to do that in this  post:&lt;br&gt;
&lt;a href="https://css-tricks.com/the-many-ways-to-change-an-svg-fill-on-hover-and-when-to-use-them/" rel="noopener noreferrer"&gt;CSS Tricks - Change SVG fill on hover&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But these filters aren't very intuitive to combine into the color you want, luckily I found this codepen which can convert a HEX value into the matching set of CSS filters:&lt;br&gt;
&lt;a href="https://codepen.io/sosuke/pen/Pjoqqp" rel="noopener noreferrer"&gt;Generate filters for a hex&lt;/a&gt;&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"require('~/assets/images/sports_icons.svg')"&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fill-green"&lt;/span&gt;&lt;br&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nc"&gt;.fill-green&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;invert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;91%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;sepia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;saturate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1436%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;hue-rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;90deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;94%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;contrast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;93%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Accessibility - color contrast&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;Another issue I had was poor color contrast.&lt;br&gt;
In most design tools you start off by creating a nice color palette which you then tweak to get proper contrast.&lt;/p&gt;

&lt;p&gt;What if you could do things the other way around? That's what &lt;a href="https://leonardocolor.io/" rel="noopener noreferrer"&gt;Leonardo Color&lt;/a&gt; does. You set your base color(s) and from there generate additional colors that achieve the contrast ratios you need to pass the &lt;a href="https://webaim.org/articles/contrast/" rel="noopener noreferrer"&gt;WCAG's criteria&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And here is another tool you can use to check contrast between colors: &lt;a href="https://webaim.org/resources/contrastchecker/" rel="noopener noreferrer"&gt;WebAIM Contrast Checker&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Making a landing page really is a good first step, it seemed like such a daunting task to me but ended up being much easier to create than my actual project.&lt;/p&gt;

&lt;p&gt;Turns out I was just scared of how concrete it would make Mylo feel, because now the whole world can see and criticize it, but in the end, what better way is there to improve?&lt;/p&gt;

&lt;p&gt;Let me know what you thought of this post and if you check out &lt;a href="https://www.mylo.fit" rel="noopener noreferrer"&gt;Mylo&lt;/a&gt; I'd love to hear your feedback!😁&lt;/p&gt;

&lt;h2&gt;
  
  
  P.S. Here's one more tip
&lt;/h2&gt;

&lt;p&gt;Making the landing page is cool and all but you're likely going to need a way to contact those who sign up, and it looks more professional to have an email address on at a custom domain.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;contact@&amp;lt;your-project&amp;gt;.com&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some hosting services offer emails but I use Netlify which doesn't offer this and getting such an address is usually a paid service but I found this guide to set you up for free!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marvintan.com/posts/free-custom-email-address/" rel="noopener noreferrer"&gt;How to setup a custom email address for free&lt;/a&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>nuxt</category>
    </item>
    <item>
      <title>How to start Coding up your Design System in Vue</title>
      <dc:creator>Maximilien Monteil </dc:creator>
      <pubDate>Fri, 31 Jan 2020 19:23:24 +0000</pubDate>
      <link>https://dev.to/maxmonteil/how-to-start-coding-up-your-design-system-in-vue-1m08</link>
      <guid>https://dev.to/maxmonteil/how-to-start-coding-up-your-design-system-in-vue-1m08</guid>
      <description>&lt;p&gt;Imagine this, you create your web app for this new epic idea and you implement the core functionality. The design can come later you think.&lt;/p&gt;

&lt;p&gt;Well later is now, you're gaining traction and getting feedback, the time has come to overhaul or completely recreate your design!&lt;/p&gt;

&lt;p&gt;In this article I'll go over the tools and methods you can use to achieve just that.&lt;/p&gt;

&lt;p&gt;I personally fell into this situation with my web app Mylo, a workout management application. As it grew and came into contact with users, issues arose with color contrast, inconsistencies, and more.&lt;/p&gt;

&lt;p&gt;Fixing these issues can be described in two pillars:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design System (how everything will look)&lt;/li&gt;
&lt;li&gt;Implementation (how to apply the look)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(I focus on look here but understand that design is only about 20% look, the rest must be considerations about the user experience)&lt;/p&gt;

&lt;p&gt;Going through this has taught me a lot but also made obvious how much more there is to learn. If you find any mistakes or have some tips please let me know :D&lt;/p&gt;

&lt;p&gt;I'll be using &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;TailwindCSS&lt;/a&gt; in this article but everything applies just as well without.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Directory Structure&lt;/li&gt;
&lt;li&gt;Colors&lt;/li&gt;
&lt;li&gt;Icons&lt;/li&gt;
&lt;li&gt;Buttons&lt;/li&gt;
&lt;li&gt;Text Inputs&lt;/li&gt;
&lt;li&gt;Radio Buttons&lt;/li&gt;
&lt;li&gt;Select Input&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pillar 1: Design System
&lt;/h2&gt;

&lt;p&gt;The first step, which is all the rage these days, is having a design system. A design system is essentially a visual codebase of your software, and just like a codebase, it is a complete and specific description of what the application should look like under almost any circumstance.&lt;/p&gt;

&lt;p&gt;And so, the more you look into what makes up a design system, the more it feels like an impossible task. A complete design system involves the colors, spacing rules, text styles, buttons, containers, branding, accessibility, and so much more.&lt;/p&gt;

&lt;p&gt;The best and most exhaustive resource I've found is the &lt;a href="https://designsystemchecklist.com/" rel="noopener noreferrer"&gt;Design System Checklist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Therefore I feel more comfortable referring to what I have as a design library with loose guidelines. It works out because I can just refer to myself for any design questions ;)&lt;/p&gt;

&lt;p&gt;So we'll be going over how to implement elements like buttons, icons, colors and a few input types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pillar 2: Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Directory Structure &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Coding up design elements is amazing for reusability and consistency but it isn't very useful if components are all across the app in random, hard to access locations. We want them organized.&lt;/p&gt;

&lt;p&gt;I recommend putting the smallest/atomic elements into the &lt;code&gt;src/components&lt;/code&gt; folder and then into their own subfolders (icons, inputs, buttons, etc.)&lt;br&gt;
Compound components, built out of the smaller ones, can be placed into the &lt;code&gt;src/layouts&lt;/code&gt; folder, again with their own subfolders.&lt;/p&gt;
&lt;h3&gt;
  
  
  Colors &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Defining and enforcing colors is a good first step.&lt;/p&gt;

&lt;p&gt;You generally have a clear discrete set of colors that your app allows, to bring them in you can either modify the TailwindCSS config file or add them as CSS variables.&lt;/p&gt;

&lt;p&gt;Here is how it looks in TailwindCSS, I overwrote the default colors to enforce the use of the system colors but you can also extend the default theme and add your own colors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tailwind.config.js&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;white&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#FFFFFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;black&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#000000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#EEEEEE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#5D5D5C&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;darker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#414040&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;green&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#83E8BC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#56806C&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;darker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#445F51&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;red&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#F25F5C&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#C15450&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;darker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#8B4440&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://tailwindcss.com/docs/theme/" rel="noopener noreferrer"&gt;TailwindCSS Docs - Theme Configuration&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're not using tailwind this can also be achieved using css variables like so:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* src/App.vue */&lt;/span&gt;
&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--white&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'#FFFFFF'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--black&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'#000000'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--gray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'#EEEEEE'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--gray-dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'#5D5D5C'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--gray-darker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'#414040'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Icons &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;First off I recommend using SVG icons because of how configurable they are. You can change their size without any quality loss, dynamically change their color, and their file size is generally smaller than an equivalent png/jpg.&lt;/p&gt;

&lt;p&gt;Getting the actual SVGs can be done through the export options of design tools like Figma, Sketch, or Illustrator. &lt;/p&gt;

&lt;p&gt;Once you have the files you can further optimize them with SVGO, there is a command line tool and a web based one.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/svg" rel="noopener noreferrer"&gt;
        svg
      &lt;/a&gt; / &lt;a href="https://github.com/svg/svgo" rel="noopener noreferrer"&gt;
        svgo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      ⚙️ Node.js tool for optimizing SVG files
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;




&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jakearchibald" rel="noopener noreferrer"&gt;
        jakearchibald
      &lt;/a&gt; / &lt;a href="https://github.com/jakearchibald/svgomg" rel="noopener noreferrer"&gt;
        svgomg
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Web GUI for SVGO
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Both work automatically by pointing them to the file though the web version makes the available options more accessible. Make sure to have a look at the final result to make sure your icons still look fine.&lt;/p&gt;

&lt;p&gt;Then we bring the icons into our Vue app, I used a method recommended in the Vue Cookbook. It's a system made by Sarah Drasner, the SVG queen, and you can find the link for it &lt;a href="https://vuejs.org/v2/cookbook/editable-svg-icons.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To make it work with TailwindCSS, you'll need to make a couple of changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;functional&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;
    &lt;span class="na"&gt;:width=&lt;/span&gt;&lt;span class="s"&gt;"props.size"&lt;/span&gt;
    &lt;span class="na"&gt;:height=&lt;/span&gt;&lt;span class="s"&gt;"props.size"&lt;/span&gt;
    &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 16 16"&lt;/span&gt;
    &lt;span class="na"&gt;:aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"props.iconName"&lt;/span&gt;
    &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"presentation"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fill-current inline-block align-baseline"&lt;/span&gt;
    &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"[
      data.class,
      data.staticClass
    ]"&lt;/span&gt;
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-bottom: -2px;"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ props.icon.replace(/-/g, ' ') }} icon&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt; &lt;span class="na"&gt;:is=&lt;/span&gt;&lt;span class="s"&gt;"injections.components[props.iconName]"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Since SVGs themselves are rather light, it felt like a lot of overhead to use full components, so I made some further changes to make use of functional components, you can check out my fork here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/MaxMonteil" rel="noopener noreferrer"&gt;
        MaxMonteil
      &lt;/a&gt; / &lt;a href="https://github.com/MaxMonteil/vue-sample-svg-icons" rel="noopener noreferrer"&gt;
        vue-sample-svg-icons
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An opinionated example of how to use SVG icons in a Vue.js application as functional components
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  Buttons &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Initially I wanted to bring in buttons the same way as with icons, using Vue components but that ended up being deceptively complicated. The component had to work with buttons, links, or a &lt;code&gt;router-link&lt;/code&gt; (using vue-router).&lt;/p&gt;

&lt;p&gt;Supporting links was important for accessibility and semantics as links are meant to take you to another page whereas buttons should not.&lt;/p&gt;

&lt;p&gt;As a solution I extracted the common classes into their own utilities in TailwindCSS, which in pure css is just a normal class rule.&lt;/p&gt;

&lt;p&gt;Some examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;.btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;font-medium&lt;/span&gt; &lt;span class="err"&gt;rounded&lt;/span&gt; &lt;span class="err"&gt;align-bottom;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.btn-primary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;px-8&lt;/span&gt; &lt;span class="err"&gt;py-2;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.btn-secondary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;px-5&lt;/span&gt; &lt;span class="err"&gt;py-1;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.btn-white&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-green-darker&lt;/span&gt; &lt;span class="err"&gt;bg-white;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.btn-green&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-white&lt;/span&gt; &lt;span class="err"&gt;bg-green-dark;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Text Inputs &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;For inputs we can use Vue components but there are a few things to take into consideration.&lt;/p&gt;

&lt;p&gt;Our wrapper components need to be lightweight and transparent, we can do that by using functional components and attaching all attributes and event listeners.&lt;/p&gt;

&lt;p&gt;I also took the chance to include the label into the component. It fits the design, is more accessible, and ensures I never forget them.&lt;/p&gt;

&lt;p&gt;Start off with a &lt;code&gt;BaseInput.vue&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/components/inputs/BaseInput.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;functional&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;
    &lt;span class="na"&gt;:ref=&lt;/span&gt;&lt;span class="s"&gt;"data.ref"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-sm leading-none font-medium"&lt;/span&gt;
    &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"props.makeGray ? 'text-gray-darker' : 'text-green-darker'"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{ props.label }}
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
      &lt;span class="na"&gt;:ref=&lt;/span&gt;&lt;span class="s"&gt;"data.ref"&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block mt-2 bg-white rounded w-full outline-none focus:shadow"&lt;/span&gt;
      &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"[
        data.class,
        data.staticClass,
      ]"&lt;/span&gt;
      &lt;span class="na"&gt;:style=&lt;/span&gt;&lt;span class="s"&gt;"[
        data.style,
        data.staticStyle,
      ]"&lt;/span&gt;
      &lt;span class="na"&gt;v-bind=&lt;/span&gt;&lt;span class="s"&gt;"data.attrs"&lt;/span&gt;
      &lt;span class="na"&gt;v-on=&lt;/span&gt;&lt;span class="s"&gt;"{ ...listeners, input: e =&amp;gt; listeners.input(e.target.value) }"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And here is an example use of &lt;code&gt;BaseInput.vue&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/components/inputs/InputLarge.vue --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;functional&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt;
    &lt;span class="na"&gt;:is=&lt;/span&gt;&lt;span class="s"&gt;"injections.components.BaseInput"&lt;/span&gt;
    &lt;span class="na"&gt;:label=&lt;/span&gt;&lt;span class="s"&gt;"props.label"&lt;/span&gt;
    &lt;span class="na"&gt;:makeGray=&lt;/span&gt;&lt;span class="s"&gt;"props.makeGray"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"font-medium text-3xl text-black pl-4 py-px"&lt;/span&gt;
    &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"props.makeGray ? 'bg-gray' : 'bg-white'"&lt;/span&gt;
    &lt;span class="na"&gt;v-bind=&lt;/span&gt;&lt;span class="s"&gt;"data.attrs"&lt;/span&gt;
    &lt;span class="na"&gt;v-on=&lt;/span&gt;&lt;span class="s"&gt;"listeners"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;BaseInput&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/inputs/BaseInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inputLarge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;BaseInput&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice how we call the &lt;code&gt;BaseInput.vue&lt;/code&gt; component. Surprisingly, imported components are not exposed in functional components when using the template format. So instead we place the imported components into injections. They could also placed into props if you prefer.&lt;/p&gt;

&lt;p&gt;This method was brought up in this github issue:&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/vuejs/vue/issues/7492" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Functional single file component with components option.
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#7492&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/terrierscript" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F13282103%3Fv%3D4" alt="terrierscript avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/terrierscript" rel="noopener noreferrer"&gt;terrierscript&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/vuejs/vue/issues/7492" rel="noopener noreferrer"&gt;&lt;time&gt;Jan 21, 2018&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Version&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;2.5.13&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Reproduction link&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;NG pattern (functional)
&lt;a href="https://codesandbox.io/s/004vv2onw0" rel="nofollow noopener noreferrer"&gt;https://codesandbox.io/s/004vv2onw0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;OK pattern (no functional)
&lt;a href="https://codesandbox.io/s/q9k5q8qq56" rel="nofollow noopener noreferrer"&gt;https://codesandbox.io/s/q9k5q8qq56&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Steps to reproduce&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;I found can't use &lt;code&gt;components&lt;/code&gt; option when  &lt;code&gt;functional&lt;/code&gt; single file component.&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;template&lt;/span&gt; &lt;span class="pl-c1"&gt;functional&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;some-children&lt;/span&gt; /&amp;gt;
  &lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;template&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;SomeChildren&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"./SomeChildren"&lt;/span&gt;

&lt;span class="pl-k"&gt;export&lt;/span&gt; &lt;span class="pl-k"&gt;default&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c1"&gt;components&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    SomeChildren
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;It's occure &lt;code&gt;Unknown custom element&lt;/code&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;What is expected?&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Not occure &lt;code&gt;Unknown custom element&lt;/code&gt; and use child component&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;What is actually happening?&lt;/h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;It's occure &lt;code&gt;Unknown custom element&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In workaround, it not occure when use &lt;code&gt;Vue.component&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;Vue&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"vue"&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;SomeChildren&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"./SomeChildren"&lt;/span&gt;
&lt;span class="pl-v"&gt;Vue&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;component&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"some-children"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-v"&gt;SomeChildren&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-k"&gt;export&lt;/span&gt; &lt;span class="pl-k"&gt;default&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;

&lt;span class="pl-c"&gt;// can use  &amp;lt;some-children /&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/vuejs/vue/issues/7492" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  Radio Buttons &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;After all the elements we've done so far, Radio Buttons are not too different. The difference is that styling them can be more involved. While it is possible to use standard buttons instead, I wanted to use the default radio buttons, again for semantics and accessibility.&lt;/p&gt;

&lt;p&gt;The trick I found was to use the behavior of labels wrapping radio buttons.&lt;br&gt;
The buttons by themselves are small and hard to touch/click but if you wrap them in a label, clicking anywhere on the label box will also select the radio button.&lt;br&gt;
Using this, I styled radio buttons by actually making the label look as I wanted and hiding the radio buttons inside the label.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;functional&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
    &lt;span class="na"&gt;:ref=&lt;/span&gt;&lt;span class="s"&gt;"data.ref"&lt;/span&gt;
    &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"[
      data.class,
      data.staticClass,
    ]"&lt;/span&gt;
    &lt;span class="na"&gt;:style=&lt;/span&gt;&lt;span class="s"&gt;"[
      data.style,
      data.staticStyle,
    ]"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;
      &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"label in props.labels"&lt;/span&gt;
      &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"label.value || label"&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative px-3 py-1 rounded"&lt;/span&gt;
      &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"(label.value || label) === props.modelValue ? '...' : '...'"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="na"&gt;:label=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        {{ label.text || label }}
      &lt;span class="nt"&gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;:ref=&lt;/span&gt;&lt;span class="s"&gt;"data.ref"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt;
        &lt;span class="na"&gt;:value=&lt;/span&gt;&lt;span class="s"&gt;"label.value || label"&lt;/span&gt;
        &lt;span class="na"&gt;v-on=&lt;/span&gt;&lt;span class="s"&gt;"{ ...listeners, input: e =&amp;gt; listeners.input(e.target.value) }"&lt;/span&gt;
        &lt;span class="na"&gt;:checked=&lt;/span&gt;&lt;span class="s"&gt;"(label.value || label) === props.modelValue"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be careful when hiding the radio button as it still needs to be visible to screen readers, tailwind offers a class for this, in standard css that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.sr-only&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As for the &lt;code&gt;v-slot&lt;/code&gt; and all the ORs (||), I explain those in the next section about &lt;code&gt;select&lt;/code&gt; inputs.&lt;/p&gt;

&lt;p&gt;A really helpful resource that also goes over checkboxes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.smashingmagazine.com/2017/08/creating-custom-inputs-vue-js" rel="noopener noreferrer"&gt;Smashing Magazine - Creating Custom Inputs in VueJS&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Select Input &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The select component is a fun one to wrap both in terms of design and functionality.&lt;/p&gt;

&lt;p&gt;Design wise it was surprising to discover how "hacky" it is to change the default downward arrow. There are a few ways to do it but the trick I went with is to remove the default style by setting &lt;code&gt;appearance: none;&lt;/code&gt; and then bringing in my SVG of choice with the URL function of CSS.&lt;/p&gt;

&lt;p&gt;To do something similar you will need to encode your SVG tag into a URL compatible string, I found this site to do just that:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://yoksel.github.io/url-encoder/" rel="noopener noreferrer"&gt;URL Encoder for SVG&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then there are a few more positioning and spacing styles to place the icon where you want.&lt;/p&gt;

&lt;p&gt;For functionality, the end user should retain control over how the drop-down values are displayed, the go to solution is to use scoped slots. With this method our component can support any array of values.&lt;/p&gt;

&lt;p&gt;This is because the official Vue doc shows examples using a String Array and an Object Array to populate the select.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;functional&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-sm font-medium text-green-darker"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{ props.label }}
    &lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt;
      &lt;span class="na"&gt;:ref=&lt;/span&gt;&lt;span class="s"&gt;"data.ref"&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"custom-arrow bg-no-repeat block mt-2 pl-2 pr-6 bg-white rounded text-black text-lg outline-none focus:shadow"&lt;/span&gt;
      &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"[
        data.class,
        data.staticClass,
      ]"&lt;/span&gt;
      &lt;span class="na"&gt;:style=&lt;/span&gt;&lt;span class="s"&gt;"[
        data.style,
        data.staticStyle,
      ]"&lt;/span&gt;
      &lt;span class="na"&gt;v-bind=&lt;/span&gt;&lt;span class="s"&gt;"data.attrs"&lt;/span&gt;
      &lt;span class="na"&gt;v-on=&lt;/span&gt;&lt;span class="s"&gt;"{ ...listeners, input: e =&amp;gt; listeners.input(e.target.value) }"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt;
        &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"option in props.values"&lt;/span&gt;
        &lt;span class="na"&gt;:value=&lt;/span&gt;&lt;span class="s"&gt;"option.value || option"&lt;/span&gt;
        &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"option.value || option"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="na"&gt;:option=&lt;/span&gt;&lt;span class="s"&gt;"option"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inputSelect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;require&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;require&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.custom-arrow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;-moz-appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='10'%3E%3Cpath fill='%23000000' d='M7.443 9.057L5.229 6.843a.666.666 0 01.943-.942l2.214 2.214 2.199-2.199a.666.666 0 11.942.942l-3.142 3.143-.942-.944z'/%3E%3C/svg%3E")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;top&lt;/span&gt; &lt;span class="m"&gt;35%&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These wrapper components also offer the chance to enforce best practices like a label for an input and an empty disabled first option for better iOS support.&lt;/p&gt;

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

&lt;p&gt;Well you've reached the end, this covers all the components I brought in from my design library. It really just scratches the surface of what is possible and I couldn't hope to be fully exhaustive. Nonetheless, I hope this helped you out and encouraged you to tackle that redesign or even just start to design!&lt;/p&gt;

&lt;p&gt;The next step after wrapping all these input fields would be to compose them into a form or other layout for reuse, these are some of the components you'd place into the &lt;code&gt;src/layouts&lt;/code&gt; folder. You can expect a follow up article once I finish it up myself :D&lt;/p&gt;

&lt;p&gt;Feel free to leave any comments below and if you have questions or want to follow up, you can find me on twitter &lt;a href="https://twitter.com/maxmonteil" rel="noopener noreferrer"&gt;@MaxMonteil&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://markus.oberlehner.net/blog/setting-up-tailwind-css-with-vue/" rel="noopener noreferrer"&gt;Markus O. - Setting up Tailwind CSS with Vue.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://markus.oberlehner.net/blog/reusable-functional-vue-components-with-tailwind-css/" rel="noopener noreferrer"&gt;Markus O. - Reusable Functional Vue.js Components with Tailwind CSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vue</category>
      <category>design</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
