<?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>Forem: Zean Qin</title>
    <description>The latest articles on Forem by Zean Qin (@zeanqin).</description>
    <link>https://forem.com/zeanqin</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%2F466627%2F2b45cf03-f0a4-4f71-99d7-7d936e39df37.jpeg</url>
      <title>Forem: Zean Qin</title>
      <link>https://forem.com/zeanqin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/zeanqin"/>
    <language>en</language>
    <item>
      <title>Extract handwritten data from paper forms using Azure Form Recognizer with supervised learning</title>
      <dc:creator>Zean Qin</dc:creator>
      <pubDate>Mon, 26 Oct 2020 11:05:07 +0000</pubDate>
      <link>https://forem.com/zeanqin/extract-handwritten-data-from-paper-forms-using-azure-form-recognizer-with-supervised-learning-135g</link>
      <guid>https://forem.com/zeanqin/extract-handwritten-data-from-paper-forms-using-azure-form-recognizer-with-supervised-learning-135g</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;At the shift changeover in most hospitals, a paper form is completed to summarise the last shift and highlight key aspects of the upcoming shift. As an example, a completed form could look like below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gvjl7AM1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dnmjqhz4w53y07r8dd7k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gvjl7AM1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dnmjqhz4w53y07r8dd7k.jpg" alt="An example of a completed Change of Shift Huddle form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These completed forms are gathered together at a later point, and a person will go through each and manually enter the data on a computer. The recorded data can then be fed into an analytical system to generate insights.&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal of the project
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The main goal of this project&lt;/strong&gt; is to streamline the data collection process by building a tool that,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the users can use to take a picture of the completed forms using their smart phones,&lt;/li&gt;
&lt;li&gt;the tool then automatically extracts the data and pre-fills the form, and&lt;/li&gt;
&lt;li&gt;the user can review and save the data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user can choose to either take the picture and upload it immediately after completing the form, or take pictures of multiple completed forms at a later point.&lt;/p&gt;

&lt;p&gt;A few other features include,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;providing a digital version of the form that the user can fill in directly on their phone/tablet,&lt;/li&gt;
&lt;li&gt;saving the original image along with the extracted data,&lt;/li&gt;
&lt;li&gt;linking data e.g. Unit, Users etc. to existing entities in the system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://riberry.health"&gt;Riberry&lt;/a&gt;, we build a platform for collecting data and generating insights within hospital environments, as well as delivering clinical improvement programs. This article only focuses on extracting data from paper forms using OCR - a feature on the data collection side of things.&lt;/p&gt;

&lt;p&gt;And below is a demo of how data is extracted from a paper form and entered into the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kWckV1Ga--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bq0lsfmnzvjeasz79tvz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kWckV1Ga--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bq0lsfmnzvjeasz79tvz.gif" alt="Extracting data from a Change of Shift Huddle form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture overview
&lt;/h2&gt;

&lt;p&gt;We're using the &lt;a href="https://azure.microsoft.com/en-au/services/cognitive-services/form-recognizer/"&gt;Form Recognizer&lt;/a&gt; service from Microsoft to extract data from the forms. More specifically, we've trained a custom model, instead of using the pre-built models, to better handle the structure of the forms that our clients use.&lt;/p&gt;

&lt;p&gt;At the very high level, we will train a model first. The users can then upload photos of the filled forms via the frontend (a.k.a Riberry) to the API called Rumble. Rumble then uses the trained model to extract data from the form and transforms and cleans up the extracted data before sending it back to the user. The image below has a more detailed explanation of this process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QA3VNzxl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/j159c35xzm0pbvpgu1es.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QA3VNzxl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/j159c35xzm0pbvpgu1es.png" alt="Architecture Overview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Train the model
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: the focus of this article is to showcase the final product and to reflect on the lessons learnt in adding the OCR support, rather than a step-by-step guide on how to use the Azure Form Recognizer service. There is no point in writing a guide either; you can't really beat their &lt;a href="https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/?branch=release-build-cogserv-forms-recognizer"&gt;documentation and examples&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're looking at learning how to use the Form Recognizer service, I highly recommend reading through their documentation - it's really the most effective way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the core of the Form Recognizer service is a set of &lt;a href="https://westus2.dev.cognitive.microsoft.com/docs/services/form-recognizer-api-v2/operations/AnalyzeWithCustomForm"&gt;REST APIs&lt;/a&gt; that allow you to train a model by using supervised/unsupervised machine learning, manage (e.g. list, delete, copy) models, and extract data using custom/pre-built models.&lt;/p&gt;

&lt;p&gt;In addition, it also offers &lt;a href="https://github.com/microsoft/OCR-Form-Tools"&gt;an open source tool (OCR-Form-Tools)&lt;/a&gt; that interacts with these REST APIs and provides an intuitive UI to label your data, train a model and validate the model by predicting a test document.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: In &lt;a href="https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/quickstarts/label-tool?tabs=v2-0#set-up-the-sample-labeling-tool"&gt;their guide on setting up the sample labelling tool&lt;/a&gt;, they talk about running the tool via Docker.&lt;/p&gt;

&lt;p&gt;Instead of using Docker, I find it's much easer to just check out a copy of the tool from &lt;a href="https://github.com/microsoft/OCR-Form-Tools"&gt;GitHub&lt;/a&gt; and build/run it from source.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After having the tool up and running, I was able to label our sample data, train a model and test the model with ease.&lt;/p&gt;

&lt;h5&gt;
  
  
  Label the training data
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SRJ2Pqpi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/u4d8e8gww7hpio904y7g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRJ2Pqpi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/u4d8e8gww7hpio904y7g.png" alt="Label the training data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Train a model
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5mTrVZG_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7hthpvndkrq36wab1sp8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5mTrVZG_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7hthpvndkrq36wab1sp8.png" alt="Train a model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Validate the trained model
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--khacrFoj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xdu2moa6ycnjej0u2j9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--khacrFoj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xdu2moa6ycnjej0u2j9d.png" alt="Validate the trained model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Miscellaneous Reflections
&lt;/h2&gt;

&lt;p&gt;Now that the project has been deployed to production, it's a good time to look it retrospectively and reflect on various things/lessons that I learnt from doing it.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The training process
&lt;/h4&gt;

&lt;p&gt;There should be 3 main stages in predicting the contents of a form using the supervised learning approach,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;detect the text elements (printed or handwritten) on the form and calculate their expected sizes and positions,&lt;/li&gt;
&lt;li&gt;extract the actual text content from each element,&lt;/li&gt;
&lt;li&gt;classify the elements, using their size and position info obtained from step 1, into the different labels specified.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: With the supervised learning, we're fitting a model for classifying elements on the page into different labels based on the position info of these elements on the page.&lt;/p&gt;

&lt;p&gt;And we're &lt;strong&gt;NOT&lt;/strong&gt; training any NLP models to extract texts from the elements identified on the page. This part is handled internally by the Form Recognizer service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The position and size of each text element is expressed by its bounding box which consists of 8 numbers. And these 8 numbers should be the main features for each training instance i.e. every text element.&lt;/p&gt;

&lt;p&gt;There are also likely a lot of data argumentation and feature manipulation techniques applied on the training data to expand the dataset artificially, in order to improve the accuracy of the models. For example, they'll likely use some label-preserving transformation techniques such as denoising/sharpening, elastic distortion, affine transformation, dimensionality reduction etc.&lt;/p&gt;

&lt;p&gt;But overall, the cost function should be relatively simple with just a few variables. And I'm not surprised that &lt;a href="https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/overview#custom-models"&gt;only 5 documents&lt;/a&gt; are required to get started with.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Adding support for checkbox/radio question
&lt;/h4&gt;

&lt;p&gt;The Form Recognizer service &lt;a href="https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/overview#input-requirements"&gt;doesn't officially support checkboxes or radio buttons&lt;/a&gt;, but I've been able to get around it easily by,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;applying a label for each option in a checkbox or radio question, and&lt;/li&gt;
&lt;li&gt;at prediction time, treating those labels with a value present as the options that the user has selected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an example, the form that the hospitals used contains a checkbox question with two options - AM or PM. During the training process, I'd set up the labels &lt;code&gt;_Shift.9:00&lt;/code&gt; and &lt;code&gt;_Shift.17:00&lt;/code&gt; for the AM and the PM option respectively (see below).  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--INCy0vRf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kt2u84d4j53pxmlk6795.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--INCy0vRf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kt2u84d4j53pxmlk6795.png" alt="Add support for checkbox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hypothetically, if we fed the form above to the trained model, a string 'x' will be extracted for both &lt;code&gt;_Shift.9:00&lt;/code&gt; and &lt;code&gt;_Shift.17:00&lt;/code&gt;. I'd consider both options checked if this was a checkbox question, and I'd consider the first option is checked if this was a radio question.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Extracting the data asynchronously and notifying the users when the result is ready
&lt;/h4&gt;

&lt;p&gt;After &lt;strong&gt;POST&lt;/strong&gt;ing a form to their endpoint to extract data from it, you get back an &lt;code&gt;Operation-Location&lt;/code&gt;, &lt;a href="https://westus2.dev.cognitive.microsoft.com/docs/services/form-recognizer-api-v2/operations/AnalyzeWithCustomForm"&gt;which is a URL containing the &lt;code&gt;resultId&lt;/code&gt; used to track the progress and obtain the result of the analyse operation&lt;/a&gt;. And we're meant to repeatedly check this URL for the result data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: I set the frequency of checking for the result to once per second. This seems to be the same frequency that their OCR tool uses for checking for the result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: If your users have to wait for the result before taking the next action, compress the image before &lt;strong&gt;POSTing&lt;/strong&gt; it for analysis to shorten wait time.&lt;/p&gt;

&lt;p&gt;The images taken on smartphones are fairly large these days, and I compress them down to &amp;lt;1MB before sending them for analysis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your users don't have to wait for the analyse result, I'd highly recommend uploading the files into a blob storage and running a background task to analyse these files asynchronously. Users can be notified after the result is ready, if needed.&lt;/p&gt;

&lt;p&gt;We couldn't do it because one of the requirements is that users need to take a photo of the form, extract data from it in real-time and save it to generate insights. In practice, the process usually takes around 5 seconds.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Don't over-automate the data binding process
&lt;/h4&gt;

&lt;h5&gt;
  
  
  4.1 Background
&lt;/h5&gt;

&lt;p&gt;The fields on the paper form don't match up exactly with those on the digital version - some data on the paper form is ignored while there are a couple of extra fields on the web version. In addition, while the paper form rarely changes, we keep updating the digital form slightly either by adding/removing questions or moving around the existing questions.&lt;/p&gt;

&lt;h5&gt;
  
  
  4.2 How we designed it
&lt;/h5&gt;

&lt;p&gt;The questions and their layouts on the web form are defined using a JSON file. Without getting into the details, in the screenshot below, the JSON structure (right) defines the &lt;strong&gt;Team&lt;/strong&gt; section (left) of the form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GUzW3end--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3ucji8pfapzl4d854mnr.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GUzW3end--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3ucji8pfapzl4d854mnr.jpeg" alt="Define questions in JSON"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, we also use the names of the questions from the JSON file to automatically construct the label (used in OCR) for that question. For example, in the screenshot below, the label - &lt;strong&gt;Team..NursesShort&lt;/strong&gt; - for the "Nurses Short" question is created by joining the names of all its ancestors and itself with a dot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n6Y_-OEY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2bwfwdpjdqmo86wcxgu8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n6Y_-OEY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2bwfwdpjdqmo86wcxgu8.jpeg" alt="Label from question names"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  4.3 Problem with the design
&lt;/h5&gt;

&lt;p&gt;This means that whenever we change the digital version of the form (e.g. adding/removing questions or re-arranging the fields on the form), a new set of labels will be generated. And we'd have to re-name the labels used in Form Recognizer, re-train the model, copy it to production etc. This is a really time consuming process.&lt;/p&gt;

&lt;h5&gt;
  
  
  4.4 What we should have done instead
&lt;/h5&gt;

&lt;p&gt;We should have de-coupled the label generation from the JSON definition of a form.&lt;/p&gt;

&lt;p&gt;Given that the paper form rarely changes, we should have just hardcoded a set of labels to be used in the Form Recognizer service. We could then expand the JSON definition to include a &lt;code&gt;label&lt;/code&gt; property that specifies the label used for each question, if applicable.&lt;/p&gt;

&lt;p&gt;This not only simplifies the code for binding the data (i.e. converting the extracted data into domain objects), but also means that we can freely re-arrange the questions on the form without having to re-train the model in Form Recognizer.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://westus2.dev.cognitive.microsoft.com/docs/services/form-recognizer-api-v2/operations/AnalyzeWithCustomForm"&gt;Form Recognizer API (v2.0)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/?branch=release-build-cogserv-forms-recognizer"&gt;Form Recognizer documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/OCR-Form-Tools"&gt;OCR-Form-Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/quickstarts/label-tool?tabs=v2-0#set-up-the-sample-labeling-tool"&gt;Set up the sample labeling tool&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Feedbacks
&lt;/h2&gt;

&lt;p&gt;That's it! Let me know in the comment section below if you have any feedbacks.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>formrecognizer</category>
      <category>ocr</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Typography basics and best practices for software developers</title>
      <dc:creator>Zean Qin</dc:creator>
      <pubDate>Mon, 21 Sep 2020 11:57:12 +0000</pubDate>
      <link>https://forem.com/zeanqin/typography-basics-and-best-practices-for-software-developers-542n</link>
      <guid>https://forem.com/zeanqin/typography-basics-and-best-practices-for-software-developers-542n</guid>
      <description>&lt;p&gt;Typography is about shaping and laying out the texts on your website to create a pleasant user experience. &lt;a href="https://ia.net/topics/the-web-is-all-about-typography-period" rel="noopener noreferrer"&gt;Web design is 95% typography&lt;/a&gt;. And it's essential for website developers to know the basics and some common practices.&lt;/p&gt;

&lt;p&gt;There's a lot more jargon and subjectivity in typography than in other areas. This article is intended as a concise and practical guide for software developers on choosing/using custom fonts and laying out the texts nicely and comfortably. It covers the basics to get you most of the way there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basics
&lt;/h2&gt;

&lt;p&gt;Let's start with some basic terms and the common categories of fonts.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Typeface vs. Font
&lt;/h3&gt;

&lt;p&gt;"&lt;strong&gt;Typeface&lt;/strong&gt;" and "&lt;strong&gt;Font&lt;/strong&gt;" mean different things,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;typeface&lt;/strong&gt; is the design (e.g. shape) of a collection of letters, numbers and symbols (also called glyphs), whereas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;font&lt;/strong&gt; is a specific size, weight, or style (e.g. regular, bold, italic) of a typeface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the time people use them interchangeably. But hey, we're trying to look professional here ;-).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Common Font Categories
&lt;/h3&gt;

&lt;p&gt;Typefaces come in all different shapes, and there is no single classification system. Below are a few of the most commonly seen categories.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.1 Serif
&lt;/h4&gt;

&lt;p&gt;A “serif” is a small stroke attached to the ends of letters, giving them a traditional feel. The "serif" category includes a few sub-categories such as Old Style, Classical, Neo-Classical, Transitional, Clarendon, etc. These typefaces are mostly used in books and newspapers.&lt;/p&gt;

&lt;p&gt;Below is what the "Caslon" font (in the Old Style subcategory) looks like. Notice the small feet present at the tops and bottoms of each letter.&lt;/p&gt;

&lt;p&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%2F6m8dalnev54q1cgvkodd.png" 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%2F6m8dalnev54q1cgvkodd.png" alt="Caslon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the &lt;a href="https://www.typewolf.com/top-10-serif-fonts" rel="noopener noreferrer"&gt;top 10 most popular serif fonts here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.2 Sans Serif
&lt;/h4&gt;

&lt;p&gt;"Sans" comes from the French "without" and as the name states - sans-serif typefaces don’t have these extra strokes, giving them a smooth, modern feel. It includes humanist, geometric and grotesk etc. as sub-categories.&lt;/p&gt;

&lt;p&gt;Below is the font called "Futura" in the Sans Serif category. Again, notice the clean and straight lines, compared to the serif fonts.&lt;/p&gt;

&lt;p&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%2Fepuqximp6vtmqsmot33i.png" 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%2Fepuqximp6vtmqsmot33i.png" alt="Futura"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the &lt;a href="https://www.typewolf.com/top-10-sans-serif-fonts" rel="noopener noreferrer"&gt;top 10 most popular sans serif fonts here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3 Monospace
&lt;/h4&gt;

&lt;p&gt;Monospaced typefaces are non-proportional - every glyph takes up the exact same space. It's possible to arrange them into visual structures. And your code editor probably uses a monospace font.&lt;/p&gt;

&lt;p&gt;Below is the "Courier" font which is a monospace font.&lt;/p&gt;

&lt;p&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%2Fsxhek8zo3xd51mxlr9fu.png" 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%2Fsxhek8zo3xd51mxlr9fu.png" alt="Courier"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the &lt;a href="https://www.typewolf.com/top-10-monospaced-fonts" rel="noopener noreferrer"&gt;top 10 most popular monospace fonts here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. "Web safe" Fonts vs Custom Fonts
&lt;/h3&gt;

&lt;p&gt;A font is "web safe" if most computers have it installed already, and these computers don't have to download it separately when visiting your site. Examples include Arial, Times New Roman, Courier, Georgia, Verdana and &lt;a href="http://web.mit.edu/jmorzins/www/fonts.html" rel="noopener noreferrer"&gt;more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A website can declare a custom font as a resource, just like CSS, images or JavaScripts. And the visiting browser will download the font and apply it to the texts on the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using fonts
&lt;/h2&gt;

&lt;p&gt;You can apply a font by using the "font-family" attribute with the following syntax in CSS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'custom-font'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallback2&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;Where is the &lt;code&gt;custom-font&lt;/code&gt; coming from? Well, it could be from either your OS if you have it installed (e.g. &lt;code&gt;Segoe UI&lt;/code&gt; on Windows, or &lt;code&gt;Roboto&lt;/code&gt; on Android), or a third party such as &lt;a href="https://fonts.google.com/" rel="noopener noreferrer"&gt;Google Fonts&lt;/a&gt;, &lt;a href="https://fonts.adobe.com/" rel="noopener noreferrer"&gt;Adobe Fonts&lt;/a&gt; etc. In the later case, you most likely need to tell the browser to download it by including the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag in the &lt;code&gt;head&lt;/code&gt; of your page, like below.&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;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com/css?family=Roboto"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flash of Unstyled Text
&lt;/h3&gt;

&lt;p&gt;In the case that a custom font used by a website is not available on the visiting computer, it needs to be transferred across the wire. This takes time! The browser will use a fallback font in the font stack until the custom font is loaded. This can cause the &lt;a href="https://www.paulirish.com/2009/fighting-the-font-face-fout/" rel="noopener noreferrer"&gt;flash of unstyled text (FOUT)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As an example, suppose you had the font stack below,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Merriweather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Georgia&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the font "Merriweather" needs to be downloaded by the browser, you could have the texts to flash like below after the page is loaded.&lt;/p&gt;

&lt;p&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%2Fdqvhzxktzwvtshjj1ve0.gif" 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%2Fdqvhzxktzwvtshjj1ve0.gif" alt="Flash of Unstyled Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some common approaches to mitigate FOUT include,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;using a third party (e.g. &lt;a href="https://fonts.google.com/" rel="noopener noreferrer"&gt;Google Fonts&lt;/a&gt;, &lt;a href="https://fonts.adobe.com/" rel="noopener noreferrer"&gt;Adobe Fonts&lt;/a&gt; etc.) to optimise the font files and deliver them via a CDN,&lt;/li&gt;
&lt;li&gt;transforming your font files using tools like &lt;a href="https://transfonter.org/" rel="noopener noreferrer"&gt;Transfonter&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;picking a fallback font that's similar to the custom font you want to use with tools like &lt;a href="https://meowni.ca/font-style-matcher/" rel="noopener noreferrer"&gt;Font style matcher&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Native font stack
&lt;/h3&gt;

&lt;p&gt;A “native font stack” allows for optimum text rendering on every device and OS with &lt;strong&gt;zero latency&lt;/strong&gt;. For example, Bootstrap 4 uses the following native font stack by default,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nv"&gt;$font-family-sans-serif&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Safari&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;OS&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;iOS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;San&lt;/span&gt; &lt;span class="n"&gt;Francisco&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;apple-system&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Chrome&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;56&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;OS&lt;/span&gt; &lt;span class="nf"&gt;X&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;San&lt;/span&gt; &lt;span class="n"&gt;Francisco&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;BlinkMacSystemFont&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Windows&lt;/span&gt;
  &lt;span class="s2"&gt;"Segoe UI"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Android&lt;/span&gt;
  &lt;span class="s2"&gt;"Roboto"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Basic&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;
  &lt;span class="s2"&gt;"Helvetica Neue"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Emoji&lt;/span&gt; &lt;span class="n"&gt;fonts&lt;/span&gt;
  &lt;span class="s2"&gt;"Apple Color Emoji"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Segoe UI Emoji"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Segoe UI Symbol"&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can find more system font stacks &lt;a href="https://css-tricks.com/snippets/css/system-font-stack/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;p&gt;I've listed some of the practices that I follow in choosing font size, heading size, line height, letter spacing and how many characters should be included in one line.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Font sizing
&lt;/h3&gt;

&lt;p&gt;Sizing in CSS can be specified in either &lt;em&gt;absolute&lt;/em&gt; units (think &lt;code&gt;px&lt;/code&gt;) or &lt;em&gt;relative&lt;/em&gt; units (e.g. &lt;code&gt;em&lt;/code&gt;). The size of an element specified in absolute units doesn't change, while the size of an element specified in relative units depends on the size of other elements on the page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should avoid using absolute units like &lt;code&gt;px&lt;/code&gt;, and instead use relative units like &lt;code&gt;em&lt;/code&gt; as much as possible.&lt;/p&gt;

&lt;p&gt;The purpose of the &lt;code&gt;px&lt;/code&gt; unit should be to serve as the foundation of a type system based on relative units. In other words, it’s an absolute value that a relative unit can point to in order to define its own size relative to that value.&lt;/p&gt;

&lt;p&gt;Fun fact, the &lt;code&gt;px&lt;/code&gt; unit doesn't actually have anything to do with screen pixels - it's just a poorly chosen name. It's actually &lt;a href="http://inamidst.com/stuff/notes/csspx" rel="noopener noreferrer"&gt;an non-linear angular measurement&lt;/a&gt;. And this is why you can actually specify pixels in decimals such as &lt;code&gt;12.4px&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As an example, most desktop browsers usually sets the texts inside the &lt;code&gt;body&lt;/code&gt; tag to &lt;code&gt;16px&lt;/code&gt; by default. You can use &lt;code&gt;2em&lt;/code&gt; on an element if you want it to be twice as big as the body text.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When setting the size of some texts, try to think in relative units - "I want element A to be twice as big as element B". Instead of thinking "I want element A to be &lt;code&gt;20px&lt;/code&gt;".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Heading sizing
&lt;/h3&gt;

&lt;p&gt;The size of headings are usually expressed in relative units. And it's common to define 6 levels of headings.&lt;/p&gt;

&lt;p&gt;Bootstrap 4 defines the size of headings as below,&lt;/p&gt;

&lt;p&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%2F5t1ofr6a7g8xo7tfiiiq.png" 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%2F5t1ofr6a7g8xo7tfiiiq.png" alt="Bootstrap Headings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another popular approach is to define a &lt;em&gt;modular scale&lt;/em&gt; to define the heading sizes. Basically, it means specifying a root number and a ratio, and ratios are multiplied by the base to produce a scale of numbers that is proportionally related. For example, the following scale uses base &lt;code&gt;1em&lt;/code&gt; and the ratio &lt;code&gt;1.5&lt;/code&gt;. You can use &lt;a href="https://www.modularscale.com/" rel="noopener noreferrer"&gt;this tool&lt;/a&gt; to create your custom scale.&lt;/p&gt;

&lt;p&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%2F7qkalbsh0aeq3ymnffgl.png" 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%2F7qkalbsh0aeq3ymnffgl.png" alt="Modular scale"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Line height
&lt;/h3&gt;

&lt;p&gt;Line height can be specified in CSS using the &lt;code&gt;line-height&lt;/code&gt; property like below,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="err"&gt;Note&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;better&lt;/span&gt; &lt;span class="err"&gt;to&lt;/span&gt; &lt;span class="err"&gt;not&lt;/span&gt; &lt;span class="err"&gt;use&lt;/span&gt; &lt;span class="err"&gt;any&lt;/span&gt; &lt;span class="err"&gt;unit&lt;/span&gt; &lt;span class="err"&gt;here.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you don't put a unit, it's relative to the computed size of the text. For example, if the text is &lt;code&gt;12px&lt;/code&gt; (or &lt;code&gt;0.75em&lt;/code&gt; in a browser with default font size of &lt;code&gt;16px&lt;/code&gt;)  in size, the line height will be &lt;code&gt;18px&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The browser sets the line height to 1.2 by default, but it's usually too tight. And a line height of 1.5 makes the viewing experience a lot better. See image below, the line height on the left is 1.2 while the line height on the right is 1.5.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&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%2F1tgorlh2gwkblts33n2i.png" 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%2F1tgorlh2gwkblts33n2i.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Letter spacing
&lt;/h3&gt;

&lt;p&gt;This is used a lot less compared to &lt;code&gt;line-height&lt;/code&gt; or &lt;code&gt;font-size&lt;/code&gt;. In general, we only need to specify custom spacing for texts that are either too big or too small. And you can do it in CSS using the &lt;code&gt;letter-spacing&lt;/code&gt; property. Similar to &lt;code&gt;font-size&lt;/code&gt;, it's best to be specified in relative units such as &lt;code&gt;em&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;letter-spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&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;h3&gt;
  
  
  5. Length of line
&lt;/h3&gt;

&lt;p&gt;The general consensus is that lines should contain 60 to 70 characters for best reading experience, especially for text heavy pages.&lt;/p&gt;

&lt;p&gt;And you can specify the line length in CSS using the following property,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&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;50ch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The unit &lt;code&gt;ch&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/length" rel="noopener noreferrer"&gt;represents the width, or more precisely the advance measure, of the glyph "0"&lt;/a&gt;. And &lt;code&gt;50ch&lt;/code&gt; generally results in line width to be 60 to 70 characters in length. A lot of text heavy sites, such as Medium.com, or even the Google search result page, put a similar limit on the number of characters in a line.&lt;/p&gt;

&lt;p&gt;That's all. Let me know in the comments section if you have any feedbacks!&lt;/p&gt;

</description>
      <category>typography</category>
      <category>webdev</category>
      <category>design</category>
      <category>css</category>
    </item>
    <item>
      <title>What are registers in Vim, how to use them and the best practices.</title>
      <dc:creator>Zean Qin</dc:creator>
      <pubDate>Thu, 10 Sep 2020 13:42:36 +0000</pubDate>
      <link>https://forem.com/zeanqin/what-are-registers-in-vim-how-to-use-them-and-the-best-practices-52p0</link>
      <guid>https://forem.com/zeanqin/what-are-registers-in-vim-how-to-use-them-and-the-best-practices-52p0</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;A register in Vim is basically a slot in memory that you can put a piece of text in and read it out later. Each register is like the clipboard provided by the OS (e.g. Windows, MacOS etc.), except that Vim has a LOT of them instead of just one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It takes a while to learn how to use them and some slow repetitions in the beginning to develop the muscle memory, but you'll never go back to the simple yank/paste again after getting comfortable with it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Basic usage
&lt;/h2&gt;

&lt;p&gt;You can list all registers and their contents by using the &lt;code&gt;:reg&lt;/code&gt; command, or using the &lt;code&gt;:reg &amp;lt;register-a&amp;gt; &amp;lt;register-b&amp;gt; ...&lt;/code&gt; to filter to the specific registers and their contents.&lt;/p&gt;

&lt;p&gt;Each register is accessed following the pattern &lt;code&gt;"&amp;lt;register-name&amp;gt;&amp;lt;command&amp;gt;&lt;/code&gt;. Note it's got 3 parts,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;"&lt;/code&gt; prefix,&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;&amp;lt;register-name&amp;gt;&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;&amp;lt;command&amp;gt;&lt;/code&gt; to perform on that register.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, &lt;code&gt;"ay&lt;/code&gt; yanks the current selected text and stores it in the register &lt;code&gt;"a&lt;/code&gt;, and &lt;code&gt;"ap&lt;/code&gt; pastes the current content of register &lt;code&gt;"a&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registers
&lt;/h2&gt;

&lt;p&gt;There're 9 different types of registers provided by Vim. I've listed them all below, but only included detailed explanation (with some common practices) for the ones I've used the most. For the advanced ones, I've included a link to the official documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Unnamed (or default) register
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;"&lt;/code&gt; register (accessed via &lt;code&gt;""&lt;/code&gt;) is the default register.&lt;/p&gt;

&lt;p&gt;Its content will be updated whenever you delete (using the &lt;code&gt;d&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;, &lt;code&gt;s&lt;/code&gt; or &lt;code&gt;x&lt;/code&gt; commands) or yank (using the &lt;code&gt;y&lt;/code&gt; command) some text.&lt;/p&gt;

&lt;p&gt;You can read the content of this register by using &lt;code&gt;""&lt;/code&gt;. For example, you can do &lt;code&gt;""p&lt;/code&gt; to paste the value store in it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Vim reads the content of the unnamed register for any put command (&lt;code&gt;p&lt;/code&gt; or &lt;code&gt;P&lt;/code&gt;) which does not specify a register. So, &lt;code&gt;p&lt;/code&gt; is the same as &lt;code&gt;""p&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Numbered registers
&lt;/h3&gt;

&lt;p&gt;These are the registers &lt;code&gt;"0&lt;/code&gt;, &lt;code&gt;"1&lt;/code&gt;, &lt;code&gt;"2&lt;/code&gt;, &lt;code&gt;"3&lt;/code&gt;, &lt;code&gt;"4&lt;/code&gt;, &lt;code&gt;"5&lt;/code&gt;, &lt;code&gt;"6&lt;/code&gt;, &lt;code&gt;"7&lt;/code&gt;, &lt;code&gt;"8&lt;/code&gt;, &lt;code&gt;"9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;"0&lt;/code&gt;&lt;/strong&gt; will be set by the most recent yank command, therefore it always has the content of the latest yank.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you yanked some text, you can always refer to it using &lt;code&gt;"0p&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;"1&lt;/code&gt;&lt;/strong&gt; will be set by the most recent delete or change command &lt;em&gt;&lt;strong&gt;only if the deleted or changed text is longer than one line (also called big delete)&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;"2&lt;/code&gt;, &lt;code&gt;"3&lt;/code&gt;, &lt;code&gt;"4&lt;/code&gt;, &lt;code&gt;"5&lt;/code&gt;, &lt;code&gt;"6&lt;/code&gt;, &lt;code&gt;"7&lt;/code&gt;, &lt;code&gt;"8&lt;/code&gt;, &lt;code&gt;"9&lt;/code&gt;&lt;/strong&gt; stores the other 8 latest &lt;em&gt;&lt;em&gt;big&lt;/em&gt;&lt;/em&gt; deletion or change. With each successive &lt;em&gt;big&lt;/em&gt; deletion or change command, Vim shifts the previous contents of &lt;code&gt;"1&lt;/code&gt; into &lt;code&gt;"2&lt;/code&gt;, &lt;code&gt;"2&lt;/code&gt; into &lt;code&gt;"3&lt;/code&gt;, and so forth, losing the previous contents of &lt;code&gt;"9&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Small delete register
&lt;/h3&gt;

&lt;p&gt;The register &lt;strong&gt;&lt;code&gt;-&lt;/code&gt;&lt;/strong&gt; is set by a delete command &lt;em&gt;&lt;strong&gt;only if the deleted text is smaller than one line (also called small delete)&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: The latest yanked texts will always appear in &lt;code&gt;"0&lt;/code&gt;. The latest small deletion will appear in &lt;code&gt;"-&lt;/code&gt; while the latest big deletion will appear in &lt;code&gt;"0&lt;/code&gt;. The other latest 8 big deletion will appear in &lt;code&gt;"2&lt;/code&gt;,  &lt;code&gt;"3&lt;/code&gt;,  &lt;code&gt;"4&lt;/code&gt;,  &lt;code&gt;"5&lt;/code&gt;,  &lt;code&gt;"6&lt;/code&gt;,  &lt;code&gt;"7&lt;/code&gt;,  &lt;code&gt;"8&lt;/code&gt;, &lt;code&gt;"9&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Named registers
&lt;/h3&gt;

&lt;p&gt;These are the registers &lt;strong&gt;&lt;code&gt;"a&lt;/code&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;code&gt;"z&lt;/code&gt;&lt;/strong&gt;, or &lt;strong&gt;&lt;code&gt;"A&lt;/code&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;code&gt;"Z&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These registers are only used when you say so.  Specify them as lowercase letters to replace their previous contents or as uppercase letters to append to their previous contents.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: I only use a couple of these as more permanent storage for code snippets such as &lt;code&gt;console.log(` `)&lt;/code&gt;, as compared to the temporary storage provided by the numbered registers and the small delete register.&lt;/p&gt;

&lt;p&gt;I find myself can survive with just the basic registers mentioned so far, and rarely use the ones below. But if you're interested, keep reading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5. Read-only registers
&lt;/h3&gt;

&lt;p&gt;These are &lt;strong&gt;&lt;code&gt;%&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;#&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;:&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;.&lt;/code&gt;&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;You can use them only with the &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;P&lt;/code&gt;, and &lt;code&gt;:put&lt;/code&gt; commands and with &lt;code&gt;CTRL-R&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;".&lt;/code&gt; contains the last inserted text (the same as what is inserted with the insert mode commands &lt;code&gt;CTRL-A&lt;/code&gt; and &lt;code&gt;CTRL-@&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: this doesn't work with &lt;code&gt;CTRL-R&lt;/code&gt; on the command-line. It works a bit differently, like inserting the text instead of putting it (&lt;code&gt;textwidth&lt;/code&gt; and other options affect what is inserted).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"%&lt;/code&gt; contains the name of the current file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"#&lt;/code&gt; contains the name of the alternate file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;":&lt;/code&gt; contains the most recent executed command-line.  Example: Use &lt;code&gt;@:&lt;/code&gt; to repeat the previous command-line command.&lt;/p&gt;

&lt;p&gt;The command-line is only stored in this register when at least one character of it was typed.  Thus it remains unchanged if the command was completely from a mapping.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Expression register
&lt;/h3&gt;

&lt;p&gt;The expression register is the &lt;strong&gt;&lt;code&gt;"=&lt;/code&gt;&lt;/strong&gt;. According to the documentation,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is not really a register that stores text, but is a way to use an expression in commands which use a register.  The expression register is read-only; you cannot put text into it.  After the &lt;code&gt;'='&lt;/code&gt;, the cursor moves to the command-line, where you can enter any expression. All normal command-line editing commands are available, including a special history for expressions.  When you end the command-line by typing &lt;code&gt;&amp;lt;CR\&amp;gt;&lt;/code&gt;, Vim computes the result of the expression.  If you end it with &lt;code&gt;&amp;lt;Esc\&amp;gt;&lt;/code&gt;, Vim abandons the expression.  If you do not enter an expression, Vim uses the previous expression (like with the "/" command).&lt;br&gt;
The expression must evaluate to a String.  A Number is always automatically converted to a String.  For the &lt;code&gt;p&lt;/code&gt; and &lt;code&gt;:put&lt;/code&gt; command, if the result is a Float it's converted into a String.  If the result is a List each element is turned into a String and used as a line. A Dictionary or FuncRef results in an error message.&lt;br&gt;
If the &lt;code&gt;"=&lt;/code&gt; register is used for the &lt;code&gt;p&lt;/code&gt; command, the String is split up at &lt;code&gt;&amp;lt;NL\&amp;gt;&lt;/code&gt; characters.  If the String ends in a &lt;code&gt;&amp;lt;NL\&amp;gt;&lt;/code&gt;, it is regarded as a linewise register.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Please see the &lt;a href="http://vimdoc.sourceforge.net/htmldoc/change.html#registers"&gt;documentation&lt;/a&gt; for details&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Selection and drop registers
&lt;/h3&gt;

&lt;p&gt;These are the &lt;strong&gt;&lt;code&gt;"*&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;"+&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;"~&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They're used for storing and retrieving the selected text for the GUI. As I've never used this, I won't write down all the details for it. If you're interested, you could read the &lt;a href="http://vimdoc.sourceforge.net/htmldoc/change.html#registers"&gt;documentation&lt;/a&gt; for details.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Black hole register
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;&lt;code&gt;"_&lt;/code&gt;&lt;/strong&gt; is the black hole register. As the name implies, nothing will happen if you write to it, and nothing will be returned from it if you try to read its content.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: You can direct your deleted text to the black hole register to avoid affecting other normal registers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  9. Last search pattern register
&lt;/h3&gt;

&lt;p&gt;This is the &lt;strong&gt;&lt;code&gt;"/&lt;/code&gt;&lt;/strong&gt; register, and it stores the most recent search pattern.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="http://vimdoc.sourceforge.net/htmldoc/change.html#registers"&gt;documentation&lt;/a&gt;,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is used for &lt;code&gt;n&lt;/code&gt; and &lt;code&gt;hlsearch&lt;/code&gt;. It is writable with &lt;code&gt;:let&lt;/code&gt;, you can change it to have &lt;code&gt;hlsearch&lt;/code&gt; highlight other matches without actually searching.  You can't yank or delete into this register.  The search direction is available in &lt;code&gt;v:searchforward&lt;/code&gt;. Note that the valued is restored when returning from a function &lt;code&gt;function-search-undo&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://vimdoc.sourceforge.net/htmldoc/change.html#registers"&gt;The nine types of registers in Vim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.brianstorti.com/vim-registers/"&gt;Vim registers: The basics and beyond&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vim</category>
      <category>registers</category>
      <category>cheatsheet</category>
    </item>
    <item>
      <title>Common Git commands and configurations used in a day-to-day workflow</title>
      <dc:creator>Zean Qin</dc:creator>
      <pubDate>Thu, 10 Sep 2020 06:31:57 +0000</pubDate>
      <link>https://forem.com/zeanqin/common-git-commands-and-configurations-used-in-a-day-to-day-workflow-8e2</link>
      <guid>https://forem.com/zeanqin/common-git-commands-and-configurations-used-in-a-day-to-day-workflow-8e2</guid>
      <description>&lt;p&gt;I'm a command line lover and use Git from the terminal all the time.&lt;/p&gt;

&lt;p&gt;If you're like me, you might start getting annoyed by some little things at a certain point. For example, it starts to get a bit annoying to always type two separate commands - &lt;code&gt;git add &amp;lt;file&amp;gt;&lt;/code&gt; then &lt;code&gt;git commit -m 'Your commit message'&lt;/code&gt; - to commit your changes. Or maybe you want to have a better looking git history when you type &lt;code&gt;git log&lt;/code&gt;. Or you want your local branch to be automatically pruned when the remote branch has been deleted. Little things like these - you get the idea.&lt;/p&gt;

&lt;p&gt;Over time, I have built up a curated list of commands, aliases and configrations that I use on a daily basis that makes my workflow more efficient and pleasant. And I'd like to share them with you below.&lt;/p&gt;

&lt;h3&gt;
  
  
  List your local branches and their remote tracking branches
&lt;/h3&gt;

&lt;p&gt;In addition, this command also shows the hash code and the commit message of the latest commit. It also tells you if the remote branch has been deleted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;-vv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, running the command produces the following output on my machine,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_brN_rc_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/txz2n6upl0q00dj5v54n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_brN_rc_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/txz2n6upl0q00dj5v54n.png" alt="git branch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Checkout a remote branch
&lt;/h3&gt;

&lt;p&gt;With Git versions ≥ 1.6.6 and with only one remote, you can just do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch
git checkout &amp;lt;branch_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;git checkout &amp;lt;branch_name&amp;gt;&lt;/code&gt; will &lt;strong&gt;NOT&lt;/strong&gt; work in modern Git if you have multiple remotes. In this case use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; &amp;lt;branch_name&amp;gt; &amp;lt;remote_name&amp;gt;/&amp;lt;branch_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or the shorthand&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;remote_name&amp;gt;/&amp;lt;branch_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add and commit in one command
&lt;/h3&gt;

&lt;p&gt;Add either one of the following aliases to your global Git config file (usually at &lt;code&gt;~/.gitconfig&lt;/code&gt; on a Linux/Mac OS system). I prefer the second one because it saves a few more keystrokes.&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;# add a `add-commit` alias, or&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.add-commit &lt;span class="s1"&gt;'!git add -A &amp;amp;&amp;amp; git commit'&lt;/span&gt;

&lt;span class="c"&gt;# add a `ac` alias to save a few more keystrokes&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.ac &lt;span class="s1"&gt;'!git add -A &amp;amp;&amp;amp; git commit'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And use it with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add-commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'My commit message'&lt;/span&gt; &lt;span class="c"&gt;# or&lt;/span&gt;
git ac &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'My commit message'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete a branch both locally and remotely
&lt;/h3&gt;

&lt;p&gt;When you're done with a branch, you can delete it from both the remote and your local machine using the commands below.&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;# delete a remote branch&lt;/span&gt;
git push &lt;span class="nt"&gt;-d&lt;/span&gt; &amp;lt;remote_name&amp;gt; &amp;lt;branch_name&amp;gt; &lt;span class="c"&gt;# or&lt;/span&gt;
git push &lt;span class="nt"&gt;-D&lt;/span&gt; &amp;lt;remote_name&amp;gt; &amp;lt;branch_name&amp;gt;

&lt;span class="c"&gt;# delete a local branch&lt;/span&gt;
git branch &lt;span class="nt"&gt;-d&lt;/span&gt; &amp;lt;branch_name&amp;gt; &lt;span class="c"&gt;# or&lt;/span&gt;
git branch &lt;span class="nt"&gt;-D&lt;/span&gt; &amp;lt;branch_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in most cases the &lt;em&gt;&lt;/em&gt; name is origin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The &lt;code&gt;-d&lt;/code&gt; option is an alias for &lt;code&gt;--delete&lt;/code&gt;, which only deletes the branch if it has already been fully merged in its upstream branch. You could also use &lt;code&gt;-D&lt;/code&gt;, which is an alias for &lt;code&gt;--delete --force&lt;/code&gt;, which deletes the branch "&lt;a href="https://git-scm.com/docs/git-branch#Documentation/git-branch.txt--D"&gt;irrespective of its merged status&lt;/a&gt;".&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete all branches that have been merged in remote
&lt;/h3&gt;

&lt;p&gt;Assume you have a long running &lt;code&gt;development&lt;/code&gt; branch, and you branch off it to create different feature branches e.g. &lt;code&gt;feature/A&lt;/code&gt;, &lt;code&gt;feature/B&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After your peers have reviewed your pull requests for both features, merged them back to &lt;code&gt;development&lt;/code&gt; and deleted them from remote. You can delete &lt;code&gt;feature/A&lt;/code&gt; and &lt;code&gt;feature/B&lt;/code&gt; from your local by running,&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;# switch to the development branch first&lt;/span&gt;
git checkout development

&lt;span class="c"&gt;# delete local branches whose remote tracking branches have been merged back to development&lt;/span&gt;
git delete-merged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You probably have noticed that &lt;code&gt;delete-merged&lt;/code&gt; is not a Git command - it's actually an alias we set up. The parameters used in the actual command is different depending on your setup. But you can follow the following steps to construct a command that suits your needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: Check out the &lt;code&gt;development&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: List all branches that have been merged into it in remote.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;--merged&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: You might see a few branches that you don't want to remove e.g. &lt;code&gt;master&lt;/code&gt;, &lt;code&gt;release&lt;/code&gt; etc. And you can filter down the list by excluding those branches by&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;--merged&lt;/span&gt; | egrep &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"(^&lt;/span&gt;&lt;span class="se"&gt;\*&lt;/span&gt;&lt;span class="s2"&gt;|master|development|skip_branch_name)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;The regular expression used by the &lt;code&gt;egrep&lt;/code&gt; command basically means "all branches whose name starts with &lt;code&gt;master&lt;/code&gt;, &lt;code&gt;development&lt;/code&gt; or &lt;code&gt;skip_branch_name&lt;/code&gt; will &lt;em&gt;not&lt;/em&gt; be deleted".&lt;/p&gt;

&lt;p&gt;You can modify the branches above or add your own branches that you don't want to delete.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: Delete all local branches that are already merged into the currently checked out branch&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;--merged&lt;/span&gt; | egrep &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"(^&lt;/span&gt;&lt;span class="se"&gt;\*&lt;/span&gt;&lt;span class="s2"&gt;|master|development|skip_branch_name)"&lt;/span&gt; | xargs git branch &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt;: Set a global alias &lt;code&gt;deleted-merged&lt;/code&gt; for the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.delete-merged &lt;span class="s1"&gt;'git branch --merged | egrep -v "(^\*|master|development|skip_branch_name)" | xargs git branch -d'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Discard unstaged files in current working directory
&lt;/h3&gt;

&lt;p&gt;To discard all unstaged files in current working directory,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a specific file, use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;--&lt;/span&gt; path/to/file/to/revert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--&lt;/code&gt; is to remove &lt;a href="https://git-scm.com/docs/git-checkout#_argument_disambiguation"&gt;argument ambiguation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rename a local and remote branch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: Rename your local branch&lt;/p&gt;

&lt;p&gt;If you are on the branch you want to rename:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;-m&lt;/span&gt; &amp;lt;new-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are on a different branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;-m&lt;/span&gt; &amp;lt;old-name&amp;gt; &amp;lt;new-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: Delete the &lt;code&gt;&amp;lt;old-name&amp;gt;&lt;/code&gt; remote branch and push the &lt;code&gt;&amp;lt;new-name&amp;gt;&lt;/code&gt; local branch&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin :&amp;lt;old-name&amp;gt; &amp;lt;new-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Reset the upstream branch for the &lt;code&gt;&amp;lt;new-name&amp;gt;&lt;/code&gt; local branch&lt;/p&gt;

&lt;p&gt;Switch to the branch and then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;new-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prettify the Git log
&lt;/h3&gt;

&lt;p&gt;You can format your Git log to look like below and set an alias for it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4sdjTvwo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mj1hjis3gcmfje3rv3w9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4sdjTvwo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mj1hjis3gcmfje3rv3w9.png" alt="git lg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: Set up the following alias in your global Git config file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.lg &lt;span class="s2"&gt;"log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)&amp;lt;%an&amp;gt;%Creset' --abbrev-commit --date=relative"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: Append additional flags if needed&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;# filter by start date&lt;/span&gt;
&lt;span class="nt"&gt;--after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2016-01-31"&lt;/span&gt;
&lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2016-01-31"&lt;/span&gt;

&lt;span class="c"&gt;# filter by end date&lt;/span&gt;
&lt;span class="nt"&gt;--before&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2017-03-10"&lt;/span&gt;
&lt;span class="nt"&gt;--until&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2017-03-10"&lt;/span&gt;

&lt;span class="c"&gt;# filter by author&lt;/span&gt;
&lt;span class="nt"&gt;--author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Zean Qin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My most commonly used command is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git lg &lt;span class="nt"&gt;--after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"yesterday"&lt;/span&gt; &lt;span class="nt"&gt;--author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Zean"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prettify Git diff
&lt;/h3&gt;

&lt;p&gt;I use &lt;a href="https://github.com/so-fancy/diff-so-fancy"&gt;diff-so-fancy&lt;/a&gt; to make the diffs more readable. Follow &lt;a href="https://github.com/so-fancy/diff-so-fancy#install"&gt;the official setup steps&lt;/a&gt; and then just run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prune remote-tracking branches automatically for a branch on the other side that has already been deleted
&lt;/h3&gt;

&lt;p&gt;Without &lt;code&gt;git fetch --prune&lt;/code&gt;, remote-tracking branches for a branch&lt;br&gt;
the other side already has removed will stay forever.&lt;/p&gt;

&lt;p&gt;To always &lt;code&gt;--prune&lt;/code&gt; for &lt;code&gt;git fetch&lt;/code&gt; and &lt;code&gt;git pull&lt;/code&gt; in all your Git repositories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; fetch.prune &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To always &lt;code&gt;--prune&lt;/code&gt; but from one single repository,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config remote.origin.prune &lt;span class="nb"&gt;true&lt;/span&gt;
                 &lt;span class="c"&gt;#^^^^^^&lt;/span&gt;
                 &lt;span class="c"&gt;#replace with your repo name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Make Git to use Vim as editor for writing commit messages
&lt;/h3&gt;

&lt;p&gt;If you want to set the editor only for Git, do either (you don’t need both):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;core.editor&lt;/code&gt; in your Git config: &lt;code&gt;git config --global core.editor "vim"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;GIT_EDITOR&lt;/code&gt; environment variable: &lt;code&gt;export GIT_EDITOR=vim&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to set the editor for Git and also other programs, set the standardized &lt;code&gt;VISUAL&lt;/code&gt; and &lt;code&gt;EDITOR&lt;/code&gt; environment variables:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
Setting both is not necessarily needed, but some programs may not use the more-correct &lt;code&gt;VISUAL&lt;/code&gt;.&lt;br&gt;
&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VISUAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vim
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;EDITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VISUAL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/2003505/how-do-i-delete-a-git-branch-locally-and-remotely"&gt;How do I delete a Git branch locally and remotely?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/1783405/how-do-i-check-out-a-remote-git-branch"&gt;How do I check out a remote Git branch?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/4298960/git-add-and-commit-in-one-command"&gt;Git add and commit in one command&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/2596805/how-do-i-make-git-use-the-editor-of-my-choice-for-commits"&gt;How do I make Git use the editor of my choice for commits?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/52704/how-do-i-discard-unstaged-changes-in-git"&gt;How do I discard unstaged changes in Git?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://multiplestates.wordpress.com/2015/02/05/rename-a-local-and-remote-branch-in-git/"&gt;Rename a local and remote branch in git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/so-fancy/diff-so-fancy"&gt;Good-lookin' diffs. Actually… nah… The best-lookin' diffs.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/18308535/automatic-prune-with-git-fetch-or-pull"&gt;Automatic prune with Git fetch or pull&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/git/git/commit/737c5a9cde708d6995c765b7c2e95033edd0a896#diff-07f3b3cf16a56e95990c64bdef634199R940"&gt;fetch: make --prune configurable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/6127328/how-can-i-delete-all-git-branches-which-have-been-merged"&gt;How can I delete all Git branches which have been merged?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>git</category>
      <category>productivity</category>
      <category>practices</category>
    </item>
  </channel>
</rss>
