Blurred Borders in CSS

Say we want to target an element and just visually blur the border of it. There is no simple, single built-in web platform feature we can reach for. But we can get it done with a little CSS trickery.

Here’s what we’re after:

Screenshot of an element with a background image that shows oranges on a wooden table. The border of this element is blurred.
The desired result.

Let’s see how we can code this effect, how we can enhance it with rounded corners, extend support so it works cross-browser, what the future will bring in this department and what other interesting results we can get starting from the same idea!

Coding the basic blurred border

We start with an element on which we set some dummy dimensions, a partially transparent (just slightly visible) border and a background whose size is relative to the border-box, but whose visibility we restrict to the padding-box:

$b: 1.5em; // border-width div { border: solid $b rgba(#000, .2); height: 50vmin; max-width: 13em; max-height: 7em; background: url(oranges.jpg) 50%/ cover border-box /* background-origin */ padding-box /* background-clip */;

The box specified by background-origin is the box whose top left corner is the 0 0 point for background-position and also the box that background-size (set to cover in our case) is relative to. The box specified by background-clip is the box within whose limits the background is visible.

The initial values are padding-box for background-origin and border-box for background-clip, so we need to specify them both in this case.

If you need a more in-depth refresher on background-origin and background-clip, you can check out this detailed article on the topic.

The code above gives us the following result:

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we add an absolutely positioned pseudo-element that covers its entire parent’s border-box and is positioned behind (z-index: -1). We also make this pseudo-element inherit its parent’s border and background, then we change the border-color to transparent and the background-clip to border-box:

$b: 1.5em; // border-width div { position: relative; /* same styles as before */ &:before { position: absolute; z-index: -1; /* go outside padding-box by * a border-width ($b) in every direction */ top: -$b; right: -$b; bottom: -$b; left: -$b; border: inherit; border-color: transparent; background: inherit; background-clip: border-box; content: '' }

Now we can also see the background behind the barely visible border:

See the Pen by thebabydino (@thebabydino) on CodePen.

Alright, you may be seeing already where this is going! The next step is to blur() the pseudo-element. Since this pseudo-element is only visible only underneath the partially transparent border (the rest is covered by its parent’s padding-box-restricted background), it results the border area is the only area of the image we see blurred.

See the Pen by thebabydino (@thebabydino) on CodePen.

We’ve also brought the alpha of the element’s border-color down to .03 because we want the blurriness to be doing most of the job of highlighting where the border is.

This may look done, but there’s something I still don’t like: the edges of the pseudo-element are now blurred as well. So let’s fix that!

One convenient thing when it comes to the order browsers apply properties in is that filters are applied before clipping. While this is not what we want and makes us resort to inconvenient workarounds in a lot of other cases… right here, it proves to be really useful!

It means that, after blurring the pseudo-element, we can clip it to its border-box!

My preferred way of doing this is by setting clip-path to inset(0) because… it’s the simplest way of doing it, really! polygon(0 0, 100% 0, 100% 100%, 0 100%) would be overkill.

See the Pen by thebabydino (@thebabydino) on CodePen.

In case you’re wondering why not set the clip-path on the actual element instead of setting it on the :before pseudo-element, this is because setting clip-path on the element would make it a stacking context. This would force all its child elements (and consequently, its blurred :before pseudo-element as well) to be contained within it and, therefore, in front of its background. And then no nuclear z-index or !important could change that.

We can prettify this by adding some text with a nicer font, a box-shadow and some layout properties.

What if we have rounded corners?

The best thing about using inset() instead of polygon() for the clip-path is that inset() can also accommodate for any border-radius we may want!

And when I say any border-radius, I mean it! Check this out!

div { --r: 15% 75px 35vh 13vw/ 3em 5rem 29vmin 12.5vmax; border-radius: var(--r); /* same styles as before */ &:before { /* same styles as before */ border-radius: inherit; clip-path: inset(0 round var(--r)); }

It works like a charm!

See the Pen by thebabydino (@thebabydino) on CodePen.

Extending support

Some mobile browsers still need the -webkit- prefix for both filter and clip-path, so be sure to include those versions too. Note that they are included in the CodePen demos embeded here, even though I chose to skip them in the code presented in the body of this article.

Alright, but what if we need to support Edge? clip-path doesn’t work in Edge, but filter does, which means we do get the blurred border, but no sharp cut limits.

Well, if we don’t need corner rounding, we can use the deprecated clip property as a fallback. This means adding the following line right before the clip-path ones:

clip: rect(0 100% 100% 0)

And our demo now works in Edge… sort of! The right, bottom and left edges are cut sharply, but the top one still remains blurred (only in the Debug mode of the Pen, all seems fine for the iframe in the Editor View). And opening DevTools or right clicking in the Edge window or clicking anywhere outside this window makes the effect of this property vanish. Bug of the month right there!

Alright, since this is so unreliable and it doesn’t even help us if we want rounded corners, let’s try another approach!

This is a bit like scratching behind the left ear with the right foot (or the other way around, depending on which side is your more flexible one), but it’s the only way I can think of to make it work in Edge.

Some of you may have already been screaming at the screen something like “but Ana… overflow: hidden!” and yes, that’s what we’re going for now. I’ve avoided it initially because of the way it works: it cuts out all descendant content outside the padding-box. Not outside the border-box, as we’ve done by clipping!

This means we need to ditch the real border and emulate it with padding, which I’m not exactly delighted about because it can lead to more complications, but let’s take it one step at a time!

As far as code changes are concerned, the first thing we do is remove all border-related properties and set the border-width value as the padding. We then set overflow: hidden and restrict the background of the actual element to the content-box. Finally, we reset the pseudo-element’s background-clip to the padding-box value and zero its offsets.

$fake-b: 1.5em; // fake border-width div { /* same styles as before */ overflow: hidden; padding: $fake-b; background: url(oranges.jpg) 50%/ cover padding-box /* background-origin */ content-box /* background-clip */; &:before { /* same styles as before */ top: 0; right: 0; bottom: 0; left: 0; background: inherit; background-clip: padding-box; }

See the Pen by thebabydino (@thebabydino) on CodePen.

If we want that barely visible “border” overlay, we need another background layer on the actual element:

$fake-b: 1.5em; // fake border-width
$c: rgba(#000, .03); div { /* same styles as before */ overflow: hidden; padding: $fake-b; --img: url(oranges.jpg) 50%/ cover; background: var(--img) padding-box /* background-origin */ content-box /* background-clip */, linear-gradient($c, $c); &:before { /* same styles as before */ top: 0; right: 0; bottom: 0; left: 0; background: var(--img); }

See the Pen by thebabydino (@thebabydino) on CodePen.

We can also add rounded corners with no hassle:

See the Pen by thebabydino (@thebabydino) on CodePen.

So why didn’t we do this from the very beginning?!

Remember when I said a bit earlier that not using an actual border can complicate things later on?

Well, let’s say we want to have some text. With the first method, using an actual border and clip-path, all it takes to prevent the text content from touching the blurred border is adding a padding (of let’s say 1em) on our element.

See the Pen by thebabydino (@thebabydino) on CodePen.

But with the overflow: hidden method, we’ve already used the padding property to create the blurred “border”. Increasing its value doesn’t help because it only increases the fake border’s width.

We could add the text into a child element. Or we could also use the :after pseudo-element!

The way this works is pretty similar to the first method, with the :after replacing the actual element. The difference is we clip the blurred edges with overflow: hidden instead of clip-path: inset(0) and the padding on the actual element is the pseudos’ border-width ($b) plus whatever padding value we want:

$b: 1.5em; // border-width div { overflow: hidden; position: relative; padding: calc(1em + #{$b}); /* prettifying styles */ &:before, &:after { position: absolute; z-index: -1; /* put them *behind* parent */ /* zero all offsets */ top: 0; right: 0; bottom: 0; left: 0; border: solid $b rgba(#000, .03); background: url(oranges.jpg) 50%/ cover border-box /* background-origin */ padding-box /* background-clip */; content: '' } &:before { border-color: transparent; background-clip: border-box; filter: blur(9px); }

See the Pen by thebabydino (@thebabydino) on CodePen.

What about having both text and some pretty extreme rounded corners? Well, that’s something we’ll discuss in another article – stay tuned!

What about backdrop-filter?

Some of you may be wondering (as I was when I started toying with various ideas in order to try to achieve this effect) whether backdrop-filter isn’t an option.

Well, yes and no!

Technically, it is possible to get the same effect, but since Firefox doesn’t yet implement it, we’re cutting out Firefox support if we choose to take this route. Not to mention this approach also forces us to use both pseudo-elements if we want the best support possible for the case when our element has some text content (which means we need the pseudos and their padding-box area background to show underneath this text).

For those who don’t yet know what backdrop-filter does: it filters out what can be seen through the (partially) transparent parts of the element we apply it on.

The way we need to go about this is the following: both pseudo-elements have a transparent border and a background positioned and sized relative to the padding-box. We restrict the background of pseudo-element on top (the :after) to the padding-box.

Now the :after doesn’t have a background in the border area anymore and we can see through to the :before pseudo-element behind it there. We set a backdrop-filter on the :after and maybe even change that border-color from transparent to slightly visible. The bottom (:before) pseudo-element’s background that’s still visible through the (partially) transparent, barely distinguishable border of the :after above gets blurred as a result of applying the backdrop-filter.

$b: 1.5em; // border-width div { overflow: hidden; position: relative; padding: calc(1em + #{$b}); /* prettifying styles */ &:before, &:after { position: absolute; z-index: -1; /* put them *behind* parent */ /* zero all offsets */ top: 0; right: 0; bottom: 0; left: 0; border: solid $b transparent; background: $url 50%/ cover /* background-origin & -clip */ border-box; content: '' } &:after { border-color: rgba(#000, .03); background-clip: padding-box; backdrop-filter: blur(9px); /* no Firefox support */ }

Remember that the live demo for this doesn’t currently work in Firefox and needs the Experimental Web Platform features flag enabled in chrome://flags in order to work in Chrome.

Eliminating one pseudo-element

This is something I wouldn’t recommend doing in the wild because it cuts out Edge support as well, but we do have a way of achieving the result we want with just one pseudo-element.

We start by setting the image background on the element (we don’t really need to explicitly set a border as long as we include its width in the padding) and then a partially transparent, barely visible background on the absolutely positioned pseudo-element that’s covering its entire parent. We also set the backdrop-filter on this pseudo-element.

$b: 1.5em; // border-width div { position: relative; padding: calc(1em + #{$b}); background: url(oranges.jpg) 50%/ cover; /* prettifying styles */ &:before { position: absolute; /* zero all offsets */ top: 0; right: 0; bottom: 0; left: 0; background: rgba(#000, .03); backdrop-filter: blur(9px); /* no Firefox support */ content: '' }

Alright, but this blurs out the entire element behind the almost transparent pseudo-element, including its text. And it’s no bug, this is what backdrop-filter is supposed to do.

The problem at hand.

In order to fix this, we need to get rid of (not make transparent, that’s completely useless in this case) the inner rectangle (whose edges are a distance $b away from the border-box edges) of the pseudo-element.

We have two ways of doing this.

The first way (live demo) is with clip-path and the zero-width tunnel technique:

$b: 1.5em; // border-width
$o: calc(100% - #{$b}); div { /* same styles as before */ &:before { /* same styles as before */ /* doesn't work in Edge */ clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 0 0, #{$b $b}, #{$b $o}, #{$o $o}, #{$o $b}, #{$b $b}); }

The second way (live demo) is with two composited mask layers (note that, in this case, we need to explicitly set a border on our pseudo):

$b: 1.5em; // border-width div { /* same styles as before */ &:before { /* same styles as before */ border: solid $b transparent; /* doesn't work in Edge */ --fill: linear-gradient(red, red); -webkit-mask: var(--fill) padding-box, var(--fill); -webkit-mask-composite: xor; mask: var(--fill) padding-box exclude, var(--fill); }

Since neither of these two properties works in Edge, this means support is now limited to WebKit browsers (and we still need to enable the Experimental Web Platform features flag for backdrop-filter to work in Chrome).

Future (and better!) solution

This is not implemented by any browser at this point, but the spec mentions a filter() function that will allow us to apply filters on individual background layers. This would eliminate the need for a pseudo-element and would reduce the code needed to achieve this effect to two CSS declarations!

border: solid 1.5em rgba(#000, .03);
background: $url border-box /* background-origin */ padding-box /* background-clip */, filter($url, blur(9px)) /* background-origin & background-clip */ border-box

If you think this is something useful to have, you can add your use cases and track implementation progress for both Chrome and Firefox.

More border filter options

I’ve only talked about blurring the border up to now, but this technique works for pretty much any CSS filter (save for drop-shadow() which wouldn’t make much sense in this context). You can play with switching between them and tweaking values in the interactive demo below:

See the Pen by thebabydino (@thebabydino) on CodePen.

And all we’ve done so far has used just one filter function, but we can also chain them and then the possibilities are endless – what cool effects can you come up with this way?

See the Pen by thebabydino (@thebabydino) on CodePen.

The post Blurred Borders in CSS appeared first on CSS-Tricks.

How To Build An Endless Runner Game In Virtual Reality (Part 3)

How To Build An Endless Runner Game In Virtual Reality (Part 3)

How To Build An Endless Runner Game In Virtual Reality (Part 3)

Alvin Wan

And so our journey continues. In this final part of my series on how to build an endless runner VR game, I’ll show you how you can synchronize the game state between two devices which will move you one step closer to building a multiplayer game. I’ll specifically introduce MirrorVR which is responsible for handling the mediating server in client-to-client communication.

Note: This game can be played with or without a VR headset. You can view a demo of the final product at

To get started, you will need the following.

  • Internet access (specifically to;
  • A Glitch project completed from part 2 of this tutorial. You can start from the part 2 finished product by navigating to!/ergo-2 and clicking “Remix to edit”;
  • A virtual reality headset (optional, recommended). (I use Google Cardboard, which is offered at $15 a piece.)

Step 1: Display Score

The game as-is functions at a bare minimum, where the player is given a challenge: avoid the obstacles. However, outside of object collisions, the game does not provide feedback to the player regarding progress in the game. To remedy this, you will implement the score display in this step. The score will be large text object placed in our virtual reality world, as opposed to an interface glued to the user’s field of view.

In virtual reality generally, the user interface is best integrated into the world rather than stuck to the user’s head.

Score display
Score display (Large preview)

Start by adding the object to index.html. Add a text mixin, which will be reused for other text elements:

<a-assets> ... <a-mixin id="text" text=" font:exo2bold; anchor:center; align:center;"></a-mixin> ...

Next, add a text element to the platform, right before the player:

<!-- Score -->
<a-text id="score" value="" mixin="text" height="40" width="40" position="0 1.2 -3" opacity="0.75"></a-text> <!-- Player -->

This adds a text entity to the virtual reality scene. The text is not currently visible, because its value is set to empty. However, you will now populate the text entity dynamically, using JavaScript. Navigate to assets/ergo.js. After the collisions section, add a score section, and define a number of global variables:

  • score: the current game score.
  • countedTrees: IDs of all trees that are included in the score. (This is because collision tests may trigger multiple times for the same tree.)
  • scoreDisplay: reference to the DOM object, corresponding to a text object in the virtual reality world.
/********* * SCORE * *********/ var score;
var countedTrees;
var scoreDisplay;

Next, define a setup function to initialize our global variables. In the same vein, define a teardown function.

var scoreDisplay; function setupScore() { score = 0; countedTrees = new Set(); scoreDisplay = document.getElementById('score');
} function teardownScore() { scoreDisplay.setAttribute('value', '');

In the Game section, update gameOver, startGame, and window.onload to include score setup and teardown.

/******** * GAME * ********/ function gameOver() { ... teardownScore();
} function startGame() { ... setupScore(); addTreesRandomlyLoop();
} window.onload = function() { setupScore(); ...

Define a function that increments the score for a particular tree. This function will check against countedTrees to ensure that the tree is not double counted.

function addScoreForTree(tree_id) { if (countedTrees.has(tree_id)) return; score += 1; countedTrees.add(tree_id);

Additionally, add a utility to update the score display using the global variable.

function updateScoreDisplay() { scoreDisplay.setAttribute('value', score);

Update the collision testing accordingly in order to invoke this score-incrementing function whenever an obstacle has passed the player. Still in assets/ergo.js, navigate to the collisions section. Add the following check and update.

AFRAME.registerComponent('player', { tick: function() { document.querySelectorAll('.tree').forEach(function(tree) { ... if (position.z > POSITION_Z_LINE_END) { addScoreForTree(tree_id); updateScoreDisplay(); } }) }

Finally, update the score display as soon as the game starts. Navigate to the Game section, and add updateScoreDisplay(); to startGame:

function startGame() { ... setupScore(); updateScoreDisplay(); ...

Ensure that assets/ergo.js and index.html match the corresponding source code files. Then, navigate to your preview. You should see the following:

Score display (Large preview)

This concludes the score display. Next, we will add proper start and Game Over menus, so that the player can replay the game as desired.

Step 2: Add Start Menu

Now that the user can keep track of the progress, you will add finishing touches to complete the game experience. In this step, you will add a Start menu and a Game Over menu, letting the user start and restart games.

Let’s begin with the Start menu where the player clicks a “Start” button to begin the game. For the second half of this step, you will add a Game Over menu, with a “Restart” button:

Start and game over menus (Large preview)

Navigate to index.html in your editor. Then, find the Mixins section. Here, append the title mixin, which defines styles for particularly large text. We use the same font as before, align text to the center, and define a size appropriate for the type of text. (Note below that anchor is where a text object is anchored to its position.)

<a-assets> ... <a-mixin id="title" text=" font:exo2bold; height:40; width:40; opacity:0.75; anchor:center; align:center;"></a-mixin>

Next, add a second mixin for secondary headings. This text is slightly smaller but is otherwise identical to the title.

<a-assets> ... <a-mixin id="heading" text=" font:exo2bold; height:10; width:10; opacity:0.75; anchor:center; align:center;"></a-mixin>

For the third and final mixin, define properties for descriptive text — even smaller than secondary headings.

<a-assets> ... <a-mixin id="copy" text=" font:exo2bold; height:5; width:5; opacity:0.75; anchor:center; align:center;"></a-mixin>

With all text styles defined, you will now define the in-world text objects. Add a new Menus section beneath the Score section, with an empty container for the Start menu:

<!-- Score -->
... <!-- Menus -->
<a-entity id="menu-container"> <a-entity id="start-menu" position="0 1.1 -3"> </a-entity>

Inside the start menu container, define the title and a container for all non-title text:

... <a-entity id="start-menu" ...> <a-entity id="start-copy" position="0 1 0"> </a-entity> <a-text value="ERGO" mixin="title"></a-text> </a-entity>

Inside the container for non-title text, add instructions for playing the game:

<a-entity id="start-copy"...> <a-text value="Turn left and right to move your player, and avoid the trees!" mixin="copy"></a-text>

To complete the Start menu, add a button that reads “Start”:

<a-entity id="start-copy"...> ... <a-text value="Start" position="0 0.75 0" mixin="heading"></a-text> <a-box id="start-button" position="0 0.65 -0.05" width="1.5" height="0.6" depth="0.1"></a-box>

Double-check that your Start menu HTML code matches the following:

<!-- Menus -->
<a-entity id="menu-container"> <a-entity id="start-menu" position="0 1.1 -3"> <a-entity id="start-copy" position="0 1 0"> <a-text value="Turn left and right to move your player, and avoid the trees!" mixin="copy"></a-text> <a-text value="Start" position="0 0.75 0" mixin="heading"></a-text> <a-box id="start-button" position="0 0.65 -0.05" width="1.5" height="0.6" depth="0.1"></a-box> </a-entity> <a-text value="ERGO" mixin="title"></a-text> </a-entity>

Navigate to your preview, and you will see the following Start menu:

Image of Start menu
Start menu (Large preview)

Still in the Menus section (directly beneath the start menu), add the game-over menu using the same mixins:

<!-- Menus -->
<a-entity id="menu-container"> ... <a-entity id="game-over" position="0 1.1 -3"> <a-text value="?" mixin="heading" id="game-score" position="0 1.7 0"></a-text> <a-text value="Score" mixin="copy" position="0 1.2 0"></a-text> <a-entity id="game-over-copy"> <a-text value="Restart" mixin="heading" position="0 0.7 0"></a-text> <a-box id="restart-button" position="0 0.6 -0.05" width="2" height="0.6" depth="0.1"></a-box> </a-entity> <a-text value="Game Over" mixin="title"></a-text> </a-entity>

Navigate to your JavaScript file, assets/ergo.js. Create a new Menus section before the Game section. Additionally, define three empty functions: setupAllMenus, hideAllMenus, and showGameOverMenu.

/******** * MENU * ********/ function setupAllMenus() {
} function hideAllMenus() {
} function showGameOverMenu() {
} /******** * GAME * ********/

Next, update the Game section in three places. In gameOver, show the Game Over menu:

function gameOver() { ... showGameOverMenu();
``` In `startGame`, hide all menus: ```
function startGame() { ... hideAllMenus();

Next, in window.onload, remove the direct invocation to startGame and instead call setupAllMenus. Update your listener to match the following:

window.onload = function() { setupAllMenus(); setupScore(); setupTrees();

Navigate back to the Menu section. Save references to various DOM objects:

/******** * MENU * ********/ var menuStart;
var menuGameOver;
var menuContainer;
var isGameRunning = false;
var startButton;
var restartButton; function setupAllMenus() { menuStart = document.getElementById('start-menu'); menuGameOver = document.getElementById('game-over'); menuContainer = document.getElementById('menu-container'); startButton = document.getElementById('start-button'); restartButton = document.getElementById('restart-button');

Next, bind both the “Start” and “Restart” buttons to startGame:

function setupAllMenus() { ... startButton.addEventListener('click', startGame); restartButton.addEventListener('click', startGame);

Define showStartMenu and invoke it from setupAllMenus:

function setupAllMenus() { ... showStartMenu();
} function hideAllMenus() {
} function showGameOverMenu() {
} function showStartMenu() {

To populate the three empty functions, you will need a few helper functions. Define the following two functions, which accepts a DOM element representing an A-Frame VR entity and shows or hides it. Define both functions above showAllMenus:

var restartButton; function hideEntity(el) { el.setAttribute('visible', false);
} function showEntity(el) { el.setAttribute('visible', true);
} function showAllMenus() {

First populate hideAllMenus. You will remove the objects from sight, then remove click listeners for both menus:

function hideAllMenus() { hideEntity(menuContainer); startButton.classList.remove('clickable'); restartButton.classList.remove('clickable');

Second, populate showGameOverMenu. Here, restore the container for both menus, as well as the Game Over menu and the ‘Restart’ button’s click listener. However, remove the ‘Start’ button’s click listener, and hide the ‘Start’ menu.

function showGameOverMenu() { showEntity(menuContainer); hideEntity(menuStart); showEntity(menuGameOver); startButton.classList.remove('clickable'); restartButton.classList.add('clickable');

Third, populate showStartMenu. Here, reverse all changes that the showGameOverMenu effected.

function showStartMenu() { showEntity(menuContainer); hideEntity(menuGameOver); showEntity(menuStart); startButton.classList.add('clickable'); restartButton.classList.remove('clickable');

Double-check that your code matches the corresponding source files. Then, navigate to your preview, and you will observe the following behavior:

Start and Game Over menus (Large preview)

This concludes the Start and Game Over menus.

Congratulations! You now have a fully functioning game with a proper start and proper end. However, we have one more step left in this tutorial: We need to synchronize the game state between different player devices. This will move us one step closer towards multiplayer games.

Step 3: Synchronizing Game State With MirrorVR

In a previous tutorial, you learned how to send real-time information across sockets, to facilitate one-way communication between a server and a client. In this step, you will build on top of a fully-fledged product of that tutorial, MirrorVR, which handles the mediating server in client-to-client communication.

Note: You can learn more about MirrorVR here.

Navigate to index.html. Here, we will load MirrorVR and add a component to the camera, indicating that it should mirror a mobile device’s view where applicable. Import the dependency and MirrorVR 0.2.3.

Next, add a component, camera-listener, to the camera:

<a-camera camera-listener ...>

Navigate to assets/ergo.js. In this step, the mobile device will send commands, and the desktop device will only mirror the mobile device.

To facilitate this, you need a utility to distinguish between desktop and mobile devices. At the end of your file, add a mobileCheck function after shuffle:

/** * Checks for mobile and tablet platforms. */
function mobileCheck() { var check = false; (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|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|xda|xiino|android|ipad|playbook|silk/i.test(a)||/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.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); return check;

First, we will synchronize the game start. In startGame, of the Game section, add a mirrorVR notification at the end.

function startGame() { ... if (mobileCheck()) { mirrorVR.notify('startGame', {}) }

The mobile client now sends notifications about a game starting. You will now implement the desktop’s response.

In the window load listener, invoke a setupMirrorVR function:

window.onload = function() { ... setupMirrorVR();

Define a new section above the Game section for the MirrorVR setup:

/************ * MirrorVR * ************/ function setupMirrorVR() { mirrorVR.init();

Next, add keyword arguments to the initialization function for mirrorVR. Specifically, we will define the handler for game start notifications. We will additionally specify a room ID; this ensures that anyone loading your application is immediately synchronized.

function setupMirrorVR() { mirrorVR.init({ roomId: 'ergo', state: { startGame: { onNotify: function(data) { hideAllMenus(); setupScore(); updateScoreDisplay(); } }, } });

Repeat the same synchronization process for Game Over. In gameOver in the Game section, add a check for mobile devices and send a notification accordingly:

function gameOver() { ... if (mobileCheck()) { mirrorVR.notify('gameOver', {}); }

Navigate to the MirrorVR section and update the keyword arguments with a gameOver listener:

function setupMirrorVR() { mirrorVR.init({ state: { startGame: {... }, gameOver: { onNotify: function(data) { gameOver(); } }, } }) }

Next, repeat the same synchronization process for the addition of trees. Navigate to addTreesRandomly in the Trees section. Keep track of which lanes receive new trees. Then, directly before the return directive, and send a notification accordingly:

function addTreesRandomly(...) { ... var numberOfTreesAdded ... var position_indices = []; trees.forEach(function (tree) { if (...) { ... position_indices.push(tree.position_index); } }); if (mobileCheck()) { mirrorVR.notify('addTrees', position_indices); } return ...

Navigate to the MirrorVR section, and update the keyword arguments to mirrorVR.init with a new listener for trees:

function setupMirrorVR() { mirrorVR.init({ state: { ... gameOver: {... }, addTrees: { onNotify: function(position_indices) { position_indices.forEach(addTreeTo) } }, } }) }

Finally, we synchronize the game score. In updateScoreDisplay from the Score section, send a notification when applicable:

function updateScoreDisplay() { ... if (mobileCheck()) { mirrorVR.notify('score', score); }

Update the mirrorVR initialization for the last time, with a listener for score changes:

function setupMirrorVR() { mirrorVR.init({ state: { addTrees: { }, score: { onNotify: function(data) { score = data; updateScoreDisplay(); } } } });

Double-check that your code matches the appropriate source code files for this step. Then, navigate to your desktop preview. Additionally, open up the same URL on your mobile device. As soon as your mobile device loads the webpage, your desktop should immediately start mirroring the mobile device’s game.

Here is a demo. Notice that the desktop cursor is not moving, indicating the mobile device is controlling the desktop preview.

Final result of the endless runner game with MirrorVR game state synchronization (Large preview)

This concludes your augmented project with mirrorVR.

This third step introduced a few basic game state synchronization steps; to make this more robust, you could add more sanity checks and more points of synchronization.


In this tutorial, you added finishing touches to your endless runner game and implemented real-time synchronization of a desktop client with a mobile client, effectively mirroring the mobile device’s screen on your desktop. This concludes the series on building an endless runner game in virtual reality. Along with A-Frame VR techniques, you’ve picked up 3D modeling, client-to-client communication, and other widely applicable concepts.

Next steps can include:

  • More Advanced Modeling
    This means more realistic 3D models, potentially created in a third-party software and imported. For example, (MagicaVoxel) makes creating voxel art simple, and (Blender) is a complete 3D modeling solution.
  • More Complexity
    More complex games, such as a real-time strategy game, could leverage a third-party engine for increased efficiency. This may mean sidestepping A-Frame and webVR entirely, instead publishing a compiled (Unity3d) game.

Other avenues include multiplayer support and richer graphics. With the conclusion of this tutorial series, you now have a framework to explore further.

Smashing Editorial (rb, ra, il)
7 Spring Cleaning Essentials for Web Designers

It’s the first day of Spring! As you look to clean up other parts of your life (e.g. your home, your refrigerator, your yard) make the cleanup of your web design business a priority as well.

If you’re anything like me, you set aside time later in the week or month, promising yourself that you’ll finally take care of “business stuff”. And if you’re also like me, you often have to postpone those business maintenance tasks because new paid work opportunities come in. (Or you’re just exhausted and want a break from looking at your screen.)

But there’s no time like the present, so if you can spare it, give yourself at least one day off from work to tackle this spring cleaning checklist. Not only will it give you time to zero in on the areas that often go neglected in your business, but you’ll come out of it feeling refreshed and ready to get back to work.

1. Clean Your Workspace

There are some people that thrive in organized chaos. However, if your workspace is piled high with stuff you don’t need, stuff that distracts you, or stuff that’s literally getting in your way as you try to work on your computer, you need to clean your physical workspace.

When you’re done, think about doing something new for your workspace, something that makes you feel excited about sitting down to work. A new piece of artwork over your desk? A book about web design you’ve been meaning to read? A postcard from a client thanking you for a job well done? Then, put it somewhere that you’ll see it every day.

This is what I’ve done with my own workspace:

2. Declutter the Desktop

It doesn’t matter how many folders you put on your desktop to keep things organized. Image files, templates, PDFs, workflow documentation — these loose documents and folders sitting on your desktop are a distraction. Worse, if you don’t move them off of it, you’re putting your business at risk for data loss (if you’re not otherwise backing it all up).

To keep your desktop clutter-free, give your files and folders a new home — one that’s in a secure, cloud-based organizational system. Google Drive and Dropbox are free to start with and easy to use.

3. Review Your Folders

If you’re still storing files on your computer (and not just your desktop), now is the time to migrate them to your cloud storage. Then, once you have all files in a centralized location (which is also great for security and collaboration purposes), work on refining your folder structure:

  • Delete old client files and folders;
  • Delete old business documentation or update it so that it’s reflective of what you do now;
  • Check for files containing the same information. Remove the duplicates;
  • Review and rename folders for improved clarity and organization;
  • Review your bookmarks. Delete ones you don’t use. Update incorrect links. Organize links based on how frequently they’re used. And create a file structure to organize it all.

Here’s an example of what I’ve done to my own bookmarks bar to improve my workflow:

4. Check on the State of Your Website

You’re in the business of designing websites for others, but when was the last time you took care of your own?

Start with a site health check:

  • Is all of the software updated? If not, back your site up and run those updates now;
  • How about security, your security plugin should have a scanner, run it and make sure all is well;
  • Does the site load as quickly as it could? If you’re unsure, run it through PageSpeed Insights to see if Google suggests any fixes.

Then, check on the content of your website:

  • Hold old is the design, if it’s over a year old, it at least needs some upgrades;
  • How old is the information about your business, if anything has changed, take care of it now;
  • Is any of the content or images no longer relevant, remove them if that’s the case;
  • How about your links, do all internal links still work, do external links point to recent and relevant sources.

Finally, spend some time updating your portfolio. Even if it’s only been a few months, you may have new samples or testimonials to show off. Don’t let outdated website samples reflect poorly on what you can do.

5. Freshen up Your Social Presence

WebDesignerDepot recently published a post discussing all the ways social media might be hurting your design business. Run through the checklist to see if you can do any cleanup and salvage your social presence.

6. Create Less Email Work

One of the things you’ll learn if you read the 4-Hour Workweek is that email is a huge time waster. Sure, you need it to communicate with clients, but what percentage of emails in your inbox are actually from clients? And how many of those client emails are actually urgent enough to warrant your attention right now?

What you need to do is create a new system for your email so that it stops distracting you from work. Here are some ways to do that:

  • Unsubscribe from all unnecessary subscriptions;
  • Review and update your email folder structure to keep everything well-organized;
  • Use filters so that client emails immediately drop into their corresponding folders;
  • Use a tool like Boomerang for Gmail. It turns off email notifications on your phone and computer, so you can stay focused during the workday.

Another thing to look at is your email signature. Here’s an example of how mine currently looks (you can also see the Boomerang “Send Later” buttons in action):

If you had to update any of your business or contact information on your site, the same applies here. Now is also a good opportunity to add links that help keep prospects and clients connected — like social media links, your website link, or a meeting scheduler.

7. Review Software Subscriptions

Finally, take a look at your software subscriptions. Is there anything you’ve signed up for in the past that you’re no longer using? How about tools that used to work really well, but that seem to do nothing but slow you down now?

Automation is crucial for web designers. Just make sure you’re using tools that actually enhance your workflow.

WebDesignerDepot has a fantastic roundup of 30 tools and services that help with this. You obviously won’t need or want to use all of them. However, if you’re trying to figure out if that tool you use now is worth it, this is a great reference to confirm your suspicions or find an alternative option.

Spring Cleaning Bonus

Once you’ve finished spring cleaning your business, it’s time to get back to thinking about revenue generation.

So, why not create a spring cleaning service or promotional offer for clients? It shouldn’t even be that hard to do since you probably provided this service in the past, just under a different name: a website redesign.

Just reach out to clients you haven’t spoken to in a year or so, and pitch them a “Spring Cleaning” website audit and cleanup for a flat fee. You can also do the same with brand new clients whose websites are certainly in need of a good scrub.


Featured image via Unsplash

Add Realistic Chalk and Sketch Lettering Effects with Sketch’it – only $5!

p img {display:inline-block; margin-right:10px;}
.alignleft {float:left;}
p.showcase {clear:both;}
body#browserfriendly p, body#podcast p, div#emailbody p{margin:0;}

Using Local with Flywheel

Have you seen Local by Flywheel? It’s a native app for helping set up local WordPress developer environments. I absolutely love it and use it to do all my local WordPress development work. It brings a lovingly designed GUI to highly technical tasks in a way that I think works very well. Plus it just works, which wins all the awards with me. Need to spin up a new site locally? Click a few buttons. Working on your site? All your sites are right there and you can flip them on with the flick of a toggle.

Local by Flywheel is useful no matter where your WordPress production site is hosted. But it really shines when paired with Flywheel itself, which is fabulous WordPress hosting that has all the same graceful combination of power and ease as Local does.

Just recently, we moved over to Local and it couldn’t have been easier.

Running locally.

Setting up a new local site (which you would do even if it’s a long-standing site and you’re just getting it set up on Flywheel) is just a few clicks. That’s one of the most satisfying parts. You know all kinds of complex things are happening behind the scenes, like containers being spun up, proper software being installed, etc, but you don’t have to worry about any of it.

(Local is free, by the way.)

The Cross-platform-ness is nice.

I work on ShopTalk with Dave Rupert, who’s on Windows. Not a problem. Local works on Windows also, so Dave can spin up site in the exact same way I can.

Setting up Flywheel hosting is just as clean and easy as Local is.

If you’ve used Local, you’ll recognize the clean font, colors, and design when using the Flywheel website to get your hosting set up. Just a few clicks and I had that going:

Things that are known to be a pain the butt are painless on Local, like making sure SSL (HTTPS) is active and a CDN is helping with assets.

You get a subdomain to start, so you can make sure your site is working perfectly before pointing a production domain at it.

I didn’t just have to put files into place on the new hosting, move the database, and cross my fingers I did it all right when re-pointing the DNS. I could get the site up and running at the subdomain first, make sure it is, then do the DNS part.

But the moving of files and all that… it’s trivial because of Local!

The best part is that shooting a site up to Flywheel from Local is also just a click away.

All the files and the database head right up after you’ve connected Local to Flywheel.

All I did was make sure I had my local site to be a 100% perfect copy of production. All the theme and plugins and stuff were already that way because I was already doing local development, and I pulled the entire database down easily with WP DB Migrate Pro.

I think I went from “I should get around to setting up this site on Flywheel.” do “Well that’s done.” in less than an hour. Now Dave and I both have a local development environment and a path to production.

The post Using Local with Flywheel appeared first on CSS-Tricks.

I Used The Web For A Day On Internet Explorer 8

I Used The Web For A Day On Internet Explorer 8

I Used The Web For A Day On Internet Explorer 8

Chris Ashton

This article is part of a series in which I attempt to use the web under various constraints, representing a given demographic of user. I hope to raise the profile of difficulties faced by real people, which are avoidable if we design and develop in a way that is sympathetic to their needs.

Last time, I navigated the web for a day using a screen reader. This time, I spent the day using Internet Explorer 8, which was released ten years ago today, on March 19th, 2009.

Who In The World Uses IE8?

Before we start; a disclaimer: I am not about to tell you that you need to start supporting IE8.

There’s every reason to not support IE8. Microsoft officially stopped supporting IE8, IE9 and IE10 over three years ago, and the Microsoft executives are even telling you to stop using Internet Explorer 11.

But as much as we developers hope for it to go away, it just. Won’t. Die. IE8 continues to show up in browser stats, especially outside of the bubble of the Western world.

Browser stats have to be taken with a pinch of salt, but current estimates for IE8 usage worldwide are around 0.3% to 0.4% of the desktop market share. The lower end of the estimate comes from w3counter:

Graph of IE8 usage over time
From a peak of almost 30% at the end of 2010, W3Counter now believes IE8 accounts for 0.3% of global usage. (Large preview)

The higher estimate comes from StatCounter (the same data feed used by the “Can I use” usage table). It estimates global IE8 desktop browser proportion to be around 0.37%.

Graph of IE8 usage vs other browsers
Worldwide usage of IE8 is at 0.37% according to StatCounter. (Large preview)

I suspected we might see higher IE8 usage in certain geographical regions, so drilled into the data by continent.

IE8 Usage By Region

Here is the per-continent IE8 desktop proportion (data from February 2018 — January 2019):

3.South America0.30%
4.North America0.35%

Someone in Asia is five times more likely to be using IE8 than someone in Oceania.

I looked more closely into the Asian stats, noting the proportion of IE8 usage for each country. There’s a very clear top six countries for IE8 usage, after which the figures drop down to be comparable with the world average:

3.North Korea1.38%

This data is summarized in the map below:

Graph showing IE8 breakdown in Asia
Iran, Turkmenistan and Afghanistan in the Middle East, and China, North Korea & Cambodia in the Far East stand out for their IE8 usage. (Large preview)

Incredibly, IE8 makes up around 4% of desktop users in Iran — forty times the proportion of IE8 users in Oceania.

Next, I looked at the country stats for Africa, as it had around the same overall IE8 usage as Asia. There was a clear winner (Eritrea), followed by a number of countries above or around the 1% usage mark:

3.Sudan & South Sudan1.33%
8.Democratic Republic of the Congo1.07%

This is summarized in the map below:

Graph showing IE8 breakdown in Africa
Eritrea stands out for its IE8 usage (3.24%). A number of other countries also have >1% usage. (Large preview)

Whereas the countries in Asia that have higher-than-normal IE8 usage are roughly batched together geographically, there doesn’t appear to be a pattern in Africa. The only pattern I can see — unless it’s a coincidence — is that a number of the world’s largest IE8 using countries famously censor internet access, and therefore probably don’t encourage or allow updating to more secure browsers.

If your site is aimed at a purely Western audience, you’re unlikely to care much about IE8. If, however, you have a burgeoning Asian or African market — and particularly if you care about users in China, Iran or Eritrea — you might very well care about your website’s IE8 experience. Yes — even in 2019!

Who’s Still Using IE?

So, who are these people? Do they really walk among us?!

Whoever they are, you can bet they’re not using an old browser just to annoy you. Nobody deliberately chooses a worse browsing experience.

Someone might be using an old browser due to the following reasons:

  • Lack of awareness
    They simply aren’t aware that they’re using outdated technology.
  • Lack of education
    They don’t know the upgrade options and alternative browsers open to them.
  • Lack of planning
    Dismissing upgrade prompts because they’re busy, but not having the foresight to upgrade during quieter periods.
  • Aversion to change
    The last time they upgraded their software, they had to learn a new UI. “If it ain’t broke, don’t fix it.”
  • Aversion to risk
    The last time they upgraded, their machine slowed to a crawl, or they lost their favorite feature.
  • Software limitation
    Their OS is too old to let them upgrade, or their admin privileges may be locked down.
  • Hardware limitation
    Newer browsers are generally more demanding of your hard disk space, memory and CPU.
  • Network limitation
    A capped data allowance or slow connection mean they don’t want to download 75MB of software.
  • Legal limitation
    They might be on a corporate machine that only condones the use of one specific browser.

Is it really such a surprise that there are still people around the world who are clinging to IE8?

I decided to put myself in the shoes of one of these anonymous souls, and browse the web for a day using IE8. You can play along at home! Download an “IE8 on Windows 7” Virtual Machine from the Microsoft website, then run it in a virtualizer like VirtualBox.

IE8 VM: Off To A Bad Start

I booted up my IE8 VM, clicked on the Internet Explorer program in anticipation, and this is what I saw:

Screenshot of default homepage of IE8 not loading
The first thing I saw was a 404. Great. (Large preview)

Hmm, okay. Looks like the default web page pulled up by IE8 no longer exists. Well, that figures. Microsoft has officially stopped supporting IE8 so why should it make sure the IE8 landing page still works?

I decided to switch to the most widely used site in the world.


Screenshot of
The Google homepage renders fine in IE8. (Large preview)

It’s a simple site, therefore difficult to get wrong — but to be fair, it’s looking great! I tried searching for something:

Screenshot of Google search results for Impractical Jokers
Those who have read my previous articles may notice a recurring theme here. (Large preview)

The search worked fine, though the layout looks a bit different to what I’m used to. Then I remembered — I’d seen the same search result layout when I used the Internet for a day with JavaScript turned off.

For reference, here is how the search results look in a modern browser with JavaScript enabled:

Screenshot of Google Chrome search results for Impractical Jokers
Cleaner layout, extra images and meta information, Netflix/Twitter integration. (Large preview)

So, it looks like IE8 gets the no-JS version of Google search. I don’t think this was necessarily a deliberate design decision — it could just be that the JavaScript errored out:

Screenshot of Google search error “Object doesn’t support this property or method”
The page tried and failed to run JavaScript. (Large preview)

Still, the end result is fine by me — I got my search results, which is all I wanted.

I clicked through to watch a YouTube video.


Screenshot of buggy YouTube video page
Funky logo, no images for related videos, and unsurprisingly, no video. (Large preview)

There’s quite a lot broken about this page. All to do with little quirks in IE.

The logo, for instance, is zoomed in and cropped. This is down to IE8 not supporting SVG, and what we’re actually seeing is the fallback option provided by YouTube. They’ve applied a background-image CSS property so that in the event of no SVG support, you’ll get an attempt at displaying the logo. Only they seem to have not set the background-size properly, so it’s a little too far zoomed in.

Screenshot of YouTube logo in IE8 and Developer Tools inspecting it
YouTube set a background-img on the logo span, which pulls in a sprite. (Large preview)

For reference, here is the same page in Chrome (see how Chrome renders an SVG instead):

Screenshot of Chrome DevTools inspecting YouTube logo
(Large preview)

And what about that Autoplay toggle? It’s rendered like a weird looking checkbox:

Screenshot of Autoplay toggle
Looks like IE8 defaults to a checkbox under the hood. (Large preview)

This appears to be down to use of a custom element (a paper-toggle-button, which is a Material Design element), which IE doesn’t understand:

Screenshot of Autoplay toggle markup
paper-toggle-button is a custom element. (The screenshot is from Chrome DevTools, alongside how the Autoplay toggle SHOULD render.) (Large preview)

I’m not surprised this hasn’t rendered properly; IE8 doesn’t even cope with the basic semantic markup we use these days. Try using an <aside> or <main> and it will basically render them as divs, but ignoring any styling you apply to them.

To enable HTML5 markup, you have to explicitly tell the browser these elements exist. They can then be styled as normal:

<!--[if lt IE 9]>  document.createElement('header'); document.createElement('nav'); document.createElement('section'); document.createElement('article'); document.createElement('aside'); document.createElement('footer'); 

That is wrapped in an IE conditional, by the way. <!--[if lt IE 9]> is a HTML comment to most browsers — and therefore gets skipped — but in IE it is a conditional which only passes “if less than IE 9”, where it executes/renders the DOM nodes within it.

So, the video page was a fail. Visiting directly didn’t fare much better:

Screenshot of IE8 YouTube homepage: “Your web browser is no longer supported”
At least I had a visible error message this time! (Large preview)

Undeterred, I ignored the warning and tried searching for a video within YouTube’s search bar.

Screenshot of Google “Sorry, your computer may be sending automated queries. We can't process your request”
Computer says no. (Large preview)

IE8 traffic is clearly suspicious enough that YouTube didn’t trust that I’m a real user, and decided not to process my search request!

Signing Up To Gmail

If I’m going to spend the day on IE8, I’m going to need an email address. So I go about trying to set up a new one.

First of all, I tried Gmail.

Screenshot of Gmail homepage
The text isn’t going to pass color contrast standards! (Large preview)

There’s something a bit off about the image and text here. I think it’s down to the fact that IE8 doesn’t support media queries — so it’s trying to show me a mobile image on desktop.

One way you can get around this is to use Sass to generate two stylesheets; one for modern browsers, and one for legacy IE. You can get IE-friendly, mobile-first CSS (see tutorial by Jake Archibald) by using a mixin for your media queries. The mixin “flattens” your legacy IE CSS to treat IE as though it’s always a specific predefined width (e.g. 65em), giving only the relevant CSS for that width. In this case, I’d have seen the correct background-image for my assumed screen size and had a better experience.

Anyway, it didn’t stop me clicking ‘Create an Account’. There were a few differences between how it looked in IE8 and a modern browser:

Screenshot comparing Gmail signup screen on Chrome and IE8
IE8 is missing the tight layout, and there’s an overlap of text, but otherwise still works. (Large preview)

Whilst promising at first sight, the form was quite buggy to fill in. The ‘label’ doesn’t get out of the way when you start filling in the fields, so your input text is obfuscated:

Screenshot of buggy labels
The labels overlapped the text I was writing. (Large preview)

The markup for this label is actually a <div>, and some clever JS moves the text out of the way when the input is focussed. The JS doesn’t succeed on IE8, so the text stays stubbornly in place.

Screenshot of gmail form markup
The ‘label’ is a div which is overlaid on form input using CSS. (Large preview)

After filling in all my details, I hit “Next”, and waited. Nothing happened.

Then I noticed the little yellow warning symbol at the bottom left of my IE window. I clicked it and saw that it was complaining about a JS error:

Screenshot of Gmail error
I got reasonably far, but then the Next button didn’t work. (Large preview)

I gave up on Gmail and turned to MSN.

Signing Up To Hotmail

I was beginning to worry that email might be off-limits for a ten-year-old browser. But when I went to Hotmail, the signup form looked OK — so far so good:

Screenshot of signup page for MSN
The signup page looked fine. Guessed we’d have more luck with a Microsoft product! (Large preview)

Then I noticed a CAPTCHA. I thought, “There’s no way I’ll get through this…”

Screenshot of captcha verification of signup state
I could see and complete the CAPTCHA. (Large preview)

To my surprise, the CAPTCHA worked!

The only quirky thing on the form was some slightly buggy label positioning, but the signup was otherwise seamless:

Screenshot of first name label, surname label, and then two empty input fields, no clear visual hierarchy
The label positions were a bit off, but I guessed my last name followed by my surname would be fine. (Large preview)

Does that screenshot look OK to you? Can you spot the deliberate mistake?

The leftmost input should have been my first name, not my surname. When I came back and checked this page later, I clicked on the “First name” label and it applied focus to the leftmost input, which is how I could have checked I was filling in the correct box in the first place. This shows the importance of accessible markup — even without CSS and visual association, I could determine exactly which input box applied to which label (albeit the second time around!).

Anyhow, I was able to complete the sign-up process and was redirected to the MSN homepage, which rendered great.

Screenshot of MSN homepage looking good
If any site is going to work in IE8, it will be the Microsoft homepage. (Large preview)

I could even read articles and forget that I was using IE8:

Screenshot of MSN article
The article works fine. No dodgy sidebars or borked images. (Large preview)

With my email registered, I was ready to go and check out the rest of the Internet!


I visited the Facebook site and was immediately redirected to the mobile site:

Screenshot of Facebook mobile
“You are using a browser that is not supported by Facebook, so we have redirected you to a simpler version to give you the best experience.” (Large preview)

This is a clever fallback tactic, as Facebook need to support a large global audience on low-end mobile devices, so need to provide a basic version of Facebook anyway. Why not offer that same baseline of experience to older desktop browsers?

I tried signing up and was able to make an account. Great! But when I logged into that account, I was treated with suspicion — just like when I searched for things on YouTube — and was faced with a CAPTCHA.

Only this time, it wasn’t so easy.

Screenshot of CAPTCHA message, but CAPTCHA image failing to load
“Please enter the code below”. Yeah, right. (Large preview)

I tried requesting new codes and refreshing the page several times, but the CAPTCHA image never loaded, so I was effectively locked out of my account.

Oh well. Let’s try some more social media.


I visited the Twitter site and had exactly the same mobile redirect experience.

Screenshot of mobile view for Twitter
Twitter treats IE8 as a mobile browser, like Facebook does. (Large preview)

But I couldn’t even get as far as registering an account this time:

Screenshot of Twitter registration screen
Your browser is no longer supported. To sign up, please update it. You can still log in to your existing user accounts. (Large preview)

Oddly, Twitter is happy for you to log in, but not for you to register in the first place. I’m not sure why — perhaps it has a similar CAPTCHA scenario on its sign-up pages which won’t work on older browsers. Either way, I’m not going to be able to make a new account.

I felt awkward about logging in with my existing Twitter account. Call me paranoid, but vulnerabilities like the CFR Watering Hole Attack of 2013 — where the mere act of visiting a specific URL in IE8 would install malware to your machine — had me nervous that I might compromise my account.

But, in the interests of education, I persevered (with a temporary new password):

Screenshot of Twitter feed
Successfully logged in. I can see tweets! (Large preview)

I could also tweet, albeit using the very basic <textarea>:

Screenshot of me writing a tweet, lamenting about the lack of emojis in the IE8 twitter view
You only miss them when they’re gone. (Large preview)

In conclusion, Twitter is basically fine in IE8 — as long as you have an account already!

I’m done with social media for the day. Let’s go check out some news.

BBC News

Screenshot of BBC homepage with “Security Warning” browser popup
The BBC appears to be loading a mixture of HTTPS and HTTP assets. (Large preview)

The news homepage looks very basic and clunky but basically works — albeit with mixed content security warnings.

Take a look at the logo. As we’ve already seen on YouTube, IE8 doesn’t support SVG, so we require a PNG fallback.

The BBC uses the <image> fallback technique to render a PNG on IE:

Screenshot of IE8 BBC News logo with devtools open
IE8 finds the base64 image inside the SVG and renders it. (Large preview)

…and to ignore the PNG when SVG is available:

Screenshot of Chrome BBC News logo with devtools open
The image part is ignored and the svg is rendered nicely. (Large preview)

This technique exploits the fact that older browsers used to obey both <image> and <img> tags, and so will ignore the unknown <svg> tag and render the fallback, whereas modern browsers ignore rendering <image> when inside an SVG. Chris Coyier explains the technique in more detail.

I tried viewing an article:

Screenshot of a BBC article, which displays fine but has a warning message at the top
This site is optimised for modern browsers, and does not fully support your browser. (Large preview)

It’s readable. I can see the headline, the navigation, the featured image. But the rest of the article images are missing:

Screenshot of BBC article with references to images that are not displaying
(Large preview)

This is to be expected, and is due to the BBC lazy-loading images. IE8 not being a ‘supported browser’ means it does not get the JavaScript that enables lazy-loading, thus the images never load at all.

Out of interest, I thought I’d see what happens if I try to access the BBC iPlayer:

Screenshot of BBC iPlayer - just a black screen
…not a lot. (Large preview)

And that got me wondering about another streaming service.


I was half expecting an empty white page when I loaded up Netflix in IE8. I was pleasantly surprised when I actually saw a decent landing page:

Screenshot of Netflix homepage
“Join free for a month” call to action, over a composite image of popular titles. (Large preview)

I compared this with the modern Chrome version:

Screenshot of Netflix homepage
“Watch free for 30 days” call to action, over a composite image of popular titles. (Large preview)

There’s a slightly different call to action (button text) — probably down to multivariate testing rather than what browser I’m on.

What’s different about the render is the centralized text and the semi-transparent black overlay.

The lack of centralized text is because of Netflix’s use of Flexbox for aligning items:

Netflix Flexbox aligning items
Netflix uses the Flexbox property justify-content: center to align its text. (Large preview)

A text-align: center on this class would probably fix the centering for IE8 (and indeed all old browsers). For maximum browser support, you can follow a CSS fallbacks approach with old ‘safe’ CSS, and then tighten up layouts with more modern CSS for browsers that support it.

The lack of background is due to use of rgba(), which is not supported in IE8 and below.

A background of rgba(0,0,0,.5) is meaningless to older browsers
A background of rgba(0,0,0,.5) is meaningless to older browsers. (Large preview)

Traditionally it’s good to provide CSS fallbacks like so, which show a black background for old browsers but show semi-transparent background for modern browsers:

rgb(0, 0, 0); /* IE8 fallback */
rgba(0, 0, 0, 0.8);

This is a very IE specific fix, however, basically every other browser supports rgba. Moreover, in this case, you’d lose the fancy Netflix tiles altogether, so it would be better to have no background filter at all! The surefire way of ensuring cross-browser support would be to bake the filter into the background image itself. Simple but effective.

Anyway, so far, so good — IE8 actually rendered the homepage pretty well! Am I actually going to be watching Breaking Bad on IE8 today?

My already tentative optimism was immediately shot down when I viewed the sign-in page:

Screenshot comparing sign-in page for Netflix on Chrome and IE8. IE version colors are all over the place, and it's hard to read the text
Can you guess which side is IE8 and which is Chrome? (Large preview)

Still, I was able to sign in, and saw a pared-back dashboard (no fancy auto-expanding trailers):

Screenshot of Netflix dashboard for logged in user
Each programme had a simple hover state with play icon and title. (Large preview)

I clicked on a programme with vague anticipation, but of course, only saw a black screen.


Ok, social media and video are out. All that’s left is to go shopping.

I checked out Amazon, and was blown away — it’s almost indistinguishable from the experience you’d get inside a modern browser:

Screenshot of Amazon homepage
The Amazon homepage looks almost as good on IE8 as it does on any other browser. (Large preview)

I’ve been drawn in by a good homepage before. So let’s click on a product page and see if this is just a fluke.

Screenshot of Amazon product page for Ferrero Rocher chocolates
The product page also looks fantastic (and makes me hungry). (Large preview)

No! The product page looked good too!

Amazon wasn’t the only site that surprised me in its backwards compatibility. Wikipedia looked great, as did the Gov.UK government website. It’s not easy to have a site that doesn’t look like an utter car crash in IE8. Most of my experiences were decidedly less polished…!

Screenshot of on IE8, layout is all over the place and text is hard to read when placed over images
It is difficult to read or navigate on IE8. (Large preview)

But a deprecated warning notice or funky layout wasn’t the worst thing I saw today.

Utterly Broken Sites

Some sites were so broken that I couldn’t even connect to them!

Screenshot: Internet Explorer cannot display the webpage
No dice when accessing GitHub. (Large preview)

I wondered if it might be a temporary VM network issue, but it happened every time I refreshed the page, even when coming back to the same site later in the day.

This happened on a few different sites throughout the day, and I eventually concluded that this never affected sites on HTTP — only on HTTPS (but not all HTTPS sites). So, what was the problem?

Using Wireshark to analyze the network traffic, I tried connecting to GitHub again. We can see that the connection failed to establish because of a fatal error, “Description: Protocol Version.”

Screenshot of Wireshark output
TLSv1 Alert (Level: Fatal, Description: Protocol Version) (Large preview)

Looking at the default settings in IE8, only TLS 1.0 is enabled — but GitHub dropped support for TLSv1 and TLSv1.1 in February 2018.

Screenshot of settings panel
Default advanced settings for IE8: TLS 1.0 is checked, TLS 1.1 and 1.2 are unchecked. (Large preview)

I checked the boxes for TLS 1.1 and TLS 1.2, reloaded the page and — voilà! — I was able to view GitHub!

Screenshot of GitHub homepage, with a “no longer supports Internet Explorer” message on it
It doesn’t look pretty, but at least I can now see it! (Large preview)

Many thanks to my extremely talented friend Aidan Fewster for helping me debug that issue.

I’m all for backwards compatibility, but this presents an interesting dilemma. According to the PCI Security Standards Council, TLS 1.0 is insecure and should no longer be used. But by forcing TLS 1.1 or higher, some users will invariably be locked out (and not all are likely to be tech-savvy enough to enable TLS 1.2 in their advanced settings).

By allowing older, insecure standards and enabling users to continue to connect to our sites, we’re not helping them — we’re hurting them, by not giving them a reason to move to safer technologies. So how far should you go in supporting older browsers?

How Can I Begin To Support Older Browsers?

When some people think of “supporting older browsers”, they might be thinking of those proprietary old hacks for IE, like that time the BBC had to do some incredibly gnarly things to support iframed content in IE7.

Or they may be thinking of making things work in the Internet Explorer “quirks mode”; an IE-specific mode of operation which renders things very differently to the standards.

But “supporting older browsers” is very different to “hacking it for IE”. I don’t advocate the latter, but we should pragmatically try to do the former. The mantra I try to live by as a web developer is this:

“Optimize for the majority, make an effort for the minority, and never sacrifice security.”

I’m going to move away from the world of IE8 now and talk about general, sustainable solutions for legacy browser support.

There are two broad strategies for supporting older browsers, both beginning with P:

  1. Polyfilling
    Strive for feature parity for all by filling in the missing browser functionality.
  2. Progressive Enhancement
    Start from a core experience, then use feature detection to layer on functionality.

These strategies are not mutually exclusive from one another; they can be used in tandem. There are a number of implementation decisions to make in either approach, each with their own nuances, which I’ll cover in more detail below.


For some websites or web pages, JavaScript is very important for functionality and you simply want to deliver working JavaScript to as many browsers as possible.

There are a number of ways to do this, but first, a history lesson.

A Brief History Of ECMAScript

ECMAScript is a standard, and JavaScript is an implementation of that standard. That means that ES5 is “ECMAScript version 5”, and ES6 is “ECMAScript version 6”. Confusingly, ES2015 is the same as ES6.

ES6 was the popularized name of that version prior to its release, but ES2015 is the official name, and subsequent ECMAScript versions are all associated with their release year.

Note: This is all helpfully explained by Brandon Morelli in a great blog post that explains the full history of JavaScript versions.

At time of writing, the latest standard is ES2018 (ES9). Most modern browsers support at least ES2015. Almost every browser supports ES5.

Technically IE8 isn’t ES5. It isn’t even ES4 (which doesn’t exist — the project was abandoned). IE8 uses the Microsoft implementation of ECMAScript 3, called JScript. IE8 does have some ES5 support but was released a few months before ES5 standards were published, and so has a mismatch of support.

Transpiling vs Polyfilling

You can write ES5 JavaScript and it will run in almost every ancient browser:

var foo = function () { return 'this is ES5!';

You can also continue to write all of your JavaScript like that — to enable backwards compatibility forever. But you’d be missing out on new features and syntactic sugar that has become available in the evolving versions of JavaScript, allowing you to write things like:

const foo = () => { return 'this is ES6!';

Try running that JavaScript in an older browser and it will error. We need to transpile the code into an earlier version of JavaScript that the browser will understand (i.e. convert our ES6 code into ES5, using automated tooling).

Now let’s say our code uses a standard ES5 method, such as Array.indexOf. Most browsers have a native implementation of this and will work fine, but IE8 will break. Remember IE8 was released a few months before ES5 standards were published, and so has a mismatch of support? One example of that is the indexOf function, which has been implemented for String but not for Array.

If we try to run the Array.indexOf method in IE8, it will fail. But if we’re already writing in ES5, what else can we do?

We can polyfill the behavior of the missing method. Developers traditionally polyfill each feature that they need by copying and pasting code, or by pulling in external third-party polyfill libraries. Many JavaScript features have good polyfill implementations on their respective Mozilla MDN page, but it’s worth pointing out that there are multiple ways you can polyfill the same feature.

For example, to ensure you can use the Array.indexOf method in IE8, you would copy and paste a polyfill like this:

if (!Array.prototype.indexOf) { Array.prototype.indexOf = (function (Object, max, min) { // big chunk of code that replicates the behaviour in JavaScript goes here! // for full implementation, visit: // })(Object, Math.max, Math.min);

So long as you call the polyfill before you pull in any of your own JS, and provided you don’t use any ES5 JavaScript feature other than Array.indexOf, your page would work in IE8.

Polyfills can be used to plug all sorts of missing functionality. For example, there are polyfills for enabling CSS3 selectors such as :last-child (unsupported in IE8) or the placeholder attribute (unsupported in IE9).

Polyfills vary in size and effectiveness and sometimes have dependencies on external libraries such as jQuery.

You may also hear of “shims” rather than “polyfills”. Don’t get too hung up on the naming — people use the two terms interchangeably. But technically speaking, a shim is code that intercepts an API call and provides a layer of abstraction. A polyfill is a type of shim, in the browser. It specifically uses JavaScript to retrofit new HTML/CSS/JS features in older browsers.

Summary of the “manually importing polyfills” strategy:

  • ✅ Complete control over choice of polyfills;
  • ✅ Suitable for basic websites;
  • ⚠️ Without additional tooling, you’re forced to write in native ES5 JavaScript;
  • ⚠️ Difficult to micromanage all of your polyfills;
  • ⚠️ Out of the box, all your users will get the polyfills, whether they need them or not.

Babel Polyfill

I’ve talked about transpiling ES6 code down to ES5. You do this using a transpiler, the most popular of which is Babel.

Babel is configurable via a .babelrc file in the root of your project. In it, you point to various Babel plugins and presets. There’s typically one for each syntax transform and browser polyfill you’ll need.

Micromanaging these and keeping them in sync with your browser support list can be a pain, so the standard setup nowadays is to delegate that micromanagement to the @babel/preset-env module. With this setup, you simply give Babel a list of browser versions you want to support, and it does the hard work for you:

{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage", "targets": { "chrome": "58", "ie": "11" } } ] ]

The useBuiltIns configuration option of @babel/preset-env is where the magic happens, in combination with an import "@babel/polyfill" (another module) in the entry point of your application.

  • When omitted, useBuiltIns does nothing. The entirety of @babel/polyfill is included with your app, which is pretty heavy.
  • When set to "entry", it converts the @babel/polyfill import into multiple, smaller imports, importing the minimum polyfills required to polyfill the targeted browsers you’ve listed in your .babelrc (in this example, Chrome 58 and IE 11).
  • Setting to "usage" takes this one step further by doing code analysis and only importing polyfills for features that are actually being used. It’s classed as “experimental” but errs on the side of “polyfill too much” rather than “too little”. In any case, I don’t see how it’s possible that it would create a bigger bundle than "entry" or false, so is a good option to choose (and is the way we’re going at the BBC).

Using Babel, you can transpile and polyfill your JavaScript prior to deploying to production, and target support in a specific minimum baseline of browsers. NB, another popular tool is TypeScript, which has its own transpiler that transpiles to ES3, in theory supporting IE8 out of the box.

Summary of using @babel/preset-env for polyfilling:

  • ✅ Delegate micromanagement of polyfills to a tool;
  • ✅ Automated tool helps prevent inclusion of polyfills you don’t need;
  • ✅ Scales to larger, complex sites;
  • ⚠️ Out of the box, all your users will get the polyfills, whether they need them or not;
  • ⚠️ Difficult to keep sight of exactly what’s being pulled into your application bundle.

Lazy Loading Polyfills With Webpack And Dynamic Imports

It is possible to leverage the new import() proposal to feature-detect and dynamically download polyfills prior to initializing your application. It looks something like this in practice:

import app from './app.js'; const polyfills = []; if (!window.fetch) { polyfills.push(import(/* webpackChunkName: "polyfill-fetch" */ 'whatwg-fetch'));
} Promise.all(polyfills) .then(app) .catch((error) => { console.error('Failed fetching polyfills', error); });

This example code is shamelessly copied from the very good article, “Lazy Loading Polyfills With Webpack And Dynamic Imports” that delves into the technique in more detail.


  • ✅ Doesn’t bloat modern browsers with unnecessary polyfills;
  • ⚠️ Requires manually managing each polyfill. is polyfilling as a service, built by the Financial Times. It works by your page making a single script request to, optionally listing the specific features you need to polyfill. Their server then analyzes the user agent string and populates the script accordingly. This saves you from having to manually provide your own polyfill solutions.

Here is the JavaScript that returns for a request made from IE8:

Screenshot of response from service for IE8
Lots of JS code to polyfill standard ES5 methods in IE8. (Large preview)

Here’s the same request, but where the request came from modern Chrome:

Screenshot of response from service for Chrome - no polyfill was required
No JS code, just a JS comment. (Large preview)

All that’s required from your site is a single script call.


  • ✅ Ease of inclusion into your web app;
  • ✅ Delegates responsibility of polyfill knowledge to a third party;
  • ⚠️ On the flipside, you’re now reliant on a third-party service;
  • ⚠️ Makes a blocking  call, even for modern browsers that don’t need any polyfills.

Progressive Enhancement

Polyfilling is an incredibly useful technique for supporting older browsers, but can be a bloat to web pages and is limited in scope.

The progressive enhancement technique, on the other hand, is a great way of guaranteeing a basic experience for all browsers, whilst retaining full functionality for your users on modern browsers. It should be achievable on most sites.

The principle is this: start from a baseline of HTML (and styling, optional), and “progressively enhance” the page with JavaScript functionality. The benefit is that if the browser is a legacy one, or if the JavaScript is broken at any point in its delivery, your site should still be functional.

The term “progressive enhancement” is often used interchangeably with “unobtrusive JavaScript“. They do mean essentially the same thing, but the latter takes it a little further in that you shouldn’t litter your HTML with lots of attributes, IDs and classes that are only used by your JavaScript.


The BBC technique of “cutting the mustard” (CTM) is a tried and tested implementation of progressive enhancement. The principle is that you write a solid baseline experience of HTML, and before downloading any enhancing JavaScript, you check for a minimum level of support. The original implementation checked for the presence of standard HTML5 features:

if ('querySelector' in document && 'localStorage' in window && 'addEventListener' in window) { // Enhance for HTML5 browsers

As new features come out and older browsers become increasingly antiquated, our cuts the mustard baseline will change. For instance, new JavaScript syntax such as ES6 arrow functions would mean this inline CTM check fails to even parse in legacy browsers — not even safely executing and failing the CTM check — so may have unexpected side-effects such as breaking other third-party JavaScript (e.g. Google Analytics).

To avoid even attempting to parse untranspiled, modern JS, we can apply this “modern take” on the CTM technique, taken from @snugug’s blog, in which we take advantage of the fact that older browsers don’t understand the type="module" declaration and will safely skip over it. In contrast, modern browsers will ignore  declarations.

http://./no-mustard.js <!-- Can be done inline too -->  import mustard from './mustard.js';
  console.log('No Mustard!');

This approach is a good one, provided you’re happy treating ES6 browsers as your new minimum baseline for functionality (~92% of global browsers at the time of writing).

However, just as the world of JavaScript is evolving, so is the world of CSS. Now that we have Grid, Flexbox, CSS variables and the like (each with a varying efficacy of fallback), there’s no telling what combination of CSS support an old browser might have that might lead to a mishmash of “modern” and “legacy” styling, the result of which looks broken. Therefore, sites are increasingly choosing to CTM their styling, so now HTML is the core baseline, and both CSS and JS are treated as enhancements.

The JavaScript-based CTM techniques we’ve seen so far have a couple of downsides if you use the presence of JavaScript to apply CSS in any way:

  1. Inline JavaScript is blocking. Browsers must download, parse and execute your JavaScript before you get any styling. Therefore, users may see a flash of unstyled text.
  2. Some users may have modern browsers, but choose to disable JavaScript. A JavaScript-based CTM prevents them from getting a styled site even when they’re perfectly capable of getting it.

The ‘ultimate’ approach is to use CSS media queries as your cuts-the-mustard litmus test. This “CSSCTM” technique is actively in use on sites such as Springer Nature.

<head> <!-- CSS-based cuts-the-mustard --> <!-- IMPORTANT: the JS depends on having this rule somewhere in the CSS: `body { clear: both }` --> <link rel="stylesheet" href="mq-test.css" media="only screen and (min-resolution: 0.1dpcm), only screen and (-webkit-min-device-pixel-ratio:0) and (min-color-index:0)">
<body> <!-- content here... -->  (function () { // wrap in an IIFE to prevent global scope pollution function isSupported () { var val = ''; if (window.getComputedStyle) { val = window.getComputedStyle(document.body, null).getPropertyValue('clear'); } else if (document.body.currentStyle) { val = document.body.currentStyle.clear; } if (val === 'both') { // references the `body { clear: both; }` in the CSS return true; } return false; } if (isSupported()) { // Load or run JavaScript for supported browsers here. } })(); 

This approach is quite brittle — accidentally overriding the clear property on your body selector would ‘break’ your site — but it does offer the best performance. This particular implementation uses media queries that are only supported in at least IE 9, iOS 7 and Android 4.4, which is quite a sensible modern baseline.

“Cuts the mustard”, in all its various guises, accomplishes two main principles:

  1. Widespread user support;
  2. Efficiently applied dev effort.

It’s simply not possible for sites to accommodate every single browser / operating system / network connection / user configuration combination. Techniques such as cuts-the-mustard help to rationalize browsers into C-grade and A-grade browsers, according to the Graded Browser Support model by Yahoo!.

Cuts-The-Mustard: An Anti-Pattern?

There is an argument that applying a global, binary decision of “core” vs “advanced” is not the best possible experience for our users. It provides sanity to an otherwise daunting technical problem, but what if a browser supports 90% of the features in your global CTM test, and this specific page doesn’t even make use of the 10% of the features it fails on? In this case, the user would get the core experience, since the CTM check would have failed. But we could have given them the full experience.

And what about cases where the given page does make use of a feature the browser doesn’t support? Well, in the move towards componentization, we could have a feature-specific fallback (or error boundary), rather than a page-level fallback.

We do this every day in our web development. Think of pulling in a web font; different browsers have different levels of font support. What do we do? We provide a few font file variations and let the browser decide which to download:

@font-face { font-family: FontName; src: url('path/filename.eot'); src: url('path/filename.eot?#iefix') format('embedded-opentype'), url('path/filename.woff2') format('woff2'), url('path/filename.woff') format('woff'), url('path/filename.ttf') format('truetype');

We have a similar fallback with HTML5 video. Modern browsers will choose which video format they want to use, whereas legacy browsers that don’t understand what a <video> element is will simply render the fallback text:

<video width="400" controls> <source src="mov_bbb.mp4" type="video/mp4"> <source src="mov_bbb.ogg" type="video/ogg"> Your browser does not support HTML5 video.

The nesting approach we saw earlier used by the BBC for PNG fallbacks for SVG is the basis for the <picture> responsive image element. Modern browsers will render the best fitting image based on the media attribute supplied, whereas legacy browsers that don’t understand what a <picture> element is will render the <img> fallback.

<picture> <source media="(min-width: 650px)" srcset="img_pink_flowers.jpg"> <source media="(min-width: 465px)" srcset="img_white_flower.jpg"> <img src="img_orange_flowers.jpg" alt="Flowers" style="width:auto;">

The HTML spec has carefully evolved over the years to provide a basic fallback mechanism for all browsers, whilst allowing features and optimisations for the modern browsers that understand them.

We could apply a similar principle to our JavaScript code. Imagine a Feature like so, where the foo method contains some complex JS:

class Feature { browserSupported() { return ('querySelector' in document); // internal cuts-the-mustard goes here } foo() { // etc }
} export default new Feature();

Before calling foo, we check if the Feature is supported in this browser by calling its browserSupported method. If it’s not supported, we don’t even attempt to call the code that would otherwise have errored our page.

import Feature from './feature'; if (Feature.browserSupported()) {;

This technique means we can avoid pulling in polyfills and just go with what’s natively supported by each individual browser, gracefully degrading individual features if unsupported.

Note that in the example above, I’m assuming the code gets transpiled to ES5 so that the syntax is understood by all browsers, but I’m not assuming that any of the code is polyfilled. If we wanted to avoid transpiling the code, we could apply the same principle but using the type="module" take on cuts-the-mustard, but it comes with the caveat that it already has a minimum ES6 browser requirement, so is only likely to start being a good solution in a couple of years:

 import Feature from './feature.js'; if (Feature.browserSupported()) {; }

We’ve covered HTML, and we’ve covered JavaScript. We can apply localized fallbacks in CSS too; there’s a @supports keyword in CSS, which allows you to conditionally apply CSS based on the presence or absence of support for a CSS feature. However, it is ironically caveated with the fact that it is not universally supported. It just needs careful application; there’s a great Mozilla blog post on how to use feature queries in CSS.

In an ideal world, we shouldn’t need a global cuts-the-mustard check. Instead, each individual HTML, JS or CSS feature should be self-contained and have its own error boundaries. In a world of web components, shadow DOM and custom elements, I expect we’ll see more of a shift to this sort of approach. But it does make it much more difficult to predict and to test your site as a whole, and there may be unintended side-effects if, say, the styling of one component affects the layout of another.

Two Main Backwards Compatibility Strategies

A summary of polyfilling as a strategy:

  • ✅ Can deliver client-side JS functionality to most users.
  • ✅ Can be easier to code when delegating the problem of backwards-compatibility to a polyfill.
  • ⚠️ Depending on implementation, could be detrimental to performance for users who don’t need polyfills.
  • ⚠️ Depending on complexity of application and age of browser, may require lots of polyfills, and therefore run very poorly. We risk shipping megabytes of polyfills to the very browsers least prepared to accept it.

A summary of progressive enhancement as a strategy:

  • ✅ Traditional CTM makes it easy to segment your code, and to manually test.
  • ✅ Guaranteed baseline of experience for all users.
  • ⚠️ Might unnecessarily deliver the core experience to users who could handle the advanced experience.
  • ⚠️ Not well suited to sites that require client-side JS for functionality.
  • ⚠️ Sometimes difficult to balance a robust progressive enhancement strategy with a performant first render. There’s a risk of over-prioritizing the ‘core’ experience to the detriment of the 90% of your users who get the ‘full’ experience (e.g. providing small images for noJS and then replacing with high-res images on lazy-load means we’ve wasted a lot of download capacity on assets that are never even viewed).


IE8 was once a cutting edge browser. (No, seriously.) The same could be said for Chrome and Firefox today.

If today’s websites are totally unusable in IE8, the websites in ten years time’ are likely to be about as unusable in today’s modern browsers — despite being built upon the open technologies of HTML, CSS, and JavaScript.

Stop and think about that for a moment. Isn’t it a bit scary? (That said, if you can’t abandon browsers after ten years and after the company who built it has deprecated it, when can you?)

IE8 is today’s scapegoat. Tomorrow it’ll be IE9, next year it’ll be Safari, a year later it might be Chrome. You can swap IE8 out for ‘old browser of choice’. The point is, there will always be some divide between what browsers developers build for, and what browsers people are using. We should stop scoffing at that and start investing in robust, inclusive engineering solutions. The side effects of these strategies tend to pay dividends in terms of accessibility, performance and network resilience, so there’s a bigger picture at play here.

We tend not to think about screen reader numbers. We simply take it for granted that it’s morally right to do our best to support users who have no other way of consuming our content, through no fault of our own. The same principle applies to people using older browsers.

We’ve covered some high-level strategies for building robust sites that should continue to work, to some degree, across a broad spectrum of legacy and modern browsers.

Once again, a disclaimer: don’t hack things for IE. That would be missing the point. But be mindful that all sorts of people use all sorts of browsers for all sorts of reasons, and that there are some solid engineering approaches we can take to make the web accessible for everyone.

Optimize for the majority, make an effort for the minority, and never sacrifice security.

Further Reading on SmashingMag:

Smashing Editorial (ra, il)
The Whole Spreadsheets as Databases Thing is Pretty Cool

A spreadsheet has always been a strong (if fairly literal) analogy for a database. A database has tables, which is like a single spreadsheet. Imagine a spreadsheet for tracking RSVPs for a wedding. Across the top, column titles like First Name, Last Name, Address, and Attending?. Those titles are also columns in a database table. Then each person in that spreadsheet is literally a row, and that’s also a row in a database table (or an entry, item, or even tuple if you’re really a nerd).

It’s been getting more and more common that this doesn’t have to be an analogy. We can quite literally use a spreadsheet UI to be our actual database. That’s meaningful in that it’s not just viewing database data as a spreadsheet, but making spreadsheet-like features first-class citizens of the app right alongside database-like features.

With a spreadsheet, the point might be viewing the thing as a whole and understanding things that way. Browsing, sorting, entering and editing data directly in the UI, and making visual output that is useful.

With a database, you don’t really look right at it — you query it and use the results. Entering and editing data is done through code and APIs.

That’s not to say you can’t look directly at a database. Database tools like Sequel Pro (and many others!) offer an interface for looking at tables in a spreadsheet-like format:

What’s nice is that the idea of spreadsheets and databases can co-exist, offering the best of both worlds at once. At least, on a certain scale.

We’ve talked about Airtable before here on CSS-Tricks and it’s a shining example of this.

Airtable calls them bases, and while you can view the data inside them in all sorts of useful ways (a calendar! a gallery! a kanban!), perhaps the primary view is that of a spreadsheet:

If all you ever do with Airtable is use it as a spreadsheet, it’s still very nice. The UI is super well done. Things like filtering and sorting feel like true first-class citizens in a way that it’s almost weird that other spreadsheet technology doesn’t. Even the types of fields feel practical and modern.

Plus with all the different views in a base, and even cooler, all the “blocks” they offer to make the views more dashboard-like, it’s a powerful tool.

But the point I’m trying to make here is that you can use your Airtable base like a database as well, since you automatically have read/write API access to your base.

So cool that these API docs use data from your own base to demonstrate the API.

I talked about this more in my article How To Use Airtable as a Front End Developer. This API access is awesome from a read data perspective, to do things like use it as a data source for a blog. Robin yanked in data to build his own React-powered interface. I dig that there is a GraphQL interface, if it is third-party.

The write access is arguably even more useful. We use it at CodePen to do CRM-ish stuff by sending data into an Airtable base with all the information we need, then use Airtable directly to visualize things and do the things we want.

Airtable alternatives?

There used to be Fieldbook, but that shut down.

RowShare looks weirdly similar (although a bit lighter on features) but it doesn’t look like it has an API, so it doesn’t quite fit the bill for that database/spreadsheet gap spanning.

Zoho Creator does have an API and interesting visualization stuff built in, which actually looks pretty darn cool. It looks like some of their marketing is based around the idea that if you need to build a CRUD app, you can do that with this with zero coding — and I think they are right that it’s a compelling sell.

Actiondesk looks interesting in that it’s in the category of a modern take on the power of spreadsheets.

While it’s connected to a database in that it looks like it can yank in data from something like MySQL or PostgreSQL, it doesn’t look like it has database-like read/write APIs.

Can we just use Google Sheets?

The biggest spreadsheet tool in the sky is, of course, the Google one, as it’s pretty good, free, and familiar. It’s more like a port of Excel to the browser, so I might argue it’s more tied to the legacy of number-nerds than it is any sort of fresh take on a spreadsheet or data storage tool.

Google Sheets has an API. They take it fairly seriously as it’s in v4 and has a bunch of docs and guides. Check out a practical little tutorial about writing to it from Slack. The problem, as I understand it, is that the API is weird and complicated and hard, like Sheets itself. Call me a wimp, but this quick start is a little eye-glazing.

What looks like the most compelling route here, assuming you want to keep all your data in Google Sheets and use it like a database, is Sheetsu. It deals with the connection/auth to the sheet on its end, then gives you API endpoints to the data that are clean and palatable.

Plus there are some interesting features, like giving you a form UI for possibly easier (or more public) data entry than dealing with the spreadsheet itself.

There is also Sheetrock.js, an open source library helping out with that API access to a sheet, but it hasn’t been touched in a few years so I’m unsure the status there.

I ain’t trying to tell you this idea entirely replaces traditional databases.

For one thing, the relational part of databases, like MySQL, is a super important aspect that I don’t think spreadsheets always handle particularly well.

Say you have an employee table in your database, and for each row in that table, it lists the department they work for.

ID Name Department
-- -- --
1 Chris Coyier Front-End Developer
2 Barney Butterscotch Human Resources 

In a spreadsheet, perhaps those department names are just strings. But in a database, at a certain scale, that’s probably not smart. Instead, you’d have another table of departments, and relate the two tables with a foreign key. That’s exactly what is described in this classic explainer doc:

To find the name of a particular employee’s department, there is no need to put the name of the employee’s department into the employee table. Instead, the employee table contains a column holding the department ID of the employee’s department. This is called a foreign key to the department table. A foreign key references a particular row in the table containing the corresponding primary key.

ID Name Department
-- -- --
1 Chris Coyier 1
2 Barney Butterscotch 2 ID Department Manager -- -- --
1 Front-End Developers Akanya Borbio
2 Human Resources Susan Snowrinkle

To be fair, spreadsheets can have relational features too (Airtable does), but perhaps it isn’t a fundamental first-class citizen like some databases treat it.

Perhaps more importantly, databases, largely being open source technology, are supported by a huge ecosystem of technology. You can host your PostgreSQL or MySQL database (or whatever all the big database players are) on all sorts of different hosting platforms and hardware. There are all sorts of tools for monitoring it, securing it, optimizing it, and backing it up. Plus, if you’re anywhere near breaking into the tens of thousands of rows point of scale, I’d think a spreadsheet has been outscaled.

Choosing a proprietary host of data is largely for convenience and fancy UX at a somewhat small scale. I kinda love it though.

The post The Whole Spreadsheets as Databases Thing is Pretty Cool appeared first on CSS-Tricks.

A Detailed Comparison Between WordPress And October CMS

A Detailed Comparison Between WordPress And October CMS

A Detailed Comparison Between WordPress And October CMS

Leonardo Losoviz

Three months ago, WordPress finally released React-powered Gutenberg to power its default content editing experience, triggering many people who are not happy with this change to look for alternatives. Some folks decided to fork and release pre-Gutenberg WordPress, however, for me this doesn’t make much sense since it still carries 15 years worth of technical debt. If I were to find an alternative to WordPress, I would try to avoid being stuck in the past, and aim for a clean cut through some mature platform built on modern foundations.

This article compares WordPress to the arguably similar yet more modern October CMS on a wide arrange of both technical and non-technical topics. The goal of the article is not to convince people to stick to WordPress or to switch to October CMS, but simply to demonstrate what aspects must be taken into account before concluding the move to a different platform. The same comparison could (and should) also be done with other platforms before making a sensible decision.

Why October CMS

I found out about October CMS when it won an award, after which I went into research mode and spent a good deal of time digging deep into this CMS — from the perspective of both a user and a developer. As I gained knowledge on this CMS, I felt confident that I could provide an objective evaluation of its features as contrasted to WordPress. I chose this CMS for the comparison over alternative options such as Grav, Statamic, ButterCMS, Joomla, Drupal, Jekyll, Hugo, and others, for the following reasons:

  • I know how this CMS works (unlike Grav);
  • It is free and open source (unlike Statamic and ButterCMS);
  • At five years, it is “relatively” new (unlike Joomla and Drupal);
  • It is a dynamic (not static) content generator and based in PHP (unlike Jekyll and Hugo).

I believe that October CMS is a good candidate because it is based on Laravel which is a framework used for building modern applications. After seven years of existence, it has received positive approval from developers (as evidenced by its sizeable community and ecosystem), and marks a distinct contrast over coding in WordPress, i.e. WordPress is mostly procedural programming while Laravel is decidedly object-oriented programming.

What’s The Difference Between The Two?

Below I will compare WordPress and October CMS on different categories and highlight what, I believe, is good and not so good about them. However, I will not pick a winner, since that’s not the objective of the article and, in any case, there is no “best” or even “better” CMS: each CMS has its own set of strengths and weaknesses that will make it more or less suitable for each task, project, company, team, and anything else. Moreover, a project may benefit from using more than one CMS, such as using some CMS to manage and provide data, and another CMS to render the view. To decide which of the dozens of CMSs out there is most suitable for your own needs is entirely up to you.

In addition, this article could never draw definitive conclusions since it is only concerned with a subset of all possibilities. For instance, we can also find online comparisons such as “WordPress vs Drupal vs Joomla”, “WordPress vs Static Site Generators” and even “WordPress vs Medium”. Because none of these articles sees the full picture, then none of these comparisons can ever be conclusive, and should not be treated as such.

Let’s start with the comparison.

Philosophy And Target Group

It is no coincidence that WordPress powers nearly 1 in 3 websites. Ever since its inception, it has strived to be extremely user-friendly and has done so successfully, removing friction for technical and non-technical users alike as well as for people from all backgrounds — irrespective of their education and economic levels. WordPress’ founder Matt Mullenweg expressed that WordPress’ motto of “Democratize Publishing” for the current era meant the following:

“People of all backgrounds, interests, and abilities should be able to access Free-as-in-speech software that empowers them to express themselves on the open web and to own their content.”

WordPress is easy to use for everyone and its inclusivity is evidenced on the development side too: It’s not uncommon to find people without a programming background (such as marketers, designers, bloggers, sales people, and others) tinkering with their WordPress installations, designing their own themes and successfully launching their own websites. WordPress is user-centric, and the needs of the users trump those of the developers. In WordPress, the user is king (or queen).

In contrast, October CMS is more geared towards the developer, as explicity established from its very first release:

“October makes one bold but obvious assumption: clients don’t build websites, developers do. The role of a client is to manage the website and convey their business requirements. The web developer, and the industry itself, revolves around mediating these factors.”

In the words of its founders, the CMS’ mission is to “prove that making websites is not rocket science.” Being based on Laravel, October CMS can claim to have strong foundations of reusable, modular code that can produce properly-architected applications, maintainable in the long term and fully customizable without requiring hacks — the type which attracts serious programmers. October CMS can also provide a great user experience, however, it is not as simple or frictionless as that provided by WordPress. Users may need to be explained how to use certain functionality before being able to use it. For instance, embedding a form from some plugin has a lengthy explanation on how to do it, which is more cumbersome than the self-evident, drag-and-drop functionality provided by several form plugins in WordPress.


WordPress is famous for its 5-minute installation, even though many people point out that (taking into consideration all the plugins that must be installed) a typical installation requires 15 minutes or more. In addition, WordPress also offers the Multisite feature, which allows us to create a network of multiple virtual sites under a single installation. This feature makes it easy for an agency to administer the sites of multiple clients — among other user cases.

Installing October CMS is also very smooth: The Wizard installation itself takes even less than five minutes, and if you install it through the Console installation, it is even faster. You can do the latter by simply navigating to the target directory and then executing curl -s | php (after which we need to input the database configuration, otherwise it behaves as a flat-file CMS). Once the installation has been completed, we will have a fully functioning website, but still quite bare (if you add the time needed to install and configure the required plugins, you can expect it to take at least 15 minutes).

October CMS Wizard installation
Installing October CMS with the Wizard is a breeze. (Large preview)


WordPress has been accused of being insecure due to the high amount of vulnerabilities that are constantly found. This forces users to have the software for the CMS and all installed plugins always up to date to avoid security exploits. Among the main issues is WordPress’ support for older versions of PHP which are not supported by the PHP development community anymore (WordPress currently supports PHP 5.2.4, while the latest fully supported PHP version is 5.6). However, this problem should be resolved in April 2019 when WordPress will officially start supporting PHP versions 5.6 and upwards.

Otherwise, WordPress is not necessarily insecure because of itself, but because of its high popularity, which makes it a primal target for hackers. However, this plays both ways: WordPress ubiquity means that its security team must really take their job seriously by constantly looking for exploits and fixing them as soon as possible, otherwise up to a third of the web is at risk. The stakes are just too high.

October CMS, on the other hand, doesn’t have a reputation of being insecure. However, since there are roughly 27,000 live sites that use October as compared with WordPress’ millions, we can’t judge the two of them on the same terms. Nevertheless, the team behind October CMS does take security seriously, as evidenced by the Wizard installation’s prompt to input the CMS backend URL, set as /backend by default but changeable to anything else, as to make it more difficult for hackers to target the site. In contrast, changing WordPress’ login and backend URLs from /wp-login.php and /wp-admin respectively to something else must be done through a plugin. In addition, October CMS can function as a flat-file CMS (i.e. without a database) and avoid database-related vulnerabilities such as SQL injection.

Technology Stack

Both WordPress and October CMS run on the traditional LAMP stack: Linux, Apache, MySQL, and PHP. (However, only PHP is fixed: we can also use Windows, Nginx, MariaDB, and others.) October CMS can also behave as a flat-file CMS, meaning that it can do without a database, however, at the cost of forgoing many functionalities (such as blog posts and users) the only functionality that is guaranteed is pages, which is considered to be the basic unit for the creation and publishing of content and shipped as a core feature.

Concerning the language stack, sites built with both WordPress and October CMS are based on HTML, CSS, and JavaScript (note that PHP is used to generate the HTML). October CMS also makes it easy to use LESS and SASS files.

Programming Paradigm

WordPress follows a functional programming paradigm, based on calculating computations by calling functions devoid of application state. Even though WordPress developers do not need to stick to functional programming (for instance, for coding their themes and plugins), the WordPress core code inherits this paradigm from 15 years of preserving backwards compatibility, which has been one of the pillars to WordPress’ success but which has the unintended consequence of accumulating technical debt.

On the other side, October CMS follows an imperative programming paradigm, based on calculating computations by manipulating objects’ state. October CMS sits on top of Laravel, a web framework fully founded on Object-Oriented Programming principles that enable the production of modular applications based on concepts such as the Model-View-Controller to decouple the user interface from the application data, Dependency Injection to configure class dependencies, and the Interface Segregation Principle to define the core services provided by the framework, among many others.


Programming in WordPress could be characterized as HDD which stands for “Hook-Driven Development”. A hook is a mechanism that allows changing a default behavior or value and allowing other code to execute related functionality. Hooks are triggered through “actions” which allow executing extra functionality, and “filters” that allow modifying values.

Hooks, which are widespread across the WordPress codebase, are one of the concepts that I most like from coding in WordPress. They allow plugins to interact with other plugins (or with a core or theme) in a clean way, providing some basic support of Aspect-Oriented Programming.

Good news is that Laravel (and in consequence October CMS) also supports the concept of hooks, which is called “events”. Events provide a simple observer implementation, enabling code to subscribe and listen for events that occur in the application and react as needed. Events make it possible to split a complex functionality into components, which can be installed independently yet collaborate with each other, thus enabling the creation of modular applications.

Dependence on JavaScript Libraries

The latest version of WordPress incorporates React-powered Gutenberg for its default content creation experience. Hence, WordPress development now relies by and large on JavaScript (predominantly through React), even though it is also possible to use other frameworks or libraries (as evidenced by Elementor Blocks for Gutenberg which is based on Marionette). In addition, WordPress still relies on Backbone.js (for the Media Manager) and jQuery (legacy code), however, we can expect the dependence on these libraries to wither away as Gutenberg is consolidated as the new norm.

October CMS depends on jQuery, which it uses to implement its optional AJAX framework to load data from the server without a browser page refresh.

Pages, Themes and Plugins

Both WordPress and October CMS treat a page as the basic unit for creating and publishing content (in WordPress case, in addition to the post), support changing the site’s look and feel through themes, and allow to install and extend the site’s functionalities through plugins. Even though the concepts are the same in both CMSs, there are a few differences in implementation that produce somewhat different behavior.

In WordPress, pages are defined as content and stored in the database. As a result, page content can be created through the CMS only (e.g. in the dashboard area), and switching from one theme to another doesn’t make an existing page become unavailable. This produces an overall frictionless experience.

In October CMS, on the other hand, pages are static files stored under the theme directory. On the positive side from this architectural decision, page content can be created from an external application, such as text editors like Sublime or Visual Studio Code. On the negative side, when switching from one theme to another, it is required to manually recreate or copy the pages from the current to the new theme, or otherwise, they will disappear.

Significantly, October CMS resolves routing through pages, hence pages are used not just as containers for content but also for functionality. For instance, a plugin for blogging depends on a page for displaying the list of blog posts under a chosen URL, another page to display a single blog post under another chosen URL, and so on. If any of these pages disappear, the associated functionality from the plugin becomes unavailable, and that URL will produce a 404. Hence, in October CMS themes and plugins are not thoroughly decoupled, and switching themes must be done carefully.

Editing a file from inside or outsite October CMS
October CMS enables the creation of content from external applications. (Large preview)

Core vs Plugin Functionality

WordPress attempts to deliver a minimal core functionality which is enhanced through plugins. WordPress relies on the 8020 rule” to decide if to include some functionality in its core experience or not. If it benefits 80% of the users it goes in, otherwise, it belongs to plugin-land. When adding plugins to a site, they can lead to bloat if too many plugins are installed. Plugins may also not work well with one another, or execute similar code or load similar assets, resulting in suboptimal performance. Hence, whereas launching a WordPress site is relatively easy, a bigger challenge is its general maintenance and being able to preserve an optimal and performant state when adding new features.

WordPress plugin directory
The WordPress plugin directory claims to have almost 55,000 plugins. (Large preview)

Likewise, October CMS also attempts to deliver a minimal core functionality, but on steroids: the only guaranteed functionality is the creation and publication of pages, and for everything else we will need to install one plugin or another, which is expressed as:

“Everything you need, and nothing you don’t.”

The objective is clear: most simple sites are only composed of pages, with possibly no blog posts, users or login area. So why should the application load resources for these when they are not needed? As a consequence, functionalities for blogging, user management, translation and several others are released through the plugin directory.

October CMS plugins directory
Searching for ‘Rainlab’ in October’s plugins directory displays plugins created by October CMS’ team. (Large preview)

October CMS also includes certain features in its core which (even though they are not always needed) can enhance the application significantly. For instance, it provides out-of-the-box support to upload media files to Amazon S3 and accesses them through the Rackspace CDN. It also includes a Media Manager which is mostly used through plugins, e.g. for adding images into a blog post. (Pages can also use the Media Manager to embed media files, however, the CMS also ships with an Assets section to upload media files for these which seems more suitable.)

I believe that October’s opinionatedness can perfectly enable us to produce an application that is as lean as possible — mostly concerning simple sites. However, it can also backfire and encourage bloat, because the line of what is needed and what is not is an arbitrary one, and it’s difficult to be set in advance by the CMS. This difficulty can be appreciated when considering the concept of a “user”: In WordPress, website users and website admins belong to the same user entity (and through roles and privileges we can make a user become an admin). In October CMS, these two are implemented separately, shipping in core the implementation for the website administrator which can log in to the backend area and modify the settings, and through a plugin the implementation of the website user. These two types of users have a different login process and a different database table for storing their data, thus arguably breaching the DRY (Don’t Repeat Yourself) principle.

This problem arises not only concerning the behavior of an entity but also what data fields it must contain. For instance, should the website user data fields be predefined? Is a telephone field required? What about an Instagram URL field, considering that Instagram got kind of cool only recently? But then, when building a professional website shouldn’t we use a LinkedIn URL field instead? These decisions clearly depend on the application and can’t be decided by either CMS or plugin.

The October CMS plugin called User implements users but without any user field, on top of which plugin User Plus adds several arbitrary user fields, which are possibly not enough, so plugin User Plus+ adds yet other user fields. When, where and how do we stop this process?

Another problem is when there is no room to add new capabilities to an entity, which leads to the creation of another, extremely similar entity, just to support those required capabilities. For instance, October CMS ships with pages, and allows to create “static pages” through a plugin. Their nature is the same: both pages and static pages are saved as static files. The only difference between them (as far as I can tell) is that static pages are edited with a visual editor instead of the HTML editor, and can be added to menus. In my opinion, only structural differences, such as having one entity saved as a static file and the other one stored in the database, could justify creating a second entity for a page (there is a pull request to do this), but for simple features, as is the case currently, it constitutes development bloat.

In summary, a well implemented October CMS application can be very lean and efficient (e.g. by removing the database when not needed), but on the contrary it can also become unnecessarily bloated, forcing developers to implement several solutions for similar entities, and which can be very confusing to use (“Should I use a page or a static page?”). Because neither WordPress or October CMS has found a perfect solution for removing bloat, we must design either application architecture with care to avoid down-the-road pain.

Content Creation

Gutenberg makes two important contributions to WordPress: It uses components as the unit for building sites (which offers several advantages over coding blobs of HTML), and it introduces an entity called a “block” which, once Gutenberg Phase 2 is completed (presumably in 2019), will provide a unified way to incorporate content into the site, thus enabling a simpler user experience as opposed to the more chaotic process of adding content through shortcodes, TinyMCE buttons, menus, widgets, and others.

WordPress Gutenberg
Since WordPress 5.0 Gutenberg is the default content creation experience. (Large preview)

Because Gutenberg blocks can produce and save static HTML as part of the blog post, then installing many Gutenberg blocks doesn’t necessarily translate into bloat on the website on the user side, but can be kept restricted to the admin side. Hence, Gutenberg can arguably be considered a good approach to produce websites in a modular way, with a simple yet powerful user experience for creating content. Possibly the biggest drawback is the (unavoidable, but not easily so) requirement to learn React, whose learning curve is rather steep.

If React components are the basic unit for creating content in WordPress, October CMS is based on the premise that knowing good old HTML is enough for building sites. Indeed, when creating a page, we are simply presented an HTML (Markup) editor:

October CMS page creation
Creating a page in October CMS. (Large preview)

If the page were solely static HTML, then there would be no need for a CMS. Instead, October CMS pages are written using Twig templates which are compiled to plain optimized PHP code. They can select a layout to include the scaffolding of the page (i.e. repetitive elements, such as the header, footer, and so on), can implement placeholders, which are defined on the layout to allow the page to customize content, and can include partials, which are reusable chunks of code. In addition, pages can include content blocks, which are either text, HTML or Markdown files that can be edited on their own and can attach components which are functionalities implemented through plugins. And finally, for whenever HTML is not enough and we need to produce dynamic code, we can add PHP functions.

The editor is all about HTML. There is no TinyMCE textarea for adding content in a visual manner — at least not through the default experience (this functionality belongs to plugin-land). Hence, having knowledge of HTML could be considered a must for using October CMS. In addition, the several different inputs for creating content (pages, layouts, placeholders, partials, content blocks, components, and PHP functions) may be very effective, however, it is certainly not as simple as through the unified block interface from WordPress. It can even get more complex since other elements can also be added (such as static pages and menus, and snippets), and some of them, such as pages and static pages, seemingly provide the same functionality, making it confusing to decide when to use one or the other.

As a result, I dare say that while pretty much anyone can use a WordPress site from the admin side, October CMS is more developer-friendly than non-technical user-friendly, so programmers may find it a joy to use, but certain other roles (marketers, sales people, and the like) may find it non-intuitive.

Media Manager

Both WordPress and October CMS are shipped with a Media Manager which allows adding media files to the site effortlessly, supporting the addition of multiple files simultaneously through a drag-and-drop interface and displaying the images within the content area. They look and behave similarly; the only notable differences I found are that WordPress’ Media Manager allows to embed image galleries, and October’s Media Manager allows to manually create a folder structure where to place the uploaded files.

October CMS Media Manager
October CMS ships with a powerful Media Manager. (Large preview)

Since the introduction of Gutenberg, though, WordPress’ media capabilities have been enhanced greatly, enabling to embed videos, pictures and photo galleries in place as compared to within a TinyMCE textarea (which only provides a non-accurate version of how it will look like in the site), and unlocking powerful, yet easy-to-use features as shown in this video.


WordPress core uses gettext to enable the translation of themes and plugins. Starting from a .pot file containing all strings to translate, we need to create a .po file containing their translation to the corresponding language/locale, and this file is then compiled to a binary .mo file suitable for fast translation extraction. Tools to perform these tasks include GlotPress (online) and Poedit (downloadable application). Conveniently, this mechanism also works for client-side localization for Gutenberg.

Poedit allows to translate strings for themes and plugins for WordPress. (Large preview)

WordPress currently doesn’t ship any solution in core to translate content, and will not do so until Phase 4 of Gutenberg (targeted for year 2020+). Until then, this functionality is provided by plugins which offer different strategies for storing and managing the translated content. For example, while plugins such as Polylang and WPML store each translation on its own row from a custom database table (which is clean since it doesn’t mix content together, but slower since it requires an additional INNER JOIN of two tables when querying the database), plugin qTranslate X stores all translations on the same field from the original database table (faster for querying the data, but content mixed all together can produce wreckage on the site if disabling the plugin). Hence, we can shop around and decide the most suitable strategy for our needs.

October CMS doesn’t ship the multilingual functionality through its core, but as a plugin created by the October CMS team that guarantees a faultless integration into the system. From a functional point of view, this plugin delivers what it promises. From a development point of view, it is not quite ideal how this plugin actually works. In WordPress, a page is simply a post with post type “page” and there is a single translation mechanism for them, but in October CMS, there are entities “page”, “static page” and “blog post” and, even though quite similar, they require three different implementations for their translations! Then, the content from a “page” can include message codes (e.g. codes called nav.content, header.title, and so on), each of which contains its translations for all locales as a serialized JSON object in database table rainlab_translate_messages. The content from a “static page” is created into a new static file per locale, however, all translated URLs for all locales are stored not in their corresponding file but instead on the default language’s file. The content for the “blog post” is stored as a serialized JSON object with one row per locale in database table rainlab_translate_attributes and the translated URL is stored with one row per locale in database table rainlab_translate_indexes. I don’t know if this complexity is due to how the plugin was implemented or whether it is due to October CMS’ architecture. Whichever the case, this is another instance of undesired bloat on the development side.

Plugin Management

Both WordPress and October CMS offer a sophisticated plugin manager which allows to search for plugins, install new plugins, and update currently-installed plugins to their latest version — all from within the backend.

October CMS software update
October CMS enables to keep all plugins up-to-date effortlessly. (Large preview)

Dependency Management

October CMS uses Composer as the package manager of choice, enabling plugins to download and install their dependencies when being installed, thus delivering a painless experience.

WordPress, on the opposite side, hasn’t officially adopted Composer (or any PHP dependency manager) because the community can’t agree if WordPress is a site or a site dependency. Hence, if they require Composer for their projects, developers must add it on their own. With the switch to Gutenberg, npm has become the preferred JavaScript dependency manager, with a popular developer toolkit depending on it, and the client-side libraries being steadily released as autonomous packages in the npm registry.

Interaction With The Database

WordPress provides functions to retrieve database data (such as get_posts) and store it (such as wp_insert_post and wp_update_post). When retrieving data, we can pass parameters to filter, limit and order the results, in order to indicate if the result must be passed as an instance of a class or as an array of properties and others. When the function doesn’t fully satisfy our requirements (e.g. when we need to do an INNER JOIN with a custom table) then we can query the database directly through global variable $wpdb. When creating a plugin with a custom post type, the code will most likely be executing custom SQL queries to retrieve and/or save data into custom tables. In summary, WordPress attempts to provide access to the database through generic functions in the first stage, and through low-level access to the database in the second stage.

October CMS employs a different approach: Instead of connecting to the database straight away, the application can use Laravel’s Eloquent ORM to access and manipulate database data through instances of classes called Models, making the interaction with the database also be based on Object-Oriented Programming. It is high-level access; just by following the rules on how to create tables and set-up relationships among entities, a plugin can retrieve and/or save data without writing a line of SQL. For instance, the code below retrieves an object from the database through model Flight, modifies a property, and stores it again:

$flight = Flight::find(1);
$flight->name = 'Darwin to Adelaide';

Upgrading The Data Model

Another reason for WordPress’ success (in addition to not breaking backward compatibility) has been its database architecture, which was engineered to enable applications to grow over time. This objective is accomplished through “meta” properties, i.e. properties that can be loosely added to a database object at any moment. These properties are not stored in a column from the corresponding entity table (either wp_posts, wp_users, wp_comments or wp_terms), but instead as a row in the corresponding “meta” table (wp_postmeta, wp_usermeta, wp_commentmeta or wp_termmeta) and retrieved doing an INNER JOIN. Hence, even though retrieving these meta values is slower, they provide unlimited flexibility, and the application’s data model rarely needs to be re-architected from scratch in order to implement some new functionality.

WordPress database architecture
WordPress provides unlimited flexibility for upgrading the application’s data model. (Large preview)

October CMS doesn’t use meta properties but instead can store several arbitrary values, which are not directly mapped as columns in the database tables, as a serialized JSON object. Otherwise, when an object needs some new property, we need to add a new column on the corresponding table (which is the reason behind plugins User Plus and User Plus+, mentioned earlier on). To update the application’s database schema, October CMS relies on Laravel’s Migrations, which are sets of instructions to execute against the schema (such as add or drop a column, rename an index, etc) and which are executed when upgrading the software (e.g. when installing a plugin’s new version).

Headless Capabilities

Both WordPress and October CMS can be used as headless, i.e. treating the CMS as a content management system that makes content accessible through APIs, which allows to render the website on the client-side and can power other applications (such as mobile apps). Indeed, WordPress is steadily heading towards headless, since the Gutenberg content editor itself treats WordPress as a headless CMS (and, as a consequence, Gutenberg can also work with any other CMS too, as Drupal Gutenberg demonstrates).

A headless system needs to implement some API to return the data, such as REST and GraphQL. WordPress supports REST through WP REST API (merged in core), exposing endpoints under some predefined route /wp-json/wp/v2/...; October CMS supports REST through plugins RESTful and API Generator, which allow to create custom endpoints and, as a consequence, support versioning as part of the endpoint URL and can offer a better security against bots. Concerning GraphQL, WordPress supports it through WPGraphQL, while October CMS currently has no implementations for it.

Quite importantly, a headless system needs to offer powerful content management capabilities. As mentioned earlier on, WordPress has a very solid database architecture, offering a plethora of data entities (users, posts and custom posts, pages, categories, tags and custom taxonomies, comments) over which the application can be reasonably well modelled, meta properties to extend these data entities (enabling the application to upgrade its data model accordingly and without major changes), and with plugin Advanced Custom Fields filling the gap to construct relationships among the data entities. In addition, plugin VersionPress allows to version control the database content using Git. Hence, WordPress is undoubtedly a good fit for managing content, as demonstrated in several projects in the wild.

On its part, and as mentioned earlier on, October CMS can omit the database and behave as a flat-file system, or it can have a database and behave as a hybrid, storing the content from pages as static files and blog posts (and others) on the database. As a consequence, content is not centralized, and its management involves a different approach. For instance, while we can use Git to version control pages, there is no support to version control the database per se; the solution to this is to populate data into the database through Seeders which, being code, can be put under version control and executed upon deployment. In addition, October CMS doesn’t offer a baked-in database model featuring predefined data entities that can support the needs of most applications. Hence, more likely than not the application will need custom development to implement its data model, which means more work, but also means that it can be more efficient (e.g. accessing a property from a column is faster than from a row in another table through an INNER JOIN, which is the case with WordPress’ meta properties).

CLI Support

Both WordPress and October CMS can be interacted with from the console through a Command Line Interface (CLI): WordPress through WP-CLI and October CMS through Laravel’s Artisan. In addition to Laravel’s commands, October CMS implements several custom commands for updating the system, migrating the database, and others. These tools make it very convenient to access the site from outside a browser, for instance for testing purposes.

Managed Hosting

It is not a problem finding a managed hosting provider for a WordPress site: given WordPress’ market share, there are dozens (if not hundreds) of providers out there vying with each other for the business, constituting a very dynamic market. The only problem is finding the most suitable provider for our specific sites based on all of their offerings, which can vary based on price, quality, type (shared or dedicated services), bandwidth and storage size, customer support, location, frequency of renewal of equipment, and other variables which we can navigate mainly through reviews comparing them (such as this one, this one or this one).

Even though nothing near as many as WordPress, October CMS still enjoys the offering from several hosting providers, which allows for some consideration and selection. Many of them are listed as October Partners, and several others are found DuckDuckGoing, but since I haven’t found any independent review of them or article comparing them, the task of finding out the most suitable one will take some effort.

Marketplace, Ecosystem And Cost

WordPress’ commercial ecosystem is estimated to be USD $10 billion/year, evidencing how many people and companies have managed to make a living by offering WordPress products and services, such as the creation of sites, hosting, theme and plugin development, support, security, and others. Indeed, its size is so big it is even bloated, meaning that it is very common to find different plugins solving the same problem, plugins that underdeliver, underperform or have not been updated for years, and themes which seem to look-alike each other. However, when creating a new site, the size and variety of the ecosystem also means that we will most likely find at least one plugin implementing each of the required functionalities, enabling us to save money by not having to develop the functionality ourselves, and the availability of customizable themes enables to produce a reasonably distinctive-looking site with minimal effort. As a consequence, we can easily create and launch a WordPress site for less than USD $100, making WordPress a sensible option for projects of any budget.

Being relatively new (only five years so far), OctoberCMS certainly doesn’t enjoy anything near WordPress’ marketplace and ecosystem sizes, however, it has been growing steadily so its size is bound to become bigger. Currently, its marketplace boasts 600+ plugins, and only a handful of themes. Concerning plugins, the October CMS team is requesting the community to put their effort into the creation of original plugins, delivering functionality not yet provided by any other plugin.

Hence, even though 600+ plugins doesn’t sound like much, at least these translate into 600+ different functionalities. This way, even though it is not possible to choose among several vendors, at least we can expect to have those basic website features (such as blogging, comments, forum, integration with social media, e-commerce, and others) to be covered. Also, since October’s founders are personally reviewing all submitted plugins and judging them according to quality guidelines, we can expect these plugins to perform as expected. As another plus, October plugins can incorporate elements from Laravel packages (even though not all of them are compatible with October, at least not without some hacks). Concerning themes, the low number of offerings implies we will most likely need to develop our own theme by hiring a developer for the task. In fact, I dare say that the theme in October CMS will most likely be a custom development, since themes and plugins are not thoroughly decoupled (as explained earlier), with the consequence that a market for easily-swappable themes is more difficult to arise. (This is a temporary problem though: once this pull request is resolved, pages will be able to be stored in the database, and swapping themes should not disrupt functionality.)

In my opinion, because of the smaller offerings of themes and plugins, creating a simple site with OctoberCMS will be more expensive than creating a simple WordPress site. For complex sites, however, October’s better architecture (Object-Oriented Programming and Model-View-Controller paradigms) makes the software more maintainable and, as a consequence, potentially cheaper.


Being a part of and having access, WordPress’ community represents one of the most compelling reasons for using WordPress. This is not simply as a matter of size (powering nearly one third of all websites in the world, there are so many stakeholders involved with WordPress, and its community is representatively big) but also as a matter of diversity. The WordPress community involves people from many different professions (developers, marketers, designers, bloggers, sales people, and so on), from all continents and countries, speaking countless languages, from different social, educational and economic backgrounds, with or without disabilities, from corporate, not-for-profit and governmental organizations, and others. Hence, it is quite likely that, for whatever problem we encounter, somebody will be able to help on any of the support forums. And contributing to WordPress is pretty straightforward too: The Make WordPress group congregates stakeholders interested in supporting different projects (accessibility, design, internationalization, and many others) and organizes how and how regularly they communicate — mostly through some dedicated channel on its Slack workspace.

Furthermore, the WordPress community is real and tangible: it doesn’t exist just online, but it gathers offline in WordCamps and meetups all over the world; in 2018, there were a total of 145 WordCamps in 48 countries with over 45,000 tickets sold, and a total of 5,400 meetup events from 687 meetup groups. Hence, it is likely that there is a local chapter nearby which anyone can join to ask for help, learn how to use the platform, keep learning on a regular basis, and teach others as well. In this sense, WordPress is not just a CMS but, more importantly, it’s also people, and considering to leave WordPress should never be done only on its technical merits but on the power of its community, too.

Attendees at WordCamp Kuala Lumpur 2017
WordCamp Kuala Lumpur 2017 drew more than 200 attendees, coming from several countries. (Large preview)

October CMS’ community is nothing near in size or diversity as WordPress’, even though it has been growing steadily following the increasing popularity of the software. October provides a support forum to ask for help, however, it is not very active. A Slack workspace exists which is pretty active and where, quite importantly, October’s founders participate regularly, helping make sure that all enquiries are properly addressed. This channel is a great source for learning low-level tips and tricks about the software, however, it is geared towards developers mainly: There are no channels concerning accessibility, design, internationalization, and other topics as in the WordPress community, at least not yet. Currently, there are no conferences concerning October CMS, but there is Laracon, the conference for the Laravel community.

Maintainers And Governance

Can we trust that the software will be maintained in the long term, so that if we decide to start a project today, we will not need to migrate to some other platform down the road? How many people are taking care of developing the software? And who is deciding in what direction the software moves towards?

Powering one-third of all sites in the world, WordPress is not short of stakeholders contributing to the software; hence we need not fear that the software will fall into decay. However, WordPress is going through internal deliberations concerning its governance model, with many members of the community expressing that decisions concerning WordPress’s direction are being taken unilaterally by Automattic, the company running Center stage of this perception was the decision to launch Gutenberg, which many members disagreed with, and which suffered a lack of proper communication by the project leads during its development and release. As a consequence, many community members are questioning the role of “benign dictator”, which has been historically granted to WordPress’ founder and Automattic’s CEO Matt Mullenweg, and researching different governance models to find a more suitable one for the future of WordPress. It is yet to be seen if this quest produces any result, or if the status quo perseveres.

Decisions concerning October CMS’ direction are mainly taken by founders Alexey Bobkov and Samuel Georges and developer and community manager Luke Towers, which keep the project going strong. October CMS doesn’t have the luxury of having a governance problem yet: Its current concern is how to make the project sustainable by generating income for the core software’s maintainers.


WordPress documentation in its own site is not extremely comprehensive, but it does the job reasonably well. However, when taking all of the documentation about WordPress into account from all sources, such as general sites (Smashing Magazine, CSS tricks, and many others), specialized sites (WPShout, WPBeginner, and many others), personal blogs, online courses, and so on, there is practically no aspect of dealing with WordPress that hasn’t already been covered.

October CMS doesn’t enjoy anything near the many third-party courses, tutorials or blog posts about it as much as WordPress does, however, the documentation on its site is reasonably comprehensive and certainly enough to start coding. October founders also regularly add new documentation through tutorials. One aspect that I personally enjoyed is the duplication of Laravel’s documentation into October’s documentation for everything of relevance, so the reader must not fill the gaps by him/herself and having to guess what is October’s domain and what is Laravel’s. However, this is not 100% perfect. October’s documentation uses terms originating from Laravel, such as middleware, service containers, facades and contracts, without adequately explaining what these are. Then, reading Laravel’s documentation in advance can be helpful (luckily, Laravel’s documentation is decidedly comprehensive, and Laravel’s screencasts, Laracasts, are another great source of learning, not just concerning Laravel but web development in general).


I set out to discover what features may be enticing for developers looking for alternatives to WordPress by comparing WordPress to a similar CMS, which I defined as being free and open source, based in PHP and producing dynamic content, and enjoying the support from some community. From the CMSs fulfilling these conditions, I chose October CMS for the comparison because of the knowledge I got about it, and because I appreciated its clean and modular coding approach as provided by Laravel, which could offer a fresh and modern perspective for building sites.

This article did not intend to pick a winner, but simply analyze when it makes sense to choose one or the other CMS, highlighting their strengths and weaknesses. There is no “best” CMS: only the most suitable CMS for a specific situation. Furthermore, anyone looking for a CMS to use on a particular project with a specific team and given a certain budget, should do some research and compare all the offerings out there to find out which one is most suitable for the particular context. It’s important not to limit to a few CMSs as I’ve done here in this article, but instead give a chance to all of them.

On a personal note, as a developer, what I found in October CMS is really appealing to me, mostly its ability to build modular applications as provided through Laravel. I would certainly consider this CMS for a new website. However, in the process of writing this article I also “rediscovered” WordPress. Being so popular, WordPress receives more than its fair share of criticisms, mostly concerning its old codebase and, since recently, the introduction of Gutenberg; however, WordPress also has certain excellent features (such as its super-scalable database model) which are seldom praised but should be taken into account too. And most importantly, WordPress should not be considered on its technical aspects alone: in particular, the size of its community and ecosystem places it a level or two above its alternatives. In a nutshell, some projects may benefit from sticking to WordPress, while others may better rely on October CMS or another platform.

As a final note, I would like to remark that exploring how another CMS works is a very rewarding activity on its own, independent of the decision reached concerning whether to use that particular CMS or not. In my case, I had been working for years on WordPress alone, and delving into October CMS was very refreshing since it taught me many things (such as the existence of PHP Standards Recommendations) which I had not been exposed to through WordPress. I may now decide to switch CMSs, or stick to WordPress knowing how to produce better code.

Further Reading on SmashingMag:

Smashing Editorial (rb, ra, yk, il)
What’s New For Designers, March 2019

This month we are all about tools that make life easier. And that’s exactly what you’ll find in this roundup of new things for designers – from color tools to workflow enhancers to code snippets to fonts that will make you smile, everything here can make your design life easier.

If we’ve missed something that you think should have been on the list, let us know in the comments. And if you know of a new app or resource that should be featured next month, tweet it to @carriecousins to be considered! is a free tool to help you build quick charts for digital use. This tool is designed to replace Google Image Charts API, which is slated for shutdown this month. Here’s how it works: Chart images are defined by their URLs. Each URL contains a JSON object that includes all the data and display options. These options follow the popular open-source graphing library Chart.js. Everything is customizable so you can use your color and font palettes to make the chart completely yours and you can embed charts in emails, SMS, reports and pretty much anything else you need.

This Person Does Not Exist

This Person Does Not Exist is a collection of randomly generated headshots for projects. The images are computer generated and are not of real people. Refresh for new faces and use in projects. The fun thing about this project is how real the images look – although you do occasionally come across some glitchy ones.

IVID <Interactive Video Player>

IVID is an interactive video player for modern browsers that comes with easy setup and use. It’s plug and play! The full VanillaJS web component player allows you to skip to the next video, uses inherited HTML5 video properties, has on-screen controls that are customizable, keyboard controls and a simple setup.


Ludwig is a toolbox that lets you train and test deep learning models without writing code. It uses a data type-based approach that works for users at all levels. Plus, it has more flexible tools for experienced users that want more control over model building and training. This tool is available under an open source license.

CSS Color Wheel

CSS Color Wheel is a new take on the classic model with an animated color wheel with different styles. The pen by Louis Hoebregts is interesting, fun and informational. (Make sure to change the curves for different versions.)

Color Harmonies

Color Harmonies is another take on the color wheel with easy to see changes that you can adjust on screen to make great combinations for projects. (The cool paint chip design is fun as well.) Change the number of colors, hue, saturation and light and watch colors change before your eyes.


Capture-Website is a screenshot tool to help you grab website images. You have a few lines of code to work with and then you can grab full screenshots of pages with ease.

Charts for Sketch

Charts for Sketch is a massive collection of charts, graphs and diagrams for Sketch. Files include symbols, resizing constraints and text and layer styles for ease of use. Charts are grouped in categories such as area charts, bar chart and bubble charts and include three color schemes each (standard, black and wireframe).


IconSVG is a collection of SVG icons that you can customize – color, ends and joins, size and stroke width – to download and use in projects. The nice thing about these icons is that you can make small changes that really make each icon your own and then flip to different icons while keeping those customizations.

Lunar Popup

Lunar Popup is an HTML/CSS animated popup builder for your website (and it’s free). Every popup is responsive, customizable and works with layers for easy building and deployment. You can use animations or not and installation only takes three steps: add the libraries, copy the markup from the modal you need, initialize and go!

Third Party Web

Third Party Web is a good learning resource. It is a summary of which third party scripts are responsible for the most excessive JavaScript execution on the web. The project is designed with four goals: to quantify the impact of third-party scripts on the web, identify the ones that are the bulkiest, give developers information about these scripts and incentives responsible third party script behavior.

Scandinavian Houses Vectors

Scandinavian Houses Vectors is a fun collection of hand-drawn house illustrations. The collection includes 30 building options inspired by Scandinavian architecture. Each illustration includes a filled and outlined style option.

Absurd Illustrations

Absurd Illustrations is a collection of hand-drawn elements with a more aloof style for landing pages and other projects. They are designed to highlight creativity and imperfection. The collection continues to grow.

Unlimited Email Tracker

Unlimited Email Tracker lets you track opens and clicks in Gmail. You can see responses to emails in real time, get histories and do it all from a browser extension that’s free. This tool adds functionality of third-party email services to your inbox.

Haiku Animator

Haiku Animator, formerly Haiku for Mac, now works on all devices. The tool allows you to animate elements for any iOS, Android or web codebase. You can also use Figma, Sketch or Illustrator to sync design assets and create Lottie files without After Effects. This is a premium tool, but comes with a free trial.


DeckRobot uses artificial intelligence to make your PowerPoint, Google Slides or Keynote slides look better. You can upload slides you have used in the past and the AI will learn your style and allow you to replicate it in a number of ways. Then you can export and edit or format your application of choice.


AXDraft is a startup’s best friend. This website includes plenty of starter legal documents to help you meet business goals. From privacy policies to NDAs to employee onboarding documents, you can find the tools you need to get started here. And the documents are free.


ActionDesk is a productivity tool that turns spreadsheets into powerful automations. Import data and do everything from provide customer support to operations help to analyzing data. It integrates with plenty of tools, such as Google Sheets, CRMs and Typeform.

Tutorial: Text Trail Effect

The Text Trail Effect tutorial in the Codrops playground helps you learn how to make a nifty text effect for slideshows. The tutorial includes five demos for variations of the effect.

Interactive Typography Cheatsheet

Interactive Typography Cheatsheet can help you up your font-knowledge game. Click on elements in the cheatsheet to learn the names of different parts of letterforms. This game-style option is a great way to make sure you know the language of type so you can better communicate with creative teams.

Iknu Font

Iknu calls itself a “spiritual theme font.” The character set includes uppercase and numbers and is designed for projects with a specific intent. It’s interesting and different, which could make it a good choice from some branding or creative retail designs.


Karton is a free, handwriting style typeface that’s appropriate for display and simple uses. It includes an uppercase character set with some numerals and alternates.


OHNO is a fun block, slab-style typeface for display purposes. It includes only uppercase letters and has a somewhat futuristic look and feel.


Throne is a fun font for all the Game of Throne fans out there. The SVG and regular font collection has a hand painted style with a full set of uppercase characters, numbers and symbols. It is most appropriate for display.


Vistol is a sans serif typeface family in 18 styles with an upright stance and some italics. This family includes some fun lines and exceptional ligatures. The thin styles are also especially nice because they maintain readability.

Add Realistic Chalk and Sketch Lettering Effects with Sketch’it – only $5!

p img {display:inline-block; margin-right:10px;}
.alignleft {float:left;}
p.showcase {clear:both;}
body#browserfriendly p, body#podcast p, div#emailbody p{margin:0;}