Skip to content

Instantly share code, notes, and snippets.

@nicolasdao
Last active January 24, 2024 02:52
Show Gist options
  • Save nicolasdao/7636d5ae21bd2a68ae485993a6d779ad to your computer and use it in GitHub Desktop.
Save nicolasdao/7636d5ae21bd2a68ae485993a6d779ad to your computer and use it in GitHub Desktop.
CSS How to. Keywords: css scss animation css-in-js cssinjs emotion styledcomponents styled-components lin linaria font google googlefont fonts

CSS GUIDE

Usefull links

WARNING: Safari is the new IE. If there is a cool feature in CSS, keep your excitement down as Safari probably don't support it yet. Unfortunatelly, this useless piece of shit browser is used a lot thanks to our little friend the iPhone. Safari is a big piece of junk, so please refer to the The million things Safari does not want you to do in CSS section, so that you can find work arounds.

Table of contents

CSS

CSS syntax

@import

Allows to import other CSS files.

@import "navigation.css";

IMPORTANT: It must be located at the top of the file

Inline CSS in HTML body

Nowadays, HTML supports adding inline CSS inside DOMs:

<div>
	<style type="text/css">
			
	</style>
</div>

CSS selectors

:not

/* Apply style on class 'MuiSwitch-thumb' only when one of its parent span does not contain the 'Mui-checked' class  */
span:not(.Mui-checked) > .MuiSwitch-thumb {
	border: 1px solid #d7d7d7;
}

Fonts

Web safe fonts

Web safe fonts are the fonts that all browsers support out-of-the-box. They also work as fallback with custom fonts when they fail to load properly (more about this below).

  • "American Typewriter", serif
  • "Andalé Mono", monospace
  • "Arial", sans-serif
  • "Arial Black", sans-serif
  • "Bodoni Poster", serif
  • "Bradley Hand", cursive
  • "Brush Script", cursive
  • "Calibri", sans-serif
  • "Candara", sans-serif
  • "Century Gothic", sans-serif
  • "Comic Sans MS", cursive
  • "Copperplate", fantasy
  • "Courier New", monospace
  • "Courier", monospace
  • "DejaVu Sans", sans-serif
  • "EB Garamond", serif
  • "Georgia", serif
  • "Helvetica", sans-serif
  • "Impact", sans-serif
  • "Linotype Didot", serif
  • "Lucida Console", monospace
  • "Lucida Sans Unicode", sans-serif
  • "Luminari", fantasy
  • "Monaco", monospace
  • "Palatino", serif
  • "Papyrus", fantasy
  • "Playbill", fantasy
  • "Roboto", sans-serif
  • "Rockwell", serif
  • "Segoe Ui", sans-serif
  • "Tahoma", sans-serif
  • "Times New Roman", serif
  • "Trattatello", fantasy
  • "Trebuchet MS", sans-serif
  • "Verdana", sans-serif

They are grouped in 5 generic fonts:

  1. serif
  2. sans-serif
  3. monospace
  4. fantasy
  5. cursive

The way CSS fonts are defined is as follow:

.some-class {
	font-family: "Montserrat", "Arial", sans-serif;
}

The font-family needs at least two fonts: the family name (e.g., Montserrat or Arial) and the generic family name (e.g., sans-serif). In the example above, the first two are the font that the browser must try to access, starting with the first one. That's why using the a web safe font as a last resort will always work.

Tip: Use a web safe font from the same generic family than the custom font. This helps keeping your brand somewhat consistent, even in case of custom font failure.

System fonts

System fonts explanation

Original article: System font deep dive

Sometimes referred as to System Font Stack, the system fonts are the default font used by the device's OS. The advantages of using those fonts are:

  • No loading time, which means best performance and no SEO negative side-effects (FOUT, CLS).
  • Website looking like native apps.
  • Font that follows the latest trend.

The obvious drawback of system fonts is that your website will look different on every operating system. The font-size, letter-spacing, width and weight you carefully select on one platform may not look ideal when another font is used on a different platform. This is why the system font is usually a good choice for body text and maybe less of a good choice for headers.

You can define the system font in your CSS as follow:

font-family: system-ui;

NOTE: The legacy version of this code above is:

font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";

System fonts in each OS

OS system-ui font
macOS, iOS, iPadOS SF Pro
Windows Segoe UI
Android Roboto
Ubuntu Ubuntu

Safari on Apple devices special case

Safari on Apple devices supports extra system ui on top of the universal system-ui:

  • ui-monospace
  • ui-rounded
  • ui-sans-serif
  • ui-serif: New Yorker.

Loading fonts - @font-face vs link

There are 1.5 ways to load a font in your website:

  1. @font-face
  2. HTML link tag, which in turns loads a CSS file that defines @font-face, which is why I mention "1.5 ways to load a font".

The usual way to include font file is to include in your CSS a font face as follow:

@font-face {
	font-family: 'Montserrat';
	font-style: italic;
	font-weight: 200;
	font-display: optional;
	src: url(https://fonts.gstatic.com/s/montserrat/v15/JTUPjIg1_i6t8kCHKm459WxZBg_z-PZwjimrq1Q_.woff2) format('woff2');
	unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

With a link, use that tag under the head tag as follow:

<!-- https://fonts.gstatic.com is the font file origin -->
<!-- It may not have the same origin as the CSS file (https://fonts.googleapis.com) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

<!-- Asynchronously load that resource -->
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap"/>

<!-- Asynchronously load that resource CSS -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap" media="print" onload="this.media='all'" />

<!-- Fallback if JS is disabled -->
<noscript>
	<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap" />
</noscript>

For more details about font loading performance, please refer to the Fonts & performances section.

Google fonts

Overview - Google Fonts

Google fonts (https://fonts.google.com/) exposes custom fonts via CSS rather than font files (e.g., woff2). This means you do not use @font-face directly (though this is what kind of happens at the end (1)). Instead, the font link is included in your page's head:

<head>
	<link rel="preconnect" href="https://fonts.gstatic.com">
	<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=optional" rel="stylesheet">
</head>

IMPORTANT: Notice the display=optional in the URL. Use it instead of the default display=swap. Otherwise, you will experience the FOIT (Flash Of Invisible Text) or FOUT (Flash Of Unstyled Text) issue.

(1) If you load that https://fonts.googleapis.com/css2?... link directly in your browser, you'll see that it returns a CSS file that uses @font-face to load .woff2 files. This CSS file may vary based on your system. This design allows Google to serve CSS files optimized for the device requesting it.

Configuration - Google Fonts

Go to https://fonts.google.com/ to create the URL optimal to your needs. That URL is structured as follow:

https://fonts.googleapis.com/css2?<FAMILY CONFIG 01>&<FAMILY CONFIG 02>

Where you can add as many <FAMILY CONFIG XX> as you want in the query parameters. <FAMILY CONFIG XX> is configured as follow:

family=<FAMILY NAME>:<CONFIG NAME 01>,<CONFIG NAME 02>@<CONFIG VALUE 01/01>,<CONFIG VALUE 02/01>;<CONFIG VALUE 01/01>,<CONFIG VALUE 02/02>

As you can see, there can be many config names and their values can create many combinations. For example, let's imagine we need this font: Raleway (family) with regular and font-weigth 300, italic and font-weigth 200 and italic and font-weigth 400. For that set of fonts, the query parameters will be:

family=Raleway:ital,wght@0,300;1,200;1,400

For multiple font families, the entire URL could be:

https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,300;1,200;1,400&family=Roboto:wght@100;400&display=swap

Icons - Google Fonts

Google fonts also supports icons. Go to https://fonts.google.com/icons to browse them. Those icons are fonts that are available in both variable and static fonts.

The variable fonts can be installed and used as follow:

<!-- Add this in your page's <head> -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200" />
<style>
.material-symbols-outlined {
	font-variation-settings:
		'FILL' 0,
		'wght' 400,
		'GRAD' 0,
		'opsz' 24
}
</style>

Where:

  • FILL is a binary to indicate whether the icon is filled or not.
  • wght is the font weight.
  • GRAD is the font's grade. Grade is an additional layer to the font-weight to control finer details.
  • opsz is the optical size. This setting helps to automatically adjust the font-weight as the icon changes size.

To use the settings icon, simply use this:

<span class="material-symbols-outlined">settings</span>

However, as discussed in the next section, variable fonts incur a heavy toll on performance. You might prefer to download a narrowed down version of the icon font. In the example above, the settings icon uses the config 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24. If that's all we need, then the font file can be updated as follow:

<!-- Add this in your page's <head> -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@40,200,0,200" /> 

The CSS config can be omitted since we only have access to a unique font-variation-settings. This setup reduces the file's size from 2.9MB to 238KB.

Variable fonts vs Static fonts

The latest version of Google fonts support Variable fonts. On paper, one might think this should be the one to use, but in practice the static version is still the correct choice for one simple reason: Variable font files are too big! Indeed, a typical variable font file is around 3MB, while a specific font file might be around 200KB.

This doesn't mean variable fonts should not be used. The scenarios where they are usefull are:

  • Experimentation and design phase.
  • Many different variation of the same font results in a too many requests to the server or represent a total file size greated than the variable font's size.

Fonts performance

Please refer to the Fonts & performances section under Performances.

Finding a native font that matches your custom font

https://meowni.ca/font-style-matcher/

Variable fonts

Variable fonts are modern font that can be fully customized via the font-variation-settings CSS property. This removes the need to download precompile fonts (one for thin, another for bold, ...). For example, the new Google Font Roboto Flex is a variable font.

Specificity or how to stop using !important

Specificity is the means by which browsers decide which CSS property values are the most relevant to an element and, therefore, will be applied. It is important to understant how it works, otherwise, you'll be tempted to overuse the !important rule to force a CSS rule to overide another. Thought this can work, it has the few following negative side-effects:

  • !important is not supported in AMP websites.
  • Many !important makes CSS messy and hard to read.

In a nutshell, remember that the most specific CSS selector wins. The priority rules by order of least importance are:

  • Type selectors (e.g., h1) and pseudo-elements (e.g., ::before).
  • Class selectors (e.g., .example), attributes selectors (e.g., [type="radio"]) and pseudo-classes (e.g., :hover).
  • ID selectors (e.g., #example).

For example, in the following HTML:

<div class="my-title">
	<span id="my-text">Hello</span>
	<span class="my-subtitle">World</span>
</div>
.my-title {
	color: red;
}

.my-subtitle {
	color: blue;
}

Hello is red and World is blue. But if we add the following CSS rule:

.my-title .my-subtitle {
	color: green;
}

Then World becomes green because .my-title .my-subtitle is more specific than .my-subtitle.

Then, if we add:

#my-text {
	color: pink;
}

Hello becomes pink because ID selectors are more specific than class selectors.

z-index and stacking order

Original article: https://www.freecodecamp.org/news/4-reasons-your-z-index-isnt-working-and-how-to-fix-it-coder-coder-6bc05f103e6c/

Correctly positioning elements on top of each other using CSS can be tricky and requires the understanding of what is referred to as stacking order.

A stack is a context in which the z-index property has an effect.

1st rule - Elements in the same stacking context will display in order of appearance

By default, elements and stacks that are lower in the DOM are higher. In the example below, by default, the 3rd div is higher than the 2nd which is higher than the 1st.

<div id="one"></div>
<div id="two"></div>
<div id="three"></div>

The z-index can overide this behavior. The following example shows how to position the 1st element on top of all the others:

<div id="one" style="z-index:1;"></div>
<div id="two"></div>
<div id="three"></div>

2nd rule - Positioned elements are always higher than unpositioned elements

<div id="one"></div>
<div id="two" style="position:relative;"></div>
<div id="three"></div>

The 2nd element is now higher than both the 1st and 3rd element. The z-index can overide this behavior. The following example shows how to position the 3rd element back on top of all the others:

<div id="one"></div>
<div id="two" style="position:relative;"></div>
<div id="three" style="z-index:1;"></div>

3rd rule - transform puts the element in a new stacking context

The transform property (as well as the opacity in certain situations) put the element into a new stack. In the example below, the 3rd element is added in a new stack, and because that stack is lower in the DOM tree than the previous two elements, it will appear on top of them, even though it has no position set while the 2nd element has:

<div id="one"></div>
<div id="two" style="position:relative;"></div>
<div id="three" style="transform:scale(1);"></div>

The z-index can overide this behavior. The following example shows how to position the 2nd element back on top of all the others:

<div id="one"></div>
<div id="two" style="position:relative;z-index:1;"></div>
<div id="three" style="transform:scale(1);"></div>

4th rule - The stacking order of a child is bounded to it's parent's stacking order

The example below, you may think that the child element is above the 3rd element because its z-index is greater. However, that's not the case, because its parent's z-index is in a lower stack that the 3rd element.

<div id="one"></div>
<div id="two" style="position:relative;z-index:1;">
	<div id="child" style="z-index:100;"></div>
</div>
<div id="three" style="z-index:1;"></div>

To fix this issue, you must increase the stack order of the 2nd element as follow:

<div id="one"></div>
<div id="two" style="position:relative;z-index:2;">
	<div id="child" style="z-index:100;"></div>
</div>
<div id="three" style="z-index:1;"></div>

Scrolling

https://24ways.org/2019/beautiful-scrolling-experiences-without-libraries/

Hiding the scroll bar while keeping the functionality

/* Hide scrollbar for Chrome, Safari and Opera */
.example::-webkit-scrollbar {
	display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.example {
	-ms-overflow-style: none;  /* IE and Edge */
	scrollbar-width: none;  /* Firefox */
}

Dealing with the scroll bouncing issue

Original article: https://www.smashingmagazine.com/2018/08/scroll-bouncing-websites/

This issue occurs mainly on mobile devices when the user scrolls down when the page is already at the top or when up when the page is already at the bottom. The default page behavior is to extend the scroll outside of the mobile screen as if it was streching. This effect is called scroll bouncing. There are many way to address this problem, but the simplest is to use CSS as follow:

body {
	width: 100%;
	position: fixed;
	overscroll-behavior: none;
}

For more info about overscroll-behavior, please refer to https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior

Animation

Basic animation examples

/* The animation code */
@keyframes example {
	from {background-color: red;}
	to {background-color: yellow;}
}

/* The element to apply the animation to */
div {
	width: 100px;
	height: 100px;
	background-color: red;
	animation-name: example;
	animation-duration: 4s;
	animation-fill-mode: forwards; /* Use to prevent the animation to come back to start when it is done. */
	/* animation-delay: 400ms;  */
}

Spinning wheel

@keyframes refresh-icon-spin {
	from {
		transform:rotate(0deg);
	}
	to {
		transform:rotate(360deg);
	}
}

& .refresh-spinner {
	animation-name: refresh-icon-spin;
	animation-duration: 1000ms;
	animation-iteration-count: infinite;
	animation-timing-function: linear;
}

Complex keyframes

Elastic keyframes

CSS Elastic ease generator

Text

Truncate

Truncate a single line:

.truncate {
	width: 160px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

Allowing multiple lines and truncating the last one if the text overflows involves:

  • Add ellipsis to the last line.
  • Hiding the rest of the text behind an overflow.
span {
	overflow: hidden;
	text-overflow: ellipsis;
	display: -webkit-box;
	-webkit-line-clamp: 2; /* number of lines to show */
	-webkit-box-orient: vertical;
}

Adjust container's height to fit long string

overflow-wrap: anywhere;

Disable text selection

.prevent-select {
	-webkit-user-select: none; /* Safari */
	-ms-user-select: none; /* IE 10 and IE 11 */
	user-select: none; /* Standard syntax */
}

Images

Fitting images

Use this CSS:

	float: left;
	width: 510px;
	height: 220px;
	object-fit: cover;

For example:

<div class="image">
	<a href="/what-we-do/project-services"><img style="float: left;width: 510px;height: 220px;object-fit: cover;" src="images/unsplash/austin-distel-wD1LRb9OeEo-unsplash.jpg" alt=""></a>
</div>

You can also try to use this:

{
	background: url(mountain.jpg);
	background-size: cover;
}

Adding a gradient mask on images

img {
	background: 
		linear-gradient( rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.6) ),
		url(/images/austin-distel-mpN7xjKQ_Ns-unsplash.jpg);
}

Shadow

Box shadow

.shadow {
	box-shadow: 0 4px 8px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
}

WARNING: For full browser support use all those properties: box-shadow, -moz-box-shadow, -webkit-box-shadow, -ms-box-shadow, -o-box-shadow.

Text shadow

Neumorphism

Original post: https://codepen.io/mirandalwashburn/pen/bGdvGwR

For this to work, make sure that the box's surroundings are not white. Use some sort of light grey (e.g., #f0f0f3):

border-radius: 8px;
box-shadow: inset -6px -6px 6px 0 rgb(255 255 255 / 70%), inset 6px 6px 6px 0 rgb(174 174 192 / 20%);

Layout

Flexbox

CSS Flexbox is the best way to place items inside a container. If you're just after aligning items, then this should do. If you wish to place items following strict rows/columns rules, then you should use CSS Grid instead.

A Complete Guide to Flexbox

Making a cell fill the entire space available

.somecell {
	flex-grow: 1;
}

Grid

CSS grid is the best way to organize content following rows/columns rules.

Basic Grid principles

1. Create a grid container
.wrapper {
	display: grid;
	grid-template-rows: 60px 1fr 2fr;
	grid-template-columns: 1fr 1fr 200px;
}

The above creates a gris with 3 rows (row 1: 60px, row 2: 1 unit of available space, row 3: 1 units of available space) and 3 columns (column 1: 1 unit of available space, column 2: 1 unit of available space and column 3: 200px). This means the row 2 and 3 as well as column 1 and 2 will auto-adjsut to fill all available space.

<div class="wrapper">
	<div>Row 1 - Column 1</div>
	<div>Row 1 - Column 2</div>
	<div>Row 1 - Column 3</div>
	<div>Row 2 - Column 1</div>
	<div>Row 2 - Column 2</div>
	<div>Row 2 - Column 3</div>
	<div>Row 3 - Column 1</div>
	<div>Row 3 - Column 2</div>
	<div>Row 3 - Column 3</div>
</div>

Notice that the content fills the grid top to right. To learn how to overide this placement, please jump to the next section.

TIP: If your to repeat the same dimensions, use the repeat CSS function: grid-template-rows: repeat(3, 1fr);

2. Place content in specific cells

The previous example places content in each cell. However, one of the neat feature of CSS grid is the ability to put content in specific cells as well as put content that overlap a group of cells.

.box1 {
	grid-column-start: 1;
	grid-column-end: 3;
}

This class places the content in the first 2 columns:

<div class="wrapper">
	<div>Row 1 - Column 1</div>
	<div>Row 1 - Column 2</div>
	<div>Row 1 - Column 3</div>
	<div>Row 2 - Column 1</div>
	<div>Row 2 - Column 2</div>
	<div>Row 2 - Column 3</div>
	<div class="box1">Row 3 - Column 1 & 2</div>
	<div>Row 3 - Column 3</div>
</div>

CSS properties grid-row-start and grid-row-end follow the same reasoning.

Another way to control the content placement is via grid-area.

3. Simple responsive grid
.wrapper {
	display: grid;
	grid-gap: 15px;
	grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}

Grid area

--margin: 30px;

display: grid;
height: 100vh;
width: 100vw;
grid-template-areas: 
	'nav nav nav'
	'work work config'
	'work work config';
grid-template-rows: 60px 1fr 1fr;
grid-template-columns: 1fr 1fr 200px;

& .nav-bar {
	grid-area: nav;
	background-color: #74BDCB;
	display: flex;
	align-items: center;
	padding: 0 var(--margin) 0 var(--margin);
}

& .workspace {
	grid-area: work;
	background-color: #FFA384;
}

& .config-pane {
	grid-area: config;
	background-color: #EFE7BC;
}

Space between cells

grid-gap: 15px;

This is a shortcut for:

grid-row-gap: 15px;
grid-column-gap: 15px;

Media queries

Easiest small screen media query

@media (max-width: 768px) {
	/* CSS for your small screen here  */
}

Masking

This feature allows you to use a background image(1) as a mask on your background element:

.mask {
	--mask: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/alarm/default/24px.svg);
	--size: 32px;

	/* This trick */
	-webkit-mask-image: var(--mask, unset);
	mask-image: var(--mask, unset);
	-webkit-mask-size: var(--size);
	mask-size: var(--size);
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;

	/* Coloring your result  */
	background-color: red; /* You could also use an image or a gradient */
}

(1) Usually an svg or png image. The transparent pixels are equivalent to 0s, which means, they will hide the background.

CSS-in-JS

Adding CSS with CSS Rules

// Show the CSS:
console.log(document.styleSheets[2].cssRules[i].cssText)

// Create a new CSS rule
document.styleSheets[5].insertRule('.helloDude {color:red;}')

WARNING: Trying to access a stylsheet from another domain will throw an exception. More about dealing with this issue under the Uncaught DOMException: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules section.

Emotion

Emotion - Basic

import { css } from '@emotion/css'

const yourClass = css`
	padding: 10px;
	background: red;
	
	&.special {
		& .hello,
		& .world {
			font-size: 13px;
		}
		
		& .hello {
			font-weight: bold;
		}
	}
	
	supercool & {
		background: cyan;
	}
`

render(
<div className={`${yourClass} special`}>
	<div class="hello">Hello</div>
	<div class="world">World</div>
</div>
)

Notice:

  • &.special: Because there is no space between & and .special, this means this style only applies if the special class is defined on the same DOM as the yourClass class.
  • & .hello: The space between & and .hello means that any DOM with the hello class nested anywhere under .special will be styled accordingly to the hello definition.
  • & .hello,& .world: Emotion allows to group styles.
  • supercool &: Allows to define style outside of the scope of the component. In this case, this means that if any ancestor defines a supercool class, this style will apply.

Emotion - Media queries

Let's changing the properties on small screen:

const yourClass = css`
	padding: 10px;
	background: red;
	
	&.special {
		& .hello,
		& .world {
			font-size: 13px;
		}
		
		& .hello {
			font-weight: bold;
		}
	}
	
	supercool & {
		background: cyan;
	}

	@media (max-width: 768px) {
		padding: 5px;
	}
`

Emotion - Global styles

https://emotion.sh/docs/@emotion/css#global-styles

Linaria

Linaria - Intro

Linaria is similar to Emotion except it compiles the styles into CSS files. Advantages:

  • Better app performances:
    • No extra parsing needed for CSS on the client (as opposed to Emotion).
    • CSS is downloaded and parsed separately from JS, which means parallel files download.
  • No style duplication on SSR
  • Catch errors early due to build-time evaluation
  • Familiar CSS syntax
  • Works without JavaScript

Linaria - Global style

import { css } from '@linaria/core'

export const myGlobals = css`
	:global() {
		html {
			box-sizing: border-box;
		}

		*,
		*:before,
		*:after {
			box-sizing: inherit;
		}

		body {
			margin: 0;
			background: red;
		}
	}
`

This myGlobals does not to be added on any DOM. The simple fact this code is executed updates the CSS globally.

Browsers compatibility

CSS properties variations

  • animation: -moz-animation, -webkit-animation
  • background-clip: -moz-background-clip, -webkit-background-clip
  • box-shadow: -moz-box-shadow, -webkit-box-shadow, -ms-box-shadow, -o-box-shadow
  • box-sizing: -moz-box-sizing, -webkit-box-sizing
  • text-fill-color: -moz-text-fill-color, -webkit-text-fill-color
  • transform: -webkit-transform, -moz-transform
  • transition: -webkit-transition, -moz-transition, -o-transition

Font anti-aliased

This is supported differently based on the browser. Use the following to cover all scenarios:

box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

Android quirks

Touch events create a weird blue highlight

Add this CSS on the affected DOM:

-webkit-tap-highlight-color: transparent;

Safari quirks

Please refer to the The million things Safari does not want you to do in CSS section.

Mobile challenges

:hover on mobile

Though the hover effect does not apply on mobile, it can be triggered by other touch interactions depending on the browser. This leads to unwanted behaviors. To avoid those unpredicted effects, the :hover effect should only be defined when it is supported. CSS media queries helps achieving this setup:

@media (hover: hover) {
	button {
		background: white;
	}
	button:hover {
		background: yellow;
	}
}

To test if the current device is mobile, please refer to the JS code in the annexes.

Performances

Literatures on performances

Non-blocking CSS

https://web.dev/defer-non-critical-css/

Animation performances

What affect performances

Best article that summarizes it: Investigate Animation Performance with DevTools Other great articles:

In a nutshell:

  • A web page rendering process follows this sequence:
    1. HTML parsing.
    2. Style calculation.
    3. Layout creation (creating the boxes that contain your content and sizing).
    4. Painting (creating the pixels for each layout).
    5. Compositing (Putting all painted layouts together to create your page).
  • Animations that suck require reprocessing steps 3 to 5.
  • Animations that rock only require reprocessing step 5.

The CSS properties that only need step 5 reprocessing are:

  • transform
  • opacity

To make sure animations runs at the optimal 60FPS, please replace the following CSS properties as follow:

  • height and width with transform: scale
  • top, left, bottom, right with transform: translate

Performances best practices

If you're interested in knowing how to best manipulate CSS in Javascript, please refer to this document: https://gist.github.com/nicolasdao/312da25b71bf08c36083694c045d8d5b#waapi--worklets

  • Stick to CSS properties transform and opacity when designing animated interactions.
  • DO NOT USE CSS VARIABLES IN transform OR opacity TO ANIMATE THEM. This is a typical mistake. Updating CSS Variables triggers a reflow because they are inheritable. When you wish to animate a DOM, update the transform OR opacity property explicitly by targetting the exact DOM.
  • Replace modifying CSS style properties individually because each property triggers a reflow for each dynamic style change. Use a single class with all the CSS properties instead. Alternatively, use cssText
// WARNING: Check that 'cssText' is supported in your browser
element.style.cssText += "left: " + left + "px; top: " + top + "px;"; // triggers reflow once
  • Apply animations with position fixed or absolute so it doesn’t affect the layout of other elements.
  • Cache any of the following CSS properties as each invocation requires a reflow:
    • clientHeight
    • clientWidth
    • offsetHeight
    • offsetWidth
    • getBoundingClientRect()

WARNING: Requesting those properties in a loop can have terrible negative implications on performances.

  • When possible, hide or show the element using visibility: hidden and visibility: visible instead of display: none and display: block. This will prevent reflows.
  • Use textContent instead of innerText. innerText creates reflow each time it is being requested.
  • Use requestAnimationFrame for expensive ops:
requestAnimationFrame(() => {
	/* should be able to get offsetHeight here */
	console.log(divContainer.offsetHeight); 
}
  • Use the will-change CSS property to force the browser to put the specific DOM with that property in its own layout:
.my-animation {
	transition: transform 200ms linear;
	will-change: transform;
}

WARNING: Do not overuse this as creating too many layout may stress the browser excessively and eventually degrade performances rather than improving it.

Fonts & performances

The ultimate truth about fonts and performances

The most important rule is: DON'T USE CUSTOM FONTS!. Instead, use a Web Safe Font or a System Font.

If you still need a custom font, you must be aware that:

  • YOU CANNOT AVOID FOUT (Flash Of Unstyled Text) with custom fonts. Browse to one of your favorite website from an established company that uses a custom fonts and pay attention to the few milliseconds when the page loads. You'll notice FOUT.
  • At best, you should aim to avoid the Content Layout Shift (CLS).

This does not mean you cannot create the illusion of NO FOUT with a custom font. To create that illusion:

  1. Optimize the font loading to speed up its availability.
  2. Use visual effects to delay when the text is rendered (making sure those effects don't create CLS).

To avoid CLS, please refer to the Avoiding Content Layout Shift due to font swapping section.

The optimization you should use with custom fonts

Original articles:

  1. By far the best performance tip: DON'T USE CUSTOM FONTS AT ALL - USE WEB SAFE FONTS INSTEAD. Web Safe Fonts are preloaded by all browsers. Most of them are listed in the Web safe fonts sections. If you still need a custom fonts, then minimize their use to parts that require strong branding (e.g.,headers) and use a web safe font for the body.
  2. Use preconnect (https://web.dev/font-best-practices/#preconnect-to-critical-third-party-origins)
<head>
	<link rel="preconnect" href="https://fonts.googleapis.com">
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
</head>
  1. Use preload to asynchronously load the font before it is needed:
<!-- https://fonts.gstatic.com is the font file origin -->
<!-- It may not have the same origin as the CSS file (https://fonts.googleapis.com) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

<!-- Asynchronously load that resource -->
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap"/>

<!-- Asynchronously load that resource CSS -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap" media="print" onload="this.media='all'" />

<!-- Fallback if JS is disabled -->
<noscript>
	<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap" />
</noscript>

The onload="this.media='all'" is a hack to prevent the browser to block rendering. More about the hack in the How to load CSS stylesheets asynchronously? section.

  1. Use fonts that are hosted on a CDN. If you don't self-host on a CDN, self-hosting your font will be worse than using a third-party that uses a CDN.
  2. Use compressed fonts. Use WOFF2. Because it uses Brotli, WOFF2 compresses 30% better than WOFF, leading to less data to download and therefore faster performance.
  3. Subset your font, i.e., don't load all characters of a font if you don't use it. Google font easily let you choose. This will decrease your font size.
  4. Match your fallback font with your custom font. Use a tool such as Font style matcher to help you.
  5. Cache your custom font on your CDN using the Cache-Control header.
  6. Use the CSS property font-display to adjust your strategy to your needs (most common values are optional and swap):
    • If performance is a top priority: Use font-display: optional. This is the most "performant" approach: text render is delayed for no longer than 100ms and there is assurance that there will be no font-swap related layout shifts. However, the downside here is the web font will not be used if it arrives late.
    • If displaying text quickly is a top priority, but you want to still ensure the web-font is used: Use font-display: swap but make sure to deliver the font early enough that it does not cause a layout shift. The downside of this option is the jarring shift when the font arrives late.
    • If ensuring text is displayed in a web font is a top priority: Use font-display: block but make sure to deliver the font early enough that it minimises the delay of the text. The downside of this is the initial text display will be delayed. Note despite this deplay, it can still cause a layout shift as the text is actually drawn invisible, and the fallback font space is therefore user to reserver the space. Once the web font loads, this may require difference space and hence a shift. This may, however, be a less jarring shift than font-display: swap as the text itself will not be seen to shift.

    Also keep in mind that these two approaches can be combined: for example, use font-display: swap for branding and other visually distinctive page elements; use font-display: optional for fonts used in body text.

  7. Don't use fonts for icons, use SVG instead. Icon fonts are more likely to cause significant layout shifts. Material design fonts and Font awesome now support SVG.
  8. Use the CSS property size-adjust to manually minimize the layout shit during web font swapping. Full details at https://web.dev/css-size-adjust/.

Web Safe Fonts and System Fonts

A semi-exhaustive list of the latest (August 2023) Web Safe Fonts is defined under the Web safe fonts section. As for system fons

Visual hacks to create the illusion of NO FOUT

Avoiding Content Layout Shift due to font swapping

Cache

Caching your CSS and font files can significantly improve your page loading time. This is not something you can do on the client side (besides using a service worker to work offline). Instead, a caching policy must be configure on your file hosting server which must set the Cache-Control response header. To learn more about this topic, please refer to the Understanding the native Cache-Control HTTP header section of the AWS CLOUDFRONT GUIDE document.

CSS variables

Main article: Performance of CSS Variables

The million things Safari does not want you to do in CSS

Border radius broken when the container uses overflow: hidden

To fix this, use the following CSS:

overflow: hidden;
-webkit-mask-image: -webkit-radial-gradient(white, black);

Gradient effect on text does not work

You must add display: block as follow:

display: block; /* For safari */
background-image: linear-gradient(0deg, red, blue);
background-clip: text;
-moz-background-clip: text;
-webkit-background-clip: text;
text-fill-color: transparent;
-moz-text-fill-color: transparent;
-webkit-text-fill-color: transparent;

height:100% does not work when the parent does not explicitely define its height

Try adding a display:flex; on the parent and then replace height:100%; on the child with height:auto;.

Mobile Safari freezes when scrolling at the top or bottom or while touching the screen as it scrolls

Known bug that the freaking Safari team never bother to fixed. This is documented here. To fix this stupid behavior, try adding the following CSS on the body:

body {
	overflow: hidden;
}

Flex gap not supported

Supported on Desktop but not on Mobile.

Forget about this simple code:

.simple {
	display: flex;
	gap: 15px;
}

SCSS

SCSS syntax

Example

.parent {
	display:none;
}

.my-class {
	color: red;
	
	&:hover {
		color: green;
	}
	
	.bg {
		background-color: black;
	}
	
	.parent & {
		display:flex;
	}
}

When transpiled by the SASS loader, this SASS code becomes this CSS:

.my-class {
	color: red;
}

.my-class:hover {
	color: green;
}

.my-class .bg {
	background-color: black;
}

.parent {
	display:none;
}

.parent .my-class {
	display:flex;
}

Variables, maps & lists

Creating variables

$my-constant: 12;

$my-maps: ("small":10px, "regular":30px, "large":60px);
$small: map.get($my-maps, "small"); // 10px

$my-list: 10px, 30px, 60px;
$second-val: list.nth($my-list, 2); // 30px
$last-val: list.nth($my-list, -1); // 60px

NOTES:

  • Both list and map are iterables. This means you can loop through them via @each.
  • A map is immutable.

Using variables to create styles

Use the #{...} to insert a variable's value with inside a string:

$my-constant: 12;

.col-#{$my-constant} {
	color:red;
}

@if

@for

$columns: 3
@for $i from 1 through $columns {
	.col-#{$i}: {
		width: ($i*10) + px
	}
}

generates this CSS:

.col-1 {
	width: 10px;
}

.col-2 {
	width: 20px;
}

.col-3 {
	width: 30px;
}

@each

$icons:
	"eye" "\f112" 12px,
	"start" "\f12e" 16px,
	"stop" "\f12f" 10px;

@each $name, $glyph, $size in $icons {
	.icon-#{$name}:before {
		display: inline-block;
		font-family: "Icon Font";
		content: $glyph;
		font-size: $size;
	}
}

Becomes this CSS:

icon-eye:before { 
	font-family: "Icon Font";
	content: \f112;
}

icon-start:before { 
	font-family: "Icon Font";
	content: \f12e;
}

@use

This keywords allows to import SCSS mixins, variables, functions into the current SCSS file. The files that are being referenced are called modules.

// style.scss
@use 'foundation/code';
@use 'foundation/lists';

In this example, there are two module files:

  • foundation/_code.scss
  • foundation/_lists.scss

Notice that those files are prefixed with underscore. This is an SCSS convention. More about this convention in the File structure and the underscore convention section.

@include

This keyword allows to invoke a mixin:

nav ul {
  @include horizontal-list;
}

Mixins

@mixin create-classes($prop, $step, $unit, $end) {
	@for $i from 1 through $end {
		&-#{$i} {
			#{$prop}: ($i*$step) + $unit
		}
	}
}

.my-class {
	@include create-classes(width, 5, px, 2)
}

generates this CSS:

.my-class-1 {
	width: 5px;
}

.my-class-2 {
	width: 10px;
}

To default arguments, use something similar to the following:

@mixin create-classes($prop, $step, $unit, $end:2) { ... }

File structure and the underscore convention

You'll notice that many SCSS library (e.g., Bootstrap) are structured this way:

_alert.scss
_badge.scss
...
bootstrap.scss

If you open the bootstrap.scss, you'll see something similar to this:

@import "badge";
@import "alert";
...

With Saas, an _ means a partial scss file. This means that files with an _ prefix are not parsed by the compiler. There will be neither _badge.css nor _alert.css. Those files are instead used to breakdown your SCSS files in modules that are then aggregated into a bigger one (in the Bootstrap example, that's the bootstrap.scss).

NOTICE: In the bootstrap.scss file, you import partial scss by omitting the underscore.

Troubleshooting

position:sticky is not working

https://www.designcise.com/web/tutorial/how-to-fix-issues-with-css-position-sticky-not-working#checking-if-an-ancestor-element-has-overflow-property-set

overflow:hidden with border-radius not working in Safari

That's a known bug. Add the following CSS property:

/* Safari hack for border-radius with overflow: hidden */
-webkit-mask-image: -webkit-radial-gradient(white, black);

Uncaught DOMException: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules

This most probably happens because the document.styleSheets[].rules JS is trying to access a stylesheet from another domain. To check if the CSS is coming from the same domain, use something similar to:

const accessibleStylesheet = !document.styleSheets[0].href || document.styleSheets[0].href.startsWith(WIN.location.origin)

A common reason stylesheet from different domain are loaded on a website are Chrome extensions.

The CSS animation comes back to its start state at the end

Add the following CSS property:

.your-class {
	animation-fill-mode: forwards;
}

FAQ

How to configure the src attribute of the img tag?

This is useful when you want to change the image based on hover state for example. Your normal HTML would normally look like this:

<div>
  <img src="https://example.com/myimage_01.jpg" alt="my image description">
</div>

Replace the above with:

<div class="image-style">
  <img src="https://example.com/myimage_01.jpg" alt="my image description">
</div>
.image-style {
	display: block;
	-moz-box-sizing: border-box;
	box-sizing: border-box;
	background: url(https://example.com/myimage_01.jpg) no-repeat;
	width: 38px; /* image width */
	height: 38px; /* image height */
	padding-left: 38px; /* Equal to image width */
}

.image-style:hover {
	cursor: pointer;
	background: url(https://example.com/myimage_02.jpg) no-repeat;
}

Note that we have to keep the explicit src attribute set to some value.

How to delay effects?

There are 2 different ways to delay CSS effects:

  1. Using the an extra parameter in the transition property. For example, the following snippet shows how to transition from no background to a black background within 100ms:
    background: black;
    transition: background 100ms ease;
    To add a 200ms delay, before that transition starts, we can rewrite the above as follow:
    background: black;
    transition: background 100ms 200ms ease;
  2. Use the transition-delay property:
    background: black;
    transition: background 100ms ease;
    transition-delay: 200ms;

The reason you would use the first method over the second depends on whether the delay happens to all transitions or not. With the first method, you can target which specific transition should be delayed.

How to prefix certain CSS properties for cross-browser compatibility?

-webkit-transition: ...;
-moz-transition: ...;
-ms-transition: ...;
-o-transition: ...;
transition: ...;

How to make an image fit a container without being skewed?

Please refer to the Fitting images section.

How to load CSS stylesheets asynchronously?

The hack is to use an invalid media attribute on the link tab. This causes the browser to download the stylesheet, but it won't wait for the content to be available before rendering the page. Once the content is loaded, the media is set to a valid value so that the style can be applied properly:

<link rel="stylesheet" href="style.css" media="none" onload="this.media='all'">

How to find a native font similar to your custom font?

https://meowni.ca/font-style-matcher/

How to have padding and border width contained inside the element's dimensions?

By default, when padding and borders are added to an element, the extra thickness is added on top of the current element's dimensions. For example, if the element's width is 100px, and its padding is 20px and border is 1px, then its total width is 121px. If you wish to preserve the element's width to 100px, add the following CSS property:

{
	box-sizing: border-box;
}

Annexes

Check if the device is mobile in JS

/*eslint-disable */
const MOBILE_USER_AGENT_01 = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i
const MOBILE_USER_AGENT_02 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i
/*eslint-enable */
let _isMobile = null
const isMobile = () => {
	if (_isMobile === null) {
		if (!WIN || !WIN.navigator || !WIN.navigator.userAgent) {
			_isMobile = false
		} else {
			const userAgent = WIN.navigator.userAgent
			_isMobile = MOBILE_USER_AGENT_01.test(userAgent) || MOBILE_USER_AGENT_02.test(userAgent.substr(0,4))
		}
	}
	return _isMobile 
}

References

@nicolasdao
Copy link
Author

Screen Shot 2021-06-13 at 12 17 21

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment