learn-end-to-end-testing-with-puppeteer

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

In this tutorial, we’ll learn what testing is, the different types of testing, and then we’ll use Puppeteer to perform end-to-end testing on our application. By the end of this tutorial, you should be able to end-to-end test your apps easily with Puppeteer.

Prerequisites

For this tutorial, you need a basic knowledge of JavaScript, ES6 and Node.js.

You must also have installed the latest version of Node.js.

We’ll be using yarn throughout this tutorial. If you don’t have yarn already installed, install it from here.

You should also know the basics of Puppeteer. To understand the basics of Puppeteer, check out this simple tutorial.

To make sure we’re on the same page, these are the versions used in this tutorial:

  • Node 13.3.0
  • npm 6.13.2
  • yarn 1.21.1
  • puppeteer 2.0.0
  • create-react-app 3.3.0

Introduction to Testing

In simple terms, testing is a process to evaluate the application works as expected. It helps in catching bugs before your application gets deployed.

There are four different types of testing:

  1. Static Testing: uses a static type system like TypeScript, ReasonML, Flow or a linter like ESLint. This helps in capturing basic errors like typos and syntax.
  2. Unit Testing: the smallest part of an application, also known as a unit, is tested.
  3. Integration Testing: multiple related units are tested together to see if the application works perfectly in combination.
  4. End-to-end Testing: the entire application is tested from start to finish, just like a regular user would, to see if it behaves as expected.

The testing trophy by Kent C Dodds is a great visualization of the different types of testing:

Testing Trophy - Kent C Dodds

The testing trophy should be read bottom-to-top. If you perform these four levels of testing, you can be confident enough with the code you ship.

Now let’s perform end-to-end testing with Puppeteer.

End-to-end Testing with Puppeteer

Let’s bootstrap a new React project with create-react-app, also known as CRA. Go ahead and type the following in the terminal:

$ npx create-react-app e2e-puppeteer

This will bootstrap a new React project in a e2e-puppeteer folder. Thanks to the latest create-react-app version, this will also install testing-library by default so we can test our applications easily.

Go inside the e2e-puppeteer directory and start the server by typing the following in the terminal:

$ cd e2e-puppeteer
$ yarn start

It should look like this:

React Init

Our App.js looks like this:

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    
logo

Edit src/App.js and save to reload.

Learn React
); } export default App;

We’ll be testing the App.js function and the code will be written in App.test.js. So go ahead and open up App.test.js. It should have the following content:

import React from 'react';
import { render } from '@testing-library/react'; // 1
import App from './App';

test('renders learn react link', () => { // 2
  const { getByText } = render(); // 3
  const linkElement = getByText(/learn react/i); // 4
  expect(linkElement).toBeInTheDocument(); // 5
});

Here’s what’s happening in the code above:

  1. We import the render function from the @testing-library/react package.
  2. We then use the global test function from Jest, which is our test runner installed by default through CRA. The first parameter is a string which describes our test, and the second parameter is a function where we write the code we want to test.
  3. Next up, we render the App component and destructure a method called getByText, which searches for all elements that have a text node with textContent.
  4. Then, we call the getByText function with the text we want to check. In this case, we check for learn react with the case insensitive flag.
  5. Finally, we make the assertion with the expect function to check if the text exists in the DOM.

This comes by default when we bootstrap with CRA. Go ahead and open up another terminal and type the following:

$ yarn test

When it shows a prompt, type a to run all the tests. You should now see this:

React Init Test

Now let’s test this application with end-to-end testing.

Testing the Boilerplate with Puppeteer

Go ahead and install puppeteer as a dev dependency by typing the following in the terminal:

$ yarn add -D puppeteer

Now open up App.test.js and paste the following:

import puppeteer from "puppeteer"; // 1

let browser;
let page;

// 2
beforeAll(async () => {
  browser = await puppeteer.launch({
    headless: false
  });
  page = await browser.newPage();
  await page.goto("http://localhost:3000/");
});

// 3
test("renders learn react link", async () => {
  await page.waitForSelector(".App");

  const header = await page.$eval(".App-header>p", e => e.innerHTML);
  expect(header).toBe(`Edit src/App.js and save to reload.`);

  const link = await page.$eval(".App-header>a", e => {
    return {
      innerHTML: e.innerHTML,
      href: e.href
    };
  });
  expect(link.innerHTML).toBe(`Learn React`);
  expect(link.href).toBe("https://reactjs.org/");
});

// 4
afterAll(() => {
  browser.close();
});

This is what we’re doing in the code above:

  1. Firstly, we import the puppeteer package and declare some global variables, browser and page.
  2. Then we have the beforeAll function provided by Jest. This runs before all tests are run. Here, we launch a new Chromium browser by calling puppeteer.launch(), while setting headless mode to false so we see what’s happening. Then, we create a new page by calling browser.newPage() and then go to our React application’s URL http://localhost:3000/ by calling the page.goto() function.
  3. Next up, we wait for the .App selector to load. When it loads, we get the innerHTML of .App-header>p selector by using the page.$eval() method and compare it with Edit src/App.js and save to reload.. We do the same thing with the .App-header>a selector. We get back innerHTML and href and then we compare them with Learn React and https://reactjs.org/ respectively to test our assertion with Jest’s expect() function.
  4. Finally, we call the afterAll function provided by Jest. This runs after all tests are run. Here, we close the browser.

This test should automatically run and give you the following result:

E2E Test Puppeteer Basic

Let’s go ahead and make a counter app.

Converting the Boilerplate to a Counter App

Firstly, edit some CSS by changing App.css to the following:

.header {
  font-size: 56px;
  text-align: center;
}

.counter-app {
  display: flex;
  justify-content: space-around;
}

button {
  background-color: navajowhite;
  font-size: 32px;
}

.count {
  font-size: 48px;
}

Now change App.js to the following:

import React, { useState } from "react";
import "./App.css";

function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      

Counter

{count}
); } export default App;

Here, we’re making a simple counter application with two buttons, Increment and Decrement. By pressing the Increment button, the counter gets increased by 1, and by pressing Decrement button, the counter gets decreased by 1. It looks like this:

React Counter

Testing the Counter App with Puppeteer

Now change the App.test.js to the following:

import puppeteer from "puppeteer";

let browser;
let page;

beforeAll(async () => {
  browser = await puppeteer.launch({
    headless: false
  });
  page = await browser.newPage();
  await page.goto("http://localhost:3000/");
});

// 1
test("renders counter", async () => {
  await page.waitForSelector(".header");

  const header = await page.$eval(".header", e => e.innerHTML);
  expect(header).toBe("Counter");
});

// 2
test("sets initial state to zero", async () => {
  await page.waitForSelector(".counter-app");

  const count = await page.$eval(".count", e => e.innerHTML);
  expect(count).toBe("0");
});

// 3
test("increments counter by 1", async () => {
  await page.waitForSelector(".counter-app");

  await page.click(".increment");
  const count = await page.$eval(".count", e => e.innerHTML);
  expect(count).toBe("1");
});

// 4
test("decrements counter by 1", async () => {
  await page.waitForSelector(".counter-app");

  await page.click(".decrement");
  const count = await page.$eval(".count", e => e.innerHTML);
  expect(count).toBe("0");
});

afterAll(() => {
  browser.close();
});

Here, we keep the beforeAll and afterAll function the same, as before, where we initialize a browser and go to http://localhost:3000/ in beforeAll and we close the browser in afterAll. Then, we do the following:

  1. We check if the text Counter is rendered. For that, we wait for the .header selector to load. Then we use page.$eval() to get the innerHTML of .header selector. And then we finally make the assertion to check if Counter is rendered.
  2. Next, we check if the initial state is zero. We wait for the .counter-app selector to load. Then we get the innerHTML from the .count selector. We finally compare if the count is 0. Notice that we’re using a string while our state is a number. This is because innerHTML always returns a string.
  3. Here, we check if clicking the button increments the state by 1. First, we wait for the .counter-app selector to load. We then click on the .increment button. This should increase the state from 0 to 1. We then get the innerHTML from the .count selector. Then we compare it to 1, as our increment function should always increase state by 1.
  4. The decrement button should decrease the state by 1. It works the same way as the increment button. First, we wait for the .counter-app selector to load. We then click on the .decrement button. This should decrease the state from 1 to 0. Notice that the state was 1 after we clicked the increment button. We then get the innerHTML from the .count selector. Then we compare it to 0, as our decrement function should always decrease state by 1.

The result should now look like this:

E2E Test Puppeteer Counter

Conclusion

In this tutorial, we learned about different types of testing — static testing, unit testing, integration testing and end-to-end testing. We then performed end-to-end testing on our boilerplate, bootstrapped with the help of create-react-app.

Later, we converted the app to a counter application. And finally we performed end-to-end testing on the counter application.

The Puppeteer library useful not only for performing end-to-end testing but also for doing different kinds of browser automation. Puppeteer is backed by Google and is actively maintained, so be sure to check its docs to understand the wide-ranging use cases it offers.

You can find the code for this tutorial on GitHub.

Akshay is a creator, computer artist and micropreneur from Mumbai.

ssh-honeypot-?-learn-from-your-attackers

This post has been transferred from my old blog and was originally published on Monday 28th November 2016.

Safety Advice

Remember running a Honey Pot is all about letting the bad guys in, therefore you’ll want to take steps to ensure the Honey Pot has no way of accessing your other systems. Remember it is solely your responsibility to ensure you secure your data and systems properly, and the author of this guide cannot be held responsible for any data loss or compromises you receive as a result of running a Honey Pot. Also bear in mind the attacker is likely to try and access stuff in the outside world or could try to use your Honey Pot to host content with legal implications, ensure you suitably firewall the front door to your Honey Pot also to just allow SSH access.

Introduction

In a world of evolving and targeted cyber threats understanding your attacker’s intentions and tools has never been more crucial. By deliberately maintaining vulnerable systems, or Honey Pots, and letting the attackers in you can analyse their activity and gather intelligence so you can be ahead of the game if you ever have a compromise. When running an SSH Honey Pot you can gain a full log of the commands an attacker attempts to run on your system and any files which they attempt to download and can be a great way to obtain samples of malicious software for analysis or understand the techniques used by an attacker to scour your data.

Selecting a HoneyPot

There are several different SSH Honey Pots out there which offer a variety of different features. These can be split in to low and high interactivity Honey Pots.

Low interactivity Honey Pots are fake SSH daemons which emulate an SSH shell, these are very easy to setup, and provides an SSH like experience however the system does not behave like a normal host and hence the attackers are normally very quick to identify and subsequently disconnect from low interactivity Honey Pots. Some low interactivity Honey Pots you can try out include Kippo (https://github.com/desaster/kippo) and Cowrie (https://github.com/micheloosterhof/cowrie), both Honey Pots provide binary logs of the attackers SSH session which you can play back at a later date and also collection of any files the attacker fetches from the internet via wget or curl commands. You can graph interesting metrics in relation to the attackers sessions using the KippoGraph Web UI (https://bruteforce.gr/kippo-graph).

High interactivity Honey Pots are normally fully-fledged hosts that allow the attacker to do everything a user can do on any normal host via SSH. Because of this these hosts generally need a lot of TLC to keep them up and running as they soon get infected with malware, root kits and a variety of other tools of the trade. You also need to keep a close eye on them to ensure they are not able to access the outside world and become part of a bot net or perform any other illicit activities. These hosts are accessed via another host which acts as a gateway, essentially allowing you to perform a man in the middle attack on the SSH connection and obtain valuable data. The gateway host keeps binary logs of the attacker’s sessions for playback along with other stats about the Honey Pot. Although high interactivity Honey Pots sound a lot more complex to run they are generally not spotted by attackers and can collect much more meaningful data than low interactivity equivalents. The most widely used high interactivity Honey Pot is HonSSH by Thomas Nicholson (https://github.com/tnich/honssh).

This guide covers the installation of a high interactivity Honey Pot based on HonSSH and also shows you how to track and extract basic data from attacker’s sessions.

Getting Started

First you’re going to need a couple of hosts, one to act as the HonSSH gateway and the other for the hacker to abuse, the hosts can be relatively modest as long as they are capable of running a Linux distribution and Python 2.7. For this guide I have chosen 2 cloud based virtual machines, if you go down the cloud route make sure you use a provider that allows private networks so you can setup the network accordingly. First arrange your physical hosts, or virtual hosts and virtual networks as follows…

Section / Variable Value Description honeypot / ssh_addr 104.130.12.96 Internet facing IP address of HonSSH Gateway server. honeypot / ssh_port 2220 SSH Port for running the HonSSH server. honeypot / client_addr 0.0.0.0 Instructs HonSSH server to use default route to contact the Honey Pot. honey-static / sensor_name my_honeypot Defines a friendly name for your Honey Pot. honey-static / honey_ip 172.20.0.1 The internal network IP for the Honey Pot. honey-static / honey_port 22 The port the Honey Pot’s SSH service listens on the internal network.

Leave all other variables blank or with their default value. If you want to use the extended functionality of HonSSH feel free to customise as required later.

Next SSH from the HonSSH server to the HoneyPot as root using the password you previously set when configuring the host. Next set the password to some value which is easy to guess for example ‘p455w0rd’ this will allow the attacker to gain access without trying too hard and then we can watch what they are up to.

(honssh_env) honssh@honssh:~$ ssh root@172.20.0.1
The authenticity of host '172.20.0.1 (172.20.0.1)' can't be established. ECDSA key fingerprint is 91:fb:67:8e:5d:68:76:67:23:30:bc:1e:59:78:92:77. Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '172.20.0.1' (ECDSA) to the list of known hosts. root@172.20.0.1's password:
root@honeypot:~# passwd
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
root@honeypot:~#

Once completed disconnect from the HoneyPot SSH session to return to the HonSSH host. Change directory into the HonSSH folder and generate some keys for HonSSH to use for cryptography. Note when using ssh-keygen we override the default path with a relative file “id_rsa” / “id_dsa”.

(honssh_env) honssh@honssh:~$ cd honssh
(honssh_env) honssh@honssh:~/honssh$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/honssh/.ssh/id_rsa): id_rsa id_rsa already exists.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa.
Your public key has been saved in id_rsa.pub.
The key fingerprint is: 85:11:45:f4:00:83:b6:d9:fb:d5:8a:56:23:a3:f9:81 honssh@honssh
(honssh_env) honssh@honssh:~/honssh$ ssh-keygen -t dsa Generating public/private dsa key pair.
Enter file in which to save the key (/home/honssh/.ssh/id_dsa): id_dsa Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_dsa.
Your public key has been saved in id_dsa.pub.
The key fingerprint is: 64:5c:8f:3c:6d:fd:a9:b8:26:83:75:4c:f9:cb:af:16 honssh@honssh

Next start up the HonSSH server, this will start the server up and give a very verbose output, hopefully you should see something similar to the below…

(honssh_env) honssh@honssh:~/honssh$ twistd -y honssh.tac -p honssh.pid –n & 2016-05-10 19:53:01 0000 [-] Log opened.
2016-05-10 19:53:01 0000 [-] twistd 16.1.1 (/home/honssh/honssh_env/bin/python 2.7.9) starting up.
2016-05-10 19:53:01 0000 [-] reactor class: twisted.internet.epollreactor.EPollReactor.
2016-05-10 19:53:01 0000 [-] HonsshServerFactory starting on 2220
2016-05-10 19:53:01 0000 [-] Starting factory
2016-05-10 19:53:01 0000 [HonsshSlimClientTransport,client] [CLIENT] - Got SSH Version String: SSH-2.0-OpenSSH_6.7p1 Debian-5
2016-05-10 19:53:01 0000 [HonsshSlimClientTransport,client] Disconnecting with error, code 10
reason: user closed connection
2016-05-10 19:53:01 0000 [HonsshSlimClientTransport,client] connection lost 2016-05-10 19:53:01 0000 [HonsshSlimClientTransport,client] [HONSSH] - HonSSH Boot Sequence Complete - Ready for attacks!
2016-05-10 19:53:01 0000 [-] Stopping factory

Open a new terminal window and try SSHing to the HonSSH Gateway server on the listening port of HonSSH, hopefully you should get a connection, try logging in with the password you previously set on the Honey Pot host. Open a new terminal window and try SSHing to the HonSSH Gateway server on the listening port of HonSSH, hopefully you should get a connection, try logging in with the password you previously set on the Honey Pot host.

$ ssh root@104.130.12.96 -p2220
Warning: Permanently added '[104.130.12.96]:2220' (RSA) to the list of known hosts. root@104.130.12.96's password:
The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law.
Last login: Tue May 10 19:45:56 2016 from 172.20.0.3
root@honeypot:~#

Congratulations, this is your first connection to your Honey Pot, this is the same as what would happen with an attacker. Go back to your previous terminal window and issue an ls command, you should see a sessions directory, in here should be another directory with your sensor name as set in the configuration file. Within this directory are for each client IP which has connected and has a session on the Honey Pot, these directories contain log files for each session. Try catting out one of the logs so we can see what the user got up to.

(honssh_env) honssh@honssh:~/honssh$ cat sessions/my_honeypot/109.159.108.13/20160510_195912_678718.log 20160510_195912_678718 - [POT ] my_honeypot - 172.20.0.1:22 20160510_195912_678718 - [SSH ] Incoming Connection from 109.159.108.13:52630 - United Kingdom
20160510_195914_325358 - [SSH ] Login Successful: root:p455w0rd 20160510_195914_503284 - [TERM0] Opened Channel 20160510_195915_262392 - [TERM0] Command Executed: ls 20160510_195918_048578 - [TERM0] Command Executed: cd / 20160510_195918_368344 - [TERM0] Command Executed: ls 20160510_195919_461375 - [TERM0] Closed Channel 20160510_195919_466126 - [SSH ] Lost Connection with 109.159.108.13
(honssh_env) honssh@honssh:~/honssh$

However, configuration is sadly not complete, unfortunately SSH servers do not usually listen on port 2220 and you may have noticed that when you are connected to the Honey Pot you are unable to do anything with the internet, try a ping to a website. Also in the case of the Honey Pot above you may have noticed it’s host name is set to ‘honeypot’. It is likely with these tell-tale signs and strange configuration not many hackers will stumble across the host, and if they do they’ll probably quit our right away. Let’s get to work on fixing these items, leave the honssh user’s account on the HonSSH Gateway server and ensure you have a root shell.

(honssh_env) honssh@honssh:~/honssh$ exit
root@honssh:~# whoami
root

Next lets configure iptables to forward port 22 to 2220 on the host and also allow outbound nat for the Honey Pot to gain internet access, we also install the iptables persistence package to ensure the rules survive a reboot.

root@honssh:~# sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/g' /etc/sysctl.conf
root@honssh:~# sysctl –p
root@honssh:~# echo 1 > /proc/sys/net/ipv4/ip_forward
root@honssh:~# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
root@honssh:~# iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 22 -j REDIRECT --to-port 2220
root@honssh:~# apt-get install iptables-persistent

When asked if you’d like to save the current rules reply with “yes”. Your configuration is now complete. Remember if you want to login to the Honey Pot connect via Port 22, if you want to administer the gateway connect on port 2222, su to the honssh user and checkout the sessions directory if you want to see whats been going on on the Honey Pot.

You probably need to update a few items on the Honey Pot server to get the internet connectivity working and to set a more reasonable hostname. SSH to the Honey Pot and edit the /etc/network/interfaces file, add the default gateway to the eth0 block of the Honey Pot server.:: When asked if you’d like to save the current rules reply with “yes”. Your configuration is now complete. Remember if you want to login to the Honey Pot connect via Port 22, if you want to administer the gateway connect on port 2222, su to the honssh user and checkout the sessions directory if you want to see whats been going on on the Honey Pot.

You probably need to update a few items on the Honey Pot server to get the internet connectivity working and to set a more reasonable hostname. SSH to the Honey Pot and edit the /etc/network/interfaces file, add the default gateway to the eth0 block of the Honey Pot server.

root@honeypot:~# vi /etc/network/interfaces auto eth0
iface eth0 inet static
    address 172.20.0.1
    gateway 172.20.0.3
    netmask 255.255.255.0

Next edit the /etc/resolv.conf file to have some valid name servers and set the hostname to something more appropriate.

root@honeypot:~# vi /etc/resolv.conf
  nameserver 8.8.4.4
  nameserver 8.8.8.8
root@honeypot:~# hostname web01.mywebsite.com
root@honeypot:~# echo "web01.mywebsite.com" > /etc/hostname

What Now?

Congratulations you now have an SSH Honey Pot listening on the internet, soon some attackers should start scanning your IP range and perform dictionary attacks as soon as they realise your running an SSH server with password authentication enabled. You should check back periodically and see if there were any active sessions and see what they have been up to, remember be highly suspicious of anything an attacker places on your Honey Pot host and take care when handling these files. It is likely from time to time you will need to rebuild the Honey Pot host as attackers are usually quite brutal with them, and you may also receive complaints from your ISP if an attacker starts doing bad stuff from your Honey Pot. In this case you should proactively manage all of these events. You can always put IPTables rules in place on the HonSSH server to ensure commonly abused outbound ports are limited, it’s also nice to run tcpdump on the HonSSH server so you can capture network traffic for analysis.

You may have noticed its rather cumbersome to collate and manage logs and session data via SSH to the HonSSH Gateway, you can make life a little easier by installing MySQL and configuring MySQL logging within the honssh.cfg file. You can then make some clever queries to make sense of the data.

Reply in the comments if you have any exciting findings from running your own Honey Pot, in the future I may publish another article covering more advanced analysis techniques for the Honey Pot.

Further Help

If you require any further assistance checkout the HonSSH Docs at https://docs.honssh.com, if you discover a bug or have a feature request please raise an issue on the HonSSH Github repo https://github.com/tnich/honssh/issues.

flash-grid:-learn-css-grid-by-building-a-grid-system

Lately, I’ve been experimenting with the idea of building a lightweight grid system based on CSS Grid.

We do have a grid system in CodyFrame, and it’s based on Flexbox. However, CSS Grid has so many powerful features not available in Flexbox, so I ended up creating Flash Grid.

I’m going to share the whole process behind creating Flash Grid. If you’d like to learn more about CSS Grid, this is a great place to start, since we’ll touch the main CSS Grid properties, and we’ll share some practical tricks to get the most out of this powerful layout system.

In case you’d rather skip the tutorial and just grab the code:


Let’s start! ?

The first step is creating the .grid class:

$grid-columns: 12 !default;

.grid {
  --grid-cols: #{$grid-columns};
  display: grid;
  grid-gap: var(--grid-gap, 0); // default grid-gap = 0
  grid-template-columns: repeat(var(--grid-cols), 1fr); // grid of 12 flexible columns

  > * {
    grid-column-end: span var(--grid-cols); // each grid item takes full-width by default
  }
}

In defining the number of grid columns, we use the !default SCSS flag in case the grid system is imported as a module, and we want this value to be customizable.

The grid-template-columns is the property where we define the grid layout: we want 12 responsive columns. The width of each column is 1fr. Fr (fraction unit) is a smart unit, equal to 1 part of the available space. Because our grid is composed of 12x 1fr columns, each flexible column takes 1/12 of the available width.

The repeat() function allows us to pass a single width value (1fr). Another way to define the same grid would be:

.grid {
  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; // ?
}

…but, you know…not so elegant!

Here’s a quick overview of the grid we’ve just created:

12 columns grid

In the screenshot above, notice the numbers in between columns (for now, focus only on the positive numbers on top). These line numbers can be used to place grid items.

In the .grid snippet, we’re also targeting all the grid children, and setting their grid-column-end value equal to span 12.

By default, we want each child to take the whole available width. grid-column-end is used to specify the grid item’s end position. You can use this property to set an end line (e.g., grid-column-end: 3;). But if you use the “span” magic word, you define how many columns should be occupied by the grid item. For example, grid-column-end: span 12; means “span this element across 12 columns”.

Why setting a default 12 columns span for the grid items? We’re working mobile-first. We can assume, in most cases, our grid items will occupy the full-width (12 columns) at first, and then a smaller amount of columns on bigger screens. Our default value prevents us from having to specify on each grid item .col-12 (span 12) manually.

The number of columns is set as a CSS custom property, in case you want to change it on a component level (or by creating other utility classes). For example:

.grid--2 {
  --grid-cols: 2;
}

Next, we can define utility classes for the grid-gap property:

.grid-gap-xxxxs { --grid-gap: var(--space-xxxxs, 0.125rem); }
.grid-gap-xxxs  { --grid-gap: var(--space-xxxs, 0.25rem); }
.grid-gap-xxs   { --grid-gap: var(--space-xxs, 0.375rem); }
.grid-gap-xs    { --grid-gap: var(--space-xs, 0.5rem); }
.grid-gap-sm    { --grid-gap: var(--space-sm, 0.75rem); }
.grid-gap-md    { --grid-gap: var(--space-md, 1.25rem); }
.grid-gap-lg    { --grid-gap: var(--space-lg, 2rem); }
.grid-gap-xl    { --grid-gap: var(--space-xl, 3.25rem); }
.grid-gap-xxl   { --grid-gap: var(--space-xxl, 5.25rem); }
.grid-gap-xxxl  { --grid-gap: var(--space-xxxl, 8.5rem); }
.grid-gap-xxxxl { --grid-gap: var(--space-xxxxl, 13.75rem); }

The spacing variables are part of CodyFrame. You can replace them with your own spacing scale or use the fallbacks specified in each variable (the value after the comma is applied if the variable is undefined).

The grid-gap property is used to define the space between grid items.

To complete the basic grid system, we should define the .col classes:

@for $i from 1 through $grid-columns {
  .col-#{$i}  { grid-column-end: span #{$i}; }
}

We use the SASS @for loop to generate the .col classes according to the number of columns specified in the $grid-columns variable.

The compiled CSS is:

.col-1  { grid-column-end: span 1; }
.col-2  { grid-column-end: span 2; }
.col-3  { grid-column-end: span 3; }
.col-4  { grid-column-end: span 4; }
.col-5  { grid-column-end: span 5; }
.col-6  { grid-column-end: span 6; }
.col-7  { grid-column-end: span 7; }
.col-8  { grid-column-end: span 8; }
.col-9  { grid-column-end: span 9; }
.col-10 { grid-column-end: span 10; }
.col-11 { grid-column-end: span 11; }
.col-12 { grid-column-end: span 12; }

The col classes specify the number of columns occupied by a grid item. Remember that the “span” word means “span the element across x columns”, where x is the number specified after span.



Add some CSS Grid spice

To recap, the barebone version of Flash Grid includes the definition of the grid, the grid-gap, and the col utility classes:

Now it’s time to add some spice! ?

Here’s the .grid-auto-cols utility class:

.grid-auto-cols { // cols = same size
  display: grid;
  grid-gap: var(--grid-gap, 0);
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}

This class is similar to the .grid class, except we don’t set a number of columns. auto-fit replaces the 12 in the .grid class. It means letting CSS Grid decide the number of columns based on the width value of the columns (the second value of the repeat() function).

But wait! The width value (1fr in the .grid class) is now replaced by a minmax() function. It literally means the minimum width of a column is 0, while the max value is 1fr. We’re setting a range of values for the column width.

The result: you get a grid where all columns have the same width, regardless of their content or the number of grid items.

Grid Auto Cols

Using a similar approach, we create the .grid-auto-{size} utility classes:

.grid-auto-xs, .grid-auto-sm, .grid-auto-md, .grid-auto-lg, .grid-auto-xl { // auto-sized grid
  display: grid;
  grid-gap: var(--grid-gap, 0);
  grid-template-columns: repeat(auto-fit, minmax(var(--col-min-width), 1fr));
}

.grid-auto-xs { --col-min-width: 8rem; }
.grid-auto-sm { --col-min-width: 10rem; }
.grid-auto-md { --col-min-width: 15rem; }
.grid-auto-lg { --col-min-width: 20rem; }
.grid-auto-xl { --col-min-width: 25rem; }

Unlike .grid-auto-cols, these new classes have a minimum width value equal to --col-min-width. The result is a responsive grid where a new column is added when there’s enough space for it (the minimum width specified in the minmax() function).

Alt Text

? With the .grid-auto utility classes (.grid-auto-cols and .grid-auto-{size}) you can create responsive layouts with no need to use .col classes on the grid items. Actually, you shouldn’t use .col classes at all if you want the .grid-auto classes to work properly.

Finally, to take advantage of the grid line numbers, we can create a new set of utility classes: col-start-{line-number} and .col-end-{line-number}.

@for $i from 1 through $grid-columns {
  .col-start-#{$i} { grid-column-start: #{$i}; }
  .col-end-#{$i 1} { grid-column-end: #{$i 1}; }
}

The .col-start classes go from .col-start-1 to col-start-12, while the .col-end classes go from .col-end-2 to .col-end-13.

Use them if you want a grid item to span between a specific start and end line.

Remember these are the line numbers:

12 columns grid

The negative numbers at the bottom are an alternative way to target the same lines. Why they’re useful: if you want to target the final line, regardless of the number of columns, you can do the following:

.col-end { 
  grid-column-end: -1; 
}

The .col-start/end classes allow you to create advanced grids:



Class breakpoint modifiers

To make our grid editable at different breakpoints, we can add class modifiers. In CodyFrame, our convention is to add a @{breakpoint} suffix to the classes (e.g., col-4@sm) to target breakpoints.

Here’s an example of the class modifiers at the x-small breakpoint:

$breakpoints: (
  xs: 32rem, 
  sm: 48rem,
  md: 64rem,
  lg: 80rem,
  xl: 90rem
) !default;

@mixin breakpoint($breakpoint) {
  @media (min-width: map-get($map: $breakpoints, $key: $breakpoint)) { @content; }
}

@include breakpoint(xs) {
  .grid-auto-xs@xs { --col-min-width: 8rem; }
  .grid-auto-sm@xs { --col-min-width: 10rem; }
  .grid-auto-md@xs { --col-min-width: 15rem; }
  .grid-auto-lg@xs { --col-min-width: 20rem; }
  .grid-auto-xl@xs { --col-min-width: 25rem; }

  .grid-auto-cols@xs { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@xs  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@xs { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@xs { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@xs { grid-column-start: auto; }
  .col-end-auto@xs { grid-column-end: auto; }
}



Beta version of ⚡️ Flash Grid

The beta of Flash Grid is good to go!

Take it for a spin on Codepen:

Final SCSS code:

// ⚡️ Flash Grid
$grid-columns: 12 !default;

.grid, [class*="grid-auto-"] {
  display: grid;
  grid-gap: var(--grid-gap, 0);
}

.grid {
  --grid-cols: #{$grid-columns};
  grid-template-columns: repeat(var(--grid-cols), 1fr);

  > * {
    grid-column-end: span var(--grid-cols);
  }
}

.grid-auto-xs, .grid-auto-sm, .grid-auto-md, .grid-auto-lg, .grid-auto-xl { // auto-sized grid
  grid-template-columns: repeat(auto-fit, minmax(var(--col-min-width), 1fr));
}

.grid-auto-xs { --col-min-width: 8rem; }
.grid-auto-sm { --col-min-width: 10rem; }
.grid-auto-md { --col-min-width: 15rem; }
.grid-auto-lg { --col-min-width: 20rem; }
.grid-auto-xl { --col-min-width: 25rem; }

.grid-auto-cols { // cols = same size
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}

.grid-gap-xxxxs { --grid-gap: var(--space-xxxxs, 0.125rem); }
.grid-gap-xxxs  { --grid-gap: var(--space-xxxs, 0.25rem); }
.grid-gap-xxs   { --grid-gap: var(--space-xxs, 0.375rem); }
.grid-gap-xs    { --grid-gap: var(--space-xs, 0.5rem); }
.grid-gap-sm    { --grid-gap: var(--space-sm, 0.75rem); }
.grid-gap-md    { --grid-gap: var(--space-md, 1.25rem); }
.grid-gap-lg    { --grid-gap: var(--space-lg, 2rem); }
.grid-gap-xl    { --grid-gap: var(--space-xl, 3.25rem); }
.grid-gap-xxl   { --grid-gap: var(--space-xxl, 5.25rem); }
.grid-gap-xxxl  { --grid-gap: var(--space-xxxl, 8.5rem); }
.grid-gap-xxxxl { --grid-gap: var(--space-xxxxl, 13.75rem); }

@for $i from 1 through $grid-columns {
  .col-#{$i}  { grid-column-end: span #{$i}; }
  .col-start-#{$i} { grid-column-start: #{$i}; }
  .col-end-#{$i 1} { grid-column-end: #{$i 1}; }
}

.col-start { grid-column-start: 1; }
.col-end { grid-column-end: -1; }

// breakpoints
$breakpoints: (
  xs: 32rem, 
  sm: 48rem,
  md: 64rem,
  lg: 80rem,
  xl: 90rem
) !default;

@mixin breakpoint($breakpoint) {
  @media (min-width: map-get($map: $breakpoints, $key: $breakpoint)) { @content; }
}

@include breakpoint(xs) {
  .grid-auto-xs@xs { --col-min-width: 8rem; }
  .grid-auto-sm@xs { --col-min-width: 10rem; }
  .grid-auto-md@xs { --col-min-width: 15rem; }
  .grid-auto-lg@xs { --col-min-width: 20rem; }
  .grid-auto-xl@xs { --col-min-width: 25rem; }

  .grid-auto-cols@xs { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@xs  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@xs { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@xs { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@xs { grid-column-start: auto; }
  .col-end-auto@xs { grid-column-end: auto; }
}

@include breakpoint(sm) {
  .grid-auto-xs@sm { --col-min-width: 8rem; }
  .grid-auto-sm@sm { --col-min-width: 10rem; }
  .grid-auto-md@sm { --col-min-width: 15rem; }
  .grid-auto-lg@sm { --col-min-width: 20rem; }
  .grid-auto-xl@sm { --col-min-width: 25rem; }

  .grid-auto-cols@sm { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@sm  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@sm { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@sm { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@sm { grid-column-start: auto; }
  .col-end-auto@sm { grid-column-end: auto; }
}

@include breakpoint(md) {
  .grid-auto-xs@md { --col-min-width: 8rem; }
  .grid-auto-sm@md { --col-min-width: 10rem; }
  .grid-auto-md@md { --col-min-width: 15rem; }
  .grid-auto-lg@md { --col-min-width: 20rem; }
  .grid-auto-xl@md { --col-min-width: 25rem; }

  .grid-auto-cols@md { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@md  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@md { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@md { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@md { grid-column-start: auto; }
  .col-end-auto@md { grid-column-end: auto; }
}

@include breakpoint(lg) {
  .grid-auto-xs@lg { --col-min-width: 8rem; }
  .grid-auto-sm@lg { --col-min-width: 10rem; }
  .grid-auto-md@lg { --col-min-width: 15rem; }
  .grid-auto-lg@lg { --col-min-width: 20rem; }
  .grid-auto-xl@lg { --col-min-width: 25rem; }

  .grid-auto-cols@lg { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@lg  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@lg { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@lg { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@lg { grid-column-start: auto; }
  .col-end-auto@lg { grid-column-end: auto; }
}

@include breakpoint(xl) {
  .grid-auto-xs@xl { --col-min-width: 8rem; }
  .grid-auto-sm@xl { --col-min-width: 10rem; }
  .grid-auto-md@xl { --col-min-width: 15rem; }
  .grid-auto-lg@xl { --col-min-width: 20rem; }
  .grid-auto-xl@xl { --col-min-width: 25rem; }

  .grid-auto-cols@xl { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@xl  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@xl { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@xl { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@xl { grid-column-start: auto; }
  .col-end-auto@xl { grid-column-end: auto; }
}

CSS selectors cheat sheet gives us a quick overview of what we’ll learn in CSS selectors

Designers often confuse with the attribute selectors, element selectors, pseudo elements, pseudo-classes, combinators. But they all are selectors. And this cheat sheet has been presented in such a way that it ends up confusing between different kind of selectors.

Selector’s cheat sheet represents all the selectors with examples so that you can learn it easily and quickly.

See a complete list of CSS selectors.

3. Pseudo Elements

  • ::firs-line
  • ::first-letter
  • ::selection
  • ::after
  • ::before
  • :placeholder

4. Combinators

  • Descendent combinator
  • Child combinator (‘>’)
  • Next-sibling combinator (‘ ‘)
  • Following combinator (‘~’)

6. Input Pseudo-classes

  • :disabled
  • :enabled
  • :checked
  • :read-only
  • :placeholder-shown
  • :out-of-range
  • :required

7. Structural Pseudo-classes

  • :first-child
  • :nth-child(E)
  • :last-child
  • :nth-last-child(E)
  • :only-child
  • :first-of-type
  • :nth-of-type(E)
  • :last-of-type
  • :nth-last-of-type(n)

A detailed explanation of CSS Selectors

1. Element Selectors

The elements are selected by the element names or universal selector.

Type selector

The element selectors select the elements by the element name or a list of element names and then applies CSS properties to the selected elements.

p, span, code{ color:red; }

Universal selector (*)

‘*’ is a universal selector. It selects all of the elements and applies CSS properties.

*{ color:red; }


2. Attribute selectors

Attribute selectors select those elements that have attributes and values according to the rules of attributes selectors.

[att]

[att] selects those elements that have ‘att’ attribute.

input[type]

[att=val]

[att=val] selects those elements that have ‘att’ attribute with the value ‘val’

div[title="warning"]

[att~=val]

‘~’ selector selects those elements that have ‘att’ attribute with at least one value matching the ‘val’ value

a[rel~="next"]

[att|=val]

‘|’ selector selects the elements that have ‘att’ attribute with the value ‘val’ following a hyphen (-).

a[hreflang|="en-US"]

[att^=val]

‘^’ selector selects the elements that have ‘att’ attribute and the value starting with ‘val’.

img[type^="image/"]

[att$=val]

‘$’ selector selects those elements that have ‘att’ attribute and the value ending with ‘val’

img[type$="png"]

[att*=val]

‘*’ selector selects the elements that have ‘att’ attribute with the ‘val’ value as substring.

p[class*="sign"]


3. Pseudo elements

A pseudo element consists of double color (::) following the name of pseudo element. And pseudo element applies CSS properties to the associated element.

It accesses the content that is not present in the hierarchy. And that is not accessible by the document language in a document tree.

::firs-line

::first-line selects firs-line of associated element and applies CSS properties.

p::first-line{font-size:20px;}

::first-letter

::first-letter selects the first letter of the associated element and applies CSS properties.

p::first-letter{text-transform:uppercase;}

::selection

::selection represents the text selected by the user in the associated element.

div::selection{background-color:lightgreen;}

::after

::after selects the content generated from CSS and the generated content is after the associated element.

div::after{content:"...learn more";}

::before

::before selects the content generated from CSS and the generated content is before the associated element.

div::before{content:"Tip: ";}

::placeholder

::placeholder pseudo element selects the input elements that have placeholder attribute.

input::placeholder{color:blue;}


4. Combinators

It selects elements based on the relation between elements within the hierarchy of elements i.e based on whether it is child, next, or a descedent element.

Descendent combinator

descendent combinator selects and applies CSS properties to the orbitrary child element.

div p{background-color:green;}

Child combinator (‘>’)

child combinator selects and applies CSS properties to the child element.

ol > li{ background-color:lightblue;}

Next-sibling combinator (‘ ‘)

‘ ‘ combinator selects and applies CSS properties to the next immediate element of the current element.

p code{ color:red;}

Following combinator (‘~’)

‘~’ combinator selects and applies CSS properties to the next element of the current element.

code ~ kbd{ color:rgb(0,225,0);}


5. Pseudo classes

Pseudo class consists of single colon (‘:’) and pseudo class name.

5.1 User Action Pseudo-classes

This pseudo-class is used when a user interacts with a link in anyway.

:link

:link selects and applies CSS properties to all of the hyperlinks.

a{color:black;}

:hover

:hover applies CSS properties to the hyperlink when a user hovers the mouse over the link.

a:hover{color:blue;}

:visited

:visited applies CSS properties to the hyperlink that has already been visited.

a:visited{color:red;}

:active

:active pseudo class applies CSS properties to the hyperlink within the active duration (the duration between mouse clicking and releasing).

a:active{color:green;}

5.2 Input Pseudo-classes

Input pseudo class selects the input element based on the bool attribute or state of the input element.

:disabled

:disabled pseudo class selects the input element that is disabled by the disabled attribute.

input:disabled{ background-color:gray;	}

:enabled

:enabled selects the input elements that are enabled and input elements are enabled by default.

input:enabled{ border-radius:2px; }

:checked

:checked selects the input elements that are already checked or checked by the user.

input:checked{ height:20px; }

:read-only

:read-only pseudo class selects the input elements that have readonly attribute.

input:read-only{ background-color:gray; }

:placeholder-shown

:placeholder-shown pseudo class selects the elements that have placeholder attribute

input:placeholder-shown{ font-size:20px; }

:out-of-range

:out-of-range pseudo class selects the input element where the value is out of range.

input:out-of-range{ color:red;	}

:required

:required pseudo class selects the input element that requires information necessarily.

input:required{ box-shadow:3px 3px 5x rgba(200,200,200,0.5); }

5.3 Structural Pseudo-classes

This pseudo class selects child elements according to the position of child element with respect to the parent and the position is evaluated according to the expression.

:first-child

:first-child selects first child of the parent element.

table tr:first-child{ background-color:gray; }

:nth-child(E)

:nth-child(E) selects the child element in accordance with the value of an expression (E) that evaluates to the position of element from the start.

table tr:nth-child(2n 1){ background-color:gray; }

:last-child

:last-child selects the last child of the parent element.

ul li:last-child{ background-color:lightblue; }

:nth-last-child(E)

:nth-last-child(E) selects the child element in accordance with the value of an expression (E) that evaluates to the position of element from the end.

table tr:nth-last-child(2n 1){ background-color:gray; }

:only-child

:only-child selects the element that is a sole child of the parent element.

div p:only-child{ background-color:lightblue; }

:first-of-type

:first-of-type selects the first child of a specific type of element. In this case, parent has more than one types of elements.

dl dd:first-of-type{ background-color:lightblue; }

:nth-of-type(E)

:nth-of-type(E) selects the child of a specific type of element in accordance with the value of expression (E) that evaluates to the position of element from the start. In this case, parent has more than one type of elements.

tr td:nth-of-type(2n 1){ background-color:gray; }

:last-of-type

:last-of-type selects the last chid of a specific type of element.

dl dd:last-of-type{ background-color:lightblue; }

:nth-last-of-type(n)

:nth-last-of-type(E) selects the child of a specific type of element in accordance with the value of expression (E) that evaluates to the position of element from the end. In this case, parent has more than one types of elements.

body > h2:nth-last-of-type(n 6){ color:blue; }


Summary

This cheat sheet covers all of the topics and sub-topics of selectors. In this page, you see a definition and an example related to every sub-topic. But you can learn it thoroughly in CSS tutorials.


meet-and-learn-from-1,300+-search-marketing-experts-at-smx-east
Photo booth picture of SMX East attendeesSMX® East, November 13-14 in NYC at the premier conference designed by search marketers for search marketers. You’ll meet prospective clients, partners, even future talent — it’s all possible at the largest gathering of SEOs and SEMs on the East Coast.

Here’s a look at the networking events in store:

Wednesday, November 13

Expo Hall Reception, 5:00pm – 6:00pm

After a full day of sessions and keynotes, unwind with a drink and delicious snacks in the Expo Hall. Mingle with fellow attendees, connect with market-defining vendors, and have fun!  This event is sponsored by Perficient Digital. (Open to all pass holders.)

Search Marketing Meetups, 6:00pm – 7:00pm

Choose from four interactive meetups covering agency life, technical SEO, multi-location search marketing, and professional development. (Open to all pass holders.)

  • Agency Meetup: Attend this exclusive meetup designed just for consultants and agency marketers. Join Search Engine Land’s editor-in-chief Ginny Marvin to discuss common obstacles and opportunities, share your own advice, and learn something new.

  • Technical SEO & Developers Meetup: Search Engine Land’s Detlef Johnson will lead a discussion about past, present, and future web development frameworks and technical search engine optimization issues that can affect success or failure. In this unique (and technical!) meetup, you’ll spend time discussing the topics that matter to technical SEOs and developers. 

  • Multi-Location Meetup: In this meetup, Search Engine Land contributing editor Greg Sterling will lead an engaging and collaborative discussion about the issues that matter to multi-location brands including the critical elements of an effective local strategy and the future of Google My Business and local search.

  • Professional Development for SEOs & SEMs Meetup: Join Jessica Bowman for an informal conversation on how to develop your professional skills in such a way that makes you the obvious choice for that next step up the workplace ladder. Discussion topics include how to stand out at meetings as a leader, strategies to earn colleague praise (which makes its way back to your boss), tips for delivering presentations like an executive, getting buy-in for new projects, advice for asking for a promotion or raise, and much more.

Open Perspectives, 7:30pm – 9:30pm

Open Perspectives, created by Microsoft, is focused on exploring principles of diversity, inclusion, and inclusive marketing. It’s the space where we can discuss deep and relevant issues and challenges that we all face with a focus on the experiences of underrepresented groups and their allies. We create an open environment where all are welcome and can contribute to the conversation. This event is hosted at Hudson Mercantile, 500 W 36 Street, New York, NY 10018. (Open to all pass holders.)

Wednesday & Thursday, November 13 & 14

Birds of a Feather Lunches

At SMX East, you’ll get more out of lunch than just hot, delicious meals. Sign up for one of our “Birds of a Feather” lunch tables for topic-driven discussion about specific areas of search. These tables are designed to let you continue the conversation with others who share your interests. Once you register, we’ll send you an invite! (Open to All Access pass holders only.)

Don’t miss your chance to share tips, talk shop, and grow your circle of professional contacts in a welcoming environment. Pick your ideal pass and register today to enjoy up to $600 off on-site rates.

I look forward to meeting you in NYC!

Psst… In addition to all of these networking events, you’ll access a firehose of content: 90 tactic-rich presentations on all-things search marketing, including two new full-day concentrations on agency operations and local search marketing for multi-location brands. See the agenda.



About The Author

why-you-should-definitely-learn-how-to-use-css-in-js

From time to time, I’ve heard that CSS-in-JS poses a barrier to entry for some people.
Like any new software abstraction, CSS-in-JS is intended to abstract away some of the complexities from a lower level implementation, in this case CSS.
Abstractions are neither good nor bad, but they do require learning new things.
Using CSS-in-JS lets you style applications without needing to worry about a few of the thornier aspects of CSS,
but it doesn’t replace CSS entirely,
and you still need to have a solid understanding of CSS to be successful.
You will absolutely need a solid understanding of
CSS properties, values and types, inheritance, layout, stacking context, and the box-model to be successful with CSS-in-JS.

CSS-in-JS isn’t a boogieman out to destroy everything you love about CSS.
In my opinion, it can make authoring CSS far more enjoyable than any other tool I’ve used in the past,
and I’d encourage you to give it a shot.

If you already know JavaScript, CSS-in-JS helps remove the need to use native CSS syntax,
or deal with context switching between different languages.
For younger developers straight out of bootcamp,
this can make styling an application more accessible because there are fewer new things to learn.
I’ve seen this myself, and remember one former colleague gushing about how Styled Components was so much easier to use than what they’d learned in school.

If you’re less familiar with JS syntax, the effect can be the total opposite,
and it can seem like too much to learn at once.
Don’t get discouraged.
You can still learn how to use CSS-in-JS, and keep in mind that whatever you learn along the way will be applicable knowledge in many other situations where JS is used.
I like to think of CSS-in-JS as a potentially low-barrier way to get started with JS.

Despite what I’ve written here,
it can still feel like a barrier because it does require learning something new,
but one thing that I love about software development is that I’m never bored and constantly learning.

If you’re not willing to learn something new, then you probably won’t like CSS-in-JS at all,
but if you do have a curious mind, I hope this post helps.

What does CSS-in-JS do?

Most libraries are designed to:

  • Let you author CSS in JavaScript syntax
  • Colocate styles with components
  • Take advantage of native JS syntax features
  • Take advantage of anything from the JS ecosystem

What does CSS-in-JS not do?

At the end of the day, you’re still writing code that generates CSS, and the full power of the language is still available.
CSS-in-JS does not get rid of the need to understand the following:

  • How styles are applied to the DOM
  • How inheritance works
  • How CSS properties work
  • How CSS layout works

What abstractions does CSS-in-JS provide?

CSS-in-JS libraries allow you to author styles without generally having to think about:

  • The cascade (not inheritance)
  • Specificity
  • Naming selectors
  • Enforcing naming conventions
  • Linting another language
  • Enforcing file structures for another language
  • Additional build tools

What you need to know

To be effective with CSS-in-JS, you’ll need to have a good grasp of the following concepts:

Types in JavaScript:

  • Strings
  • Numbers
  • Objects
  • Arrays
  • Boolean
  • Null
  • Undefined

MDN is a great resource for learning JS, and I’d highly recommend their tutorial on
JavaScript data types and data structures.

To really excell with CSS-in-JS, you’ll benefit from understanding the following:

What other benefits does CSS-in-JS have?

  • It can make dead code elimination easier to manage
  • It will throw errors to help you avoid mistakes, including syntax, type, and undefined errors
  • Many libraries offer support for theming
  • You can use virtually any package from npm
  • You can use ES modules and scope
  • Most libraries offer ways to handle dynamic styling
  • Styles are generally “scoped” to a specific component
  • It’s far easier to write unit tests with CSS-in-JS
  • Many libraries offer performance improvements like critical CSS with no additional setup needed

Where do I start?

There’s certainly no one-size-fits-all answer here.

If you haven’t used React before, or have less experience with JS, I’d recommend checking out the Gatsby tutorial.
Once you have a good grasp of the concepts laid out in the tutorial, see the guide on using Using CSS-in-JS.

If you know enough React to get going on your own, I’d recommend checking out Styled Components.
Then, once you’ve got that working, definitely check out Emotion.
These are the two most widely used CSS-in-JS libraries at the moment,
and between the two of them, they cover a lot of ground.

If you have any questions or thoughts on how to make this post better, please reach out to me on Twitter.