Collective #313
Unsplash API * Design Better Data Tables * Fair Analytics * Pingy CLI * Archetype * Button Styles * gofccyours…
Unsplash API * Design Better Data Tables * Fair Analytics * Pingy CLI * Archetype * Button Styles * gofccyours…
When you develop a game, you need to sprinkle conditionals everywhere. If Pac-Man eats a power pill, then ghosts should run away. If the player has low health, then enemies attack more aggressively. If the space invader hits the left edge, then it should start moving right. Usually, these bits of code are strewn around, embedded in larger functions, and the overall logic of the game is difficult to see or reuse to build up new levels.
Apple’s GameplayKit has several algorithms and data structures that make it easier to follow game development best practices. One of them, GKRuleSystem, lets you build up complex conditional logic from smaller pieces. By structuring your code around it, you’ll create rules that are easier to change or reuse for new levels. In this article, we’re going to take typical game logic code and learn how to represent it as a rule system.
I love puzzle games. The good ones start by teaching you how the game world works. Then, along the way, you discover new capabilities and apply them to harder challenges. The developer needs to balance each level to make sure that you never get bored or want to give up. Two of my favorites are Monument Valley5 and Hundreds6. A newer iOS game, Mini Metro7, is a subway simulation game and looks perfect for me. The developers say players need to “constantly redesign their line layout to meet the needs of a rapidly-growing city,” which is a good description of the progressive challenges I’m looking for.
These games have lots of levels, each one a little harder or with a new twist. You’d think that it would be easy to build up the code for successive levels from previous ones, but as you’ll see, it can be harder than it looks.
I recently started to make a game like this called Puz-o-Mat. The Puz-o-Mat is a box with five colored buttons on it. The goal of each level is to get all of the buttons to light up by discovering the pattern that satisfies the rules of the level. Puz-o-Mat will give you feedback if you are on the right track and buzz and flash its lights to reset the level if you make a mistake.
To see why we might need a rule system, it’s useful to try to implement the game levels as a set of evaluation functions first. You can follow the code below in a Playground on GitHub12.
The goal of the first level of Puz-o-Mat is to press each button once. When you press a button, it lights up to let you know that you are on the right track. If you press one that is lit up already, the game will reset and the lights will turn off. When you tap each button once, you win the level.
Since we have a finite and defined set of game outcomes, we can just list them in an enum called GameOutcome:
enum GameOutcome: String { case win case reset }
And then, define an evaluation function that returns a GameOutcome given the buttons. If there is no outcome yet, it returns nil.
func evaluateLevel001(buttons: [Button]) -> GameOutcome? { var foundUntappedButton = false for button in buttons { if button.tapCount >= 2 { return .reset } else if button.tapCount == 0 { foundUntappedButton = true } } if !foundUntappedButton { return .win } return nil }
It’s not too hard to understand, but it’s concerning that we need a loop and three conditionals to describe the easiest level.
The second level of the game is a little harder to complete. Instead of pressing any button you want, you have to press them in order. Here’s how to do that:
func evaluateLevel002(buttons: [Button]) -> GameOutcome? { var foundUntappedButton = false for button in buttons { if button.tapCount >= 2 { return .reset } else if button.tapCount == 0 { foundUntappedButton = true } else if button.tapCount == 1 && foundUntappedButton { return .reset } } if !foundUntappedButton { return .win } return nil }
This code has just one extra else if statement. It would be nice to share some code with the previous evaluator.
As we go on from level to level, you’ll find that a line here or there may be duplicated, but it’s hard to come up with a function that handles all levels. You could do it by taking another parameter, but this game is going to have 100’s of levels; we can’t keep adding parameters and extra conditionals for each of the variations.
Moving on, the next level needs you to tap the buttons in reverse order. The function looks exactly like the last one, except the loop looks like this:
for button in buttons.reversed()
Again, a lot of code is the same, but it’s awkward to reuse.
There are some patterns, though.
Even though there is a pattern to the level game logic, all of the parts are mixed together and aren’t easily separated for reuse.
Using this insight we could try to restructure the code. A promising place to start is by pulling out the conditionals into separate functions. As we’ll see later, this is the design of GameplayKit’s rule system, but we can get part of the way there by playing with this code first. Then, we’ll convert our result to GameplayKit.
First, let’s use a type alias to define what a rule is:
typealias Rule = (_: [Button]) -> GameOutcome?
A rule is a function that takes the array of buttons and responds with an outcome if it knows what to do or, nil if not.
Many levels may limit the number of taps on a button, so we’ll make a function to return a rule that checks buttons against a tap limit:
func makeTapLimitRule(maxTaps: Int) -> Rule { return { buttons in if (buttons.first { $0.tapCount >= maxTaps }) != nil { return .reset } return nil } }
makeTapLimitRule takes a parameter, which is the number of taps to check for, and returns a closure that tells you if the game should reset or not.
Here’s one that can check if all buttons have been tapped once.
func makeAllTappedRule() -> Rule { return { buttons in if (buttons.first { $0.tapCount != 1 }) == nil { return .win } return nil } }
To use these buttons in our first level, we return an array of rules:
func rulesForLevel001() -> [Rule] { return [ makeTapLimitRule(maxTaps: 2), makeAllTappedRule(), ] }
Then, we need loop through all of the rules and run them so that they can check their conditional against the button state. If any of the rule functions return a GameOutcome, we’ll return it from the evaluator. If none are true, we’ll return nil.
13The code is:
func evaluate(buttons: [Button], rules: [Rule]) -> GameOutcome? { for rule in rules { if let outcome = rule(buttons) { return outcome } } return nil }
Our first level is simply:
func evaluateLevel001WithRules(buttons: [Button]) -> GameOutcome? { return evaluate(buttons: buttons, rules: rulesForLevel001()) }
Following this train of thought, you could expand the rules to take in more parameters or check more states. Each level is expressed as a list of rules that produce some outcome. Rules are separately encapsulated and easily reused.
This is such a common way of structuring algorithms in game logic, that Apple provides a set of classes in GameplayKit that we can use to build games in this style. They are collectively known as the Rule System classes and are primarily implemented in GKRuleSystem14 and GKRule15.
Using these Rule System classes hides all of the complexity of the evaluator, but more importantly, delivers much more power than our simple one.
In the GameplayKit rule system, you need to define three things:
GameOutcome enum we used in our examples above.GKRule objects to evaluate. They each provide a conditional to check against the state and an action to perform if the conditional is true.
16To run the algorithm, we need to:
GKRuleSystem object.evaluate function.This diagram shows how the individual objects interact over time:
17The code to complete this interaction is straightforward:
func evaluate(state: [String: Any], rules: [GKRule]) -> GameOutcome? { let rs = GKRuleSystem() rs.state.addEntries(from: state) rs.add(rules) rs.evaluate() if rs.facts.count > 0, let fact = rs.facts[0] as? NSString { return GameOutcome(rawValue: fact) } return nil }
Note: GameplayKit classes were designed for Objective-C, which is why I had to derive GameOutcome from String and use the enums as rawValues.
This is a very simple use of GKRuleSystem, and it’s fine if you don’t need to do this in the context of an action game (and need to keep up with a high frame rate). In that case, you can use the fact that the rule system state property is a mutable dictionary that you can alter directly rather than recreate. You could also reuse rule system objects with rules set up.
This is similar to our evaluator from the last section, but this one can operate over a more complex state object. Also, GKRuleSystem.evaluate() is more sophisticated than a loop over the rules. You can provide rule precedence, fact retraction (rules that reverse fact assertions), and find out which exact rules were invoked, among other features.
The last step is converting our Rule functions to GKRule. You could derive GKRule subclasses for each rule, but for most applications, the GKRule constructors will be good enough.
One of the constructors18 takes two blocks:
init(blockPredicate predicate: @escaping (GKRuleSystem) -> Bool, action: @escaping (GKRuleSystem) -> Void)
The first block is a conditional that looks at the state, while the second block is called to assert a fact if the first block returns true.
It’s a little cumbersome to use, but here’s our two tap reset rule:
GKRule( blockPredicate: { rs in guard let buttons = rs.state["b"] as? [Button] else { return false } return (buttons.first { $0.tapCount >= 2 }) != nil }, action: { rs in rs.assertFact(GameOutcome.reset.rawValue) })
During the GKRuleSystem evaluation, this object will have its first block called to see if the rule should be applied. The block could refer to external sources, but the most common thing is to look at the passed in rule system’s state property and check it for a condition. If the first block returns true, then the second one is called. It could also do anything, but it’s expected that it would assert or retract facts.
This is a good constructor to use if you have complex state and conditionals, or if you are using information outside of the rule system to implement rules. Since the expectation and normal case is to use the state and make facts, there is a more direct constructor to use.
If you can express your conditional as an NSPredicate against the state dictionary, GKRulehas a constructor to assert facts directly19 based on them. It’s:
init(predicate: NSPredicate, assertingFact fact: NSObjectProtocol, grade: Float)
Let’s extend GameOutcome with a function that creates GKRule objects for us.
extension GameOutcome { func assertIf(_ predicateFormat: String) -> GKRule { let pred = NSPredicate(format: predicateFormat) return GKRule( predicate: pred, assertingFact: self.rawValue, grade: 1.0) } }
Our rules function is just:
func predicateRulesForLevel001() -> [GKRule] { return [ GameOutcome.reset.assertIf("ANY $b.tapCount == 2"), GameOutcome.win.assertIf("ALL $b.tapCount == 1"), ] }
And the level evaluator becomes a one-liner:
func evaluateLevel001WithPredicateRules(buttons: [Button]) -> GameOutcome? { return evaluate(state: ["b": buttons], rules: predicateRulesForLevel001()) }
The only thing you need for a new level is a new list of GKRules. We have gone from an imperative level description where we need to give every step to a declarative one where we provide queries and outcomes.
Another benefit of this approach is that the predicates are serializable, so they can be stored in external files or sent over the network. With just a few more lines of code, we could move the predicate and enum to a .plist file and load them instead of hard-coding them. Then, a game designer on your team could make or tweak levels without editing code. The main coding work would be to add more state and outcomes.
20In this example, facts are mutually exclusive; you can either win or reset, but not both. But, this is not a restriction of rule systems. If it makes sense for multiple facts to be asserted, then you may do so, and the code that checks the facts will need to reconcile them. One example is that we could treat each button’s light as a fact (on or off) and there would be a fact asserted for each button as the game was played. In that case, there could certainly be many facts asserted at the same time.
Finally, you may have noticed that facts are asserted with a grade. This grade is a number from 0 to 1 that you can think of as a probability that this fact is correct. We used 1 to indicate certainty, but you could use lower numbers to indicate that the fact only has a probability of being true. These probabilities can be combined and, using random numbers, we could pick among the asserted facts and have emergent, rather than deterministic, game behavior. This is known as Fuzzy Logic, and I’ll cover that in a future article.
All of the code in this article is available in a three-page Playground on GitHub22.
(da, yk, aa, il)
As a designer, you will be facing more demands and opportunities to work with digital systems that embody machine learning. To have your say about how best to use it, you need a good understanding about its applications and related design patterns.
This article illustrates the power of machine learning through the applications of detection, prediction and generation. It gives six reasons why machine learning makes products and services better and introduces four design patterns relevant to such applications. To help you get started, I have included two non-technical questions that will help with assessing whether your task is ready to be learned by a machine.
Big data and big promises. We are expecting a great many things to happen once the big data deluge has been funnelled into a nurturing stream of bits. Data can be used in many ways. One is to build smart products, and another is to make better design and business decisions. The latter also, ultimately, trickle into products.
Machine learning is a very promising approach radically shaping future product and service development. Machine learning is a branch of artificial intelligence. It employs many methods: Deep learning and neural networks are two well-known instances.
Machine learning means that, instead of programmers providing computers with very detailed instructions on how to perform a task, computers learn the task by themselves. A recent, remarkable milestone was when Google’s AlphaGo software learned to master the game of Go at the level of a world champion!
Pretty much anything that a normal person can do in <1 sec, we can now automate with AI.
– Andrew Ng (@AndrewYNg), 19 October 20165
This story will lead you into problem discovery through examples of what sorts of problems machines today readily chew on. A basic understanding of machine learning, commonly known as ML, will help. If you are unfamiliar with ML, I suggest you read my friendly introduction6 to the topic or Nvidia’s clear explanation7 of the differences between ML, AI and deep learning.
As a designer, you will be facing more demands and opportunities to work with digital systems that embody machine learning. As the hype around machine intelligence intensifies, this will lead to technology-driven pressure to extensively utilize machine learning. This may happen with little understanding of its actual benefits and its impact on product desirability and customer experience.
As a designer, to have your say about any plans for machine intelligence and how it is best implemented on the human interface layer, you need to know what it can do and how digital services can utilize it. This post provides an overview of applications, with concrete examples, as well new design guidelines that you can put to work. This will help with making actual design decisions and identifying the right design patterns, including situations when no directly applicable solution exists and you must transfer ideas across domains.
To exploit the capability of machine learning, you must grasp the nature of a task as a computer might see it. The concept I’ll use to describe it is the core problem of learning. This refers to defining what exactly we would like the computer to learn in order for it to complete the task we have assigned to it. These goals are not always evident at the practical, holistic level of a finished product (say, Tesla’s autopilot function). This story will help you to see what goes on under the surface.
Say you want the computer to drive your car.
This is a very high abstraction level learning goal. We need to go further down and break it into smaller chunks. We need to ask questions such as, can the computer accelerate and decelerate, and can the machine recognize red and green lights? To identify the core problem is already a move towards understanding whether the overarching task is a good fit for a machine to learn. Once the core problem of learning is defined well, then it is possible to say whether the ML computers of today can solve it with adequate accuracy and in a decent amount of time. This is what matters most for actually making the application work.
Suppose you manage to teach some skill to a machine. How would your product or service benefit from it? Here are six possible benefits:
In rare cases, machine learning might enable a computer to perform tasks that humans simply can’t perform because of speed requirements or the scale of data. But most of the time, ML helps to automate repetitive, time-consuming tasks that defy the limits of human labor cost or attentional span. For instance, sorting through recycling waste 24/7 is more reliably and affordably done by a computer.
In some areas, machine learning may offer a new type of expert system that augments and assists humans. This could be the case in design, where a computer might make a proposal for a new layout or color palette aligned with the designer’s efforts. Google Slides already offers this type of functionality through the suggested layouts feature11. Augmenting human drivers would improve traffic safety if a vehicle could, for example, start braking before the human operator could possibly react, saving the car from a rear-end collision.
12Google Slides provides design assistance via the Explore function. Right pane demonstrates the variations it has generated from elements initially composed by the user.
In 2016, the most celebrated milestone of machine learning was AlphaGo’s victory14 over the world champion of Go, Lee Sedol15. Considering that Go is an extremely complicated game to master, this was a remarkable achievement. Beyond exotic games such as Go, Google Image Search is maybe the best-known application of machine learning. Search feels so natural and mundane when it effectively hides away all of the complexity is embeds. With over 30 billion search queries every day, Google Image Search constantly gets more opportunities to learn.
There are already more individual machine learning applications than are reasonable to list here. But a major simplification16 is not sufficient either, I feel. One way to appreciate the variety is to look at successful ML applications from Eric Siegel’s book Predictive Analytics from 2013. The listed applications fall under the following domains:
His cross-industry collection of examples is a powerful illustration of the omnipresence of predictive applications, even though not all of his 147 examples utilize machine learning as we know it. However, for a designer, knowing whether your problem domain is among the nine listed will give an idea of whether machine learning has already proven to be useful or whether you are facing a great unknown.
As I see it, the power of learning algorithms comes down to two major applications: detection and prediction. Detection is about interpreting the present, and prediction is about the way of the future. Interestingly, machines can also do generative or “creative” tasks. However these are still a marginal application.
17When you combine detection and prediction, you can achieve impressive overall results. For instance, combine the detection of traffic signs, vehicles and pedestrians with the prediction of vehicular and pedestrian movements and of the times to vehicle line crossings, and you have the makings of an autonomous vehicle!
This is my preferred way of thinking about machine learning applications. In practice, detection and prediction are sometimes much alike because they don’t yet cut into the heart and bones of machine learning (recall the basics of machine learning19), but I believe they offer an appropriate level of abstraction to talk about machine learning applications. Let’s clarify these functions through examples.
There are at least four major types of applications of detection. Each deals with a different core learning problem. They are:
Text and speech are the most natural interaction and communication methods. Thus, it has not been feasible in the realm of computers. Previous generations of voice dialling and interactive voice response systems were not very impressive. Only in this decade have we seen a new generation of applications that take spoken commands and even have a dialogue with us! This can go so smoothly that we can’t tell computers and humans apart in text-based chats, indicating that computers have passed the Turing test20.
Dealing with speech, new systems such as personal assistant Siri or Amazon’s Echo device are capable of interpreting a wide range of communications and responding intelligently. The technical term for this capability is natural language processing (NLP). This indicates that, based on successful text and speech detection (i.e. recognition), computers can also interpret the meaning of words, spoken and written, and take action.
Text interpretation enables equally powerful applications. The detection of emotion, or sentiment, from text means that large masses of text can be automatically analyzed to reveal what people in social media think about brands, products or presidential candidates. For instance, Google Translate just recently witnessed significant quality improvements21 by switching to an ML approach to translations.
22Jeff Bezos says the Echo “isn’t about” getting people to shop on Amazon, and he may be right https://t.co/UuDdAms0Yt24pic.twitter.com/erodS8hoJg25
– BI Tech (@SAI) 13 February 201726
Computer vision gives metaphorical eyes to a machine. The most radical example of this is a computer reconstruction of human perception from brain scans! However, that is hardly as useful an application as one that automates the tagging of photos or videos to help you explore Google Photos or Facebook. The latter service recognizes faces to an even scary level of accuracy.
Image interpretation finds many powerful business applications in industrial quality control, recording vehicle registration plates, analyzing roadside photos for correct traffic signs, and monitoring empty parking spaces. The recent applications of computer vision to skin cancer diagnosis27 have actually proven more proficient than human doctors, leading to the discovery of new diagnostic criteria!
28Speech was already mentioned, but other audio signals are also well detected by computers. Shazam and SoundHound have for years provided reliable detection of songs either from a recording fragment or a sung melody. The Fraunhofer Institute developed the Silometer30, an app to detect varieties of coughs as a precursor to medical diagnosis. I would be very surprised if we don’t see many new applications for human and non-human sounds in the near future.
Given that computers are seeing and hearing what we do, it is not surprising that they have became capable of analyzing and detecting human behavior and identity as well — for instance, with Microsoft Kinect recognizing our body motion. Machines can identify movements in a football game to automatically generate game statistics. Apple’s iPad Pro recognizes31 whether the user is using their finger or the pencil for control, to prevent unwanted gestures. A huge number of services detect what kind of items typically go together in a shopping cart; this enables Amazon to suggest that you might also be interested in similar products.
In the world of transportation, it would be a major safety improvement if we could detect when a driver is about to fall asleep behind the steering wheel, to prevent traffic accidents. Identity detection is another valuable function enabled by several signals. A Japanese research institute has developed a car seat32 that recognizes who’s sitting in it. Google’s reCAPTCHA33 is a unique system that tells apart humans from spam bots. Perhaps the most notorious example of guessing people’s health was Target’s successful detection of expectant mothers34. This was followed by a marketing campaign that awkwardly disclosed the pregnancy of Target customers, resulting in much bad publicity.
35Machine learning is also used to detect and prevent fraudulent, abusive or dangerous content and schemes. It is not always major attacks; sometimes it is just about blocking bad checks or preventing petty criminals from entering the NFL’s Superbowl arena36. The best successes are found in anti-spam; for instance, Google has been doing an excellent job for years of filtering spam from your Gmail inbox.
I will conclude with a good-willed detection example from the normal human sphere. Whales can be reliably recognized37 from underwater recordings of their sounds — once more, thanks to machine learning. This can help human-made fishing machinery to avoid contact with whales38 for their protection.
39Text and speech prediction have opened up new opportunities for interaction with smart devices. Conversational interfaces are the most prominent example of this development, but definitely not the only one. As we try to hide the interface and underlying complexity from users, we are balancing between what we hide and what we reveal. Suggested features help users to discover what the invisible UI is capable of.
Graphical user interfaces (GUIs) have made computing accessible for the better part of the human race that enjoys normal vision. GUIs provided a huge usability improvement in terms of feature discovery. Icon and menus were the first useful metaphors for direct manipulation of digital objects using a mouse and keyboard. With multitouch screens, we have gained the new power of pinching, dragging and swiping to interact. Visual clues aren’t going anywhere, but they are not going to be enough when interaction modalities expand.
Haptic interaction in the first consumer generation of wearables and in the foremost conversational interfaces presents a new challenge for feature discovery. Non-visual cues must be used that facilitate the interaction, particularly at the very onset of the interactive relationship. Feature suggestions — the machine exposing its features and informing the user what it is capable of — are one solution to this riddle.
In the case of a chatbot employed for car rentals, this could be, “Please ask me about available vehicles, upgrades and your past reservations.”
Specific application areas come with specific, detailed patterns. For instance, Patrick Hebron’s recent ebook41 from O’Reilly contains a great discussion of the solutions for conversational interfaces.
Several generations of TV watchers have been raised to watch weather forecasts for fun, ever since regular broadcasts began after the Second World War42. The realm of prediction today is wide and varied. Some applications may involve non-machine learning parts that help in performing predictions.
Here I will focus on the prediction of human activities, but note that the prediction of different non-human activities is currently gaining huge interest. Predictive maintenance of machines and devices is one such application, and more are actively envisioned as the Internet of Things generates more data to learn from.
Predicting different forms of human behavior falls roughly into the following core learning challenges and applications:
Different types of recommendations are about predicting user preferences. When Netflix recommends a movie or Spotify generates a playlist of your future favorite music, they are trying to predict whether you will like it, watch it or listen through to the end of the piece. Netflix is on the lookout for your rating of the movie afterwards, whereas Spotify or Pandora might measure whether you are returning to enjoy the same song over and over again without skipping. This way, our behaviors and preferences become connected even without our needing to express them explicitly. This is something machines can learn about and exploit.
In design, predicting which content or which interaction models appeal to users could give rise to the personalization of interfaces. This is mostly based on predicting which content a user would be most interested in. For a few years now, Amazon has been heavily personalizing the front page, predicting what stuff and links should be present in anticipation of your shopping desires and habits.
Recommendations are a special case of predicting individual behavior. The scope of predictions does not end with trivial matters, such as whether you like Game of Thrones or Lady Gaga. Financial institutions attempt to predict who will default on their loan or try to refinance it. Big human-resource departments might predict employee performance and attrition. Hospitals might predict the discharge of a patient or the prognosis of a cancer. Rather more serious humans conditions, such as divorce, premature birth and even death within a certain timeframe, have been all been predicted with some success. Of course, predicting fun things can get serious when money is involved, as when big entertainment companies try to guess which songs43 and movies will top the charts to direct their marketing and production efforts.
44The important part about predictions is that they lead to individual assessment that are actionable. The more reliable the prediction and the weightier the consequences, the more profitable and useful the predictions become.
Predicting collective behavior becomes a generalization of individuals but with different implications and scope. In these cases, intervention is only successful if it affects most of the crowd. The looming result of a presidential election, cellular network use or seasonal shopping expenditure can all be subject to prediction. When predicting financial risk or a company’s key performance indicators, the gains of saving or making money are noticeable. J.P. Morgan Chase was one of the first banks to increase efficiency by predicting mortgage defaulters (those who never pay back) and refinancers (those who pay back too early). On the other hand, the recent US presidential election is a good reminder that none of this is yet perfect.
In a close resemblance to tools for antivirus and other present dangers, future trouble is also predictable. Predictive policing is about forecasting where street conflicts might happen or where squatters are taking over premises, which would help administrators to distribute resources to the right places. A similar process is going on in energy companies, as they try to estimate the capacity needed to last the night.
Once a computer gets to know you and to predict your desires and preferences, it can start to serve you in new, more effective ways. This is personalization, the automated optimization of a service. Responsive website layouts are a crude way of doing this.
The utilization of machine learning features with interfaces could lead to highly personalized user experiences. Akin to giving everyone a unique desktop and homescreen, services and apps will start to adapt to people’s preferences as well. This new degree of personalization presents opportunities as well as forces designers to flex their thoughts on how to create truly adaptive interfaces that are largely controlled by the logic of machine learning. If you succeed in this, you will reward users with a superior experience and will impart a feeling of being understood46.
47Currently, personalization is foremost applied in order to curate content. For instance, Amazon carefully considers which products would appeal to potential buyers on its front page. But it will not end with that. Personalization will likely lead to much bigger changes across UIs — for instance, even in the presentation of the types of interactive elements a user likes to use.
Say you are now convinced that your users and customers would benefit from a ML-boosted service. Next, you must consider the business and technology perspectives. The first question to ask is, If ML works at least as well as you want, what value would it add to your product? Be honest: Traditional business logic applies here, too.
If machine learning doesn’t create value, then it is probably a waste of resources. Marketing people might fancy your new implementation, but the business folks will not necessarily fund your next machine learning experiment unless you have a business case for why machine learning would improve the customer experience or revenue directly.
OK, suppose you’ve cleared the viability check box. I expect you also have at least a hypothesis of the core learning challenge. This might be, for example, Can a computer learn to predict when the user would need to take an umbrella or a waterproof jacket when they leave the house in the morning.
Next, you’ll need to figure out whether you can expect the machine to learn the job you wish to get done. We’ve come to matters of data. Data broadly refers to any information you can feed the computer: weather data, shopping data for umbrellas and waterproof gear, social media updates and so forth.
Here are two simple diagnostic questions to assess the data situation:
The first question looks easy but is difficult to answer in advance. You need both good quality and a sufficient quantity of data. Some signals are noisy (that is, have poor quality). You might need more examples in these cases than in others. Some features are very prominent and easy to learn, such as in the case of detecting nighttime or daytime in photos. This can be solved with as few as 30 training images49!
Typical applications require thousands of instances of input data and possibly the desired answers. The more complicated the task, the more material will be needed. AlphaGo learned to master the game after analyzing a staggering 30 million games50 and then playing some against itself! This is likely excessive in most situations, but it gives you an idea of the scale of what computers can and may need to swallow — from 30 to 30 million. This is one of the reasons why increasing computing capacity and certain hardware really makes computers smarter by speeding up the learning of truly big data.
51The second question is, Are there patterns in the teaching material that can be recognized by a human expert? If a human can do the task, then there’s a fair chance that there are regularities in the data that might be picked up by the computer as well.
If you have a positive answer to least one of the questions, you can go forward with some confidence that machine learning might indeed be of use to you. What you’d do after this discovery is mostly beyond the scope of this article.
In order to build a machine learning solution, it is best to boldly go and prototype your desired functionality. If you have enough samples, your data scientist teammate will quickly discover whether the machine shows proficiency in the learning task (particularly if you use scalable could computing). If not, you’ll need to get more data.
What follows is a series of iterations on the learning mechanisms and a process of integrating it with the product or service. The appearance of intelligent applications can be achieved by carefully putting together several pieces of machine learning and even conventional programming. This is called an ensemble approach. What at best appears as a seamless interactive experience for the user may in fact be the product of a very complex rubric of different machine learning components working together.
Human intelligence is heavily needed to tweak the learning algorithms. Such was the architecture underlying Watson54, the Jeopardy-winning artificial intelligence system created by IBM and the algorithm that claimed the Netflix Prize55. In the latter competition, several teams combined their individual tweaks during the final months of the competition to eventually exceed the criteria for success. This is also what the Google Translate team did to ascend to the next level. And that work is something I consider to fall under the domain of traditional software development.
56Thus far, I’ve talked about the possibilities of machine learning and given some practical advice on how to figure out its feasibility. I’ve also introduced two general design patterns that are tightly connected to machine learning. However, they are not enough.
A designer engaged in service and interface design will have several questions concerning interaction in their mind by now. How can we and will we communicate machine intelligence to users, and what kinds of new interfaces will machine learning call for?
This entails both opportunities to do things in a new way, as well as requirements for new designs. To me, this means we will need a rise in importance of several design patterns or, rather, abstract design features, which will become important as services get smarter. They include:
I have already covered suggested features58 and personalization59 as a part of detection and prediction, but what are granularity and graceful failure?
Photoshop is an excellent example of a tool with a steep learning curve and a great deal of granularity in controlling what can be done. Most of the time, you work on small operations, each of which has a very specific influence. The creative combination of many small things allows for interesting patterns to emerge on a larger scale. Holistic, black-box operations such as transformative filters and automatic corrections are not really the reason why professionals use Photoshop.
What will happen when machines learn to predict what we are doing repeatedly? For instance, I frequently perform certain actions in Photoshop before uploading my photos to a blog. While I could manually automate this, creating yet another user-defined feature among thousands already in the product, Photoshop might learn to predict my intentions and offer a more prominent shortcut, or a highway, to fast-forward me to my intended destination. As Adobe currently puts effort into bringing AI into Creative Cloud, we’ll likely see something even more clever than this very soon. It is up to you to let the machine figure out the appropriate shortcuts in your application.
60A funny illustration of a similar train of thought comes from Cristopher Hesse’s machine-learning-based image-to-image translation62, which provides interesting content-specific filling of doodles. Similar to Photoshop’s content-aware fill, it creates most hilarious visualizations of building facades, cats, shoes and bags based on minimal user input.
63I call the final pattern graceful failure. It means saying “sorry, I can’t do what you want because…” in an understandable way.
This is by no means unique to machine learning applications. It is innately human, but something that computers have been notoriously bad at since the time that syntax errors were repeatedly echoed by Commodore computers in the 1980s. But with machine learning, it’s slightly different. Because machine learning takes a fuzzy-logic approach to computing, there are new ways that the computer could produce unexpected results — that is, things could go very bad, and that has to be designed for. Nobody seriously blames the car in question for the death that occurred in the Tesla autopilot accident in 2016.
The other part is that building applications that rely on modern machine learning is still in its infancy. Classic software development has been around for so long that we’ve learned to deal with its insufficiencies better. As Peter Norvig, famous AI researcher and Google’s research director, puts it like this65:
The problem here is the methodology for scaling this up to a whole industry is still in progress.… We don’t have the decades of experience that we have in developing and verifying regular software.
The nature of learning is such that computers learn from what is given to them. If the algorithm has to deal with something else, then the results will not be to your liking. For example, if you’ve trained a system to detect animal species from pet photos and then start using it to classify plants, there will be trouble. This is more or less why Microsoft’s Twitterbot Tay had to be silenced66 after it picked up the wrong examples from malicious users when exposed to real-world conditions.
The uncertainty in detection and prediction should be taken into consideration. How this is done depends on the application. Consider Google Search. No one is offended or truly hurt, but merely amused or frustrated, by bad search results. Of course, bad results will eventually be bad for business. However, if your bank started using a chatbot that suddenly could not figure out your checking account’s balance, you would be rightfully worried and should be offered a quick way to resolve your trouble.
To deal with failure, interfaces would do well to help both parties adjust. Users can tolerate one or two “I didn’t get that, please say that again” prompts (but no more) if that’s what it takes to advance the dialogue. For services that include machine learning, extensive testing is best. Next comes informing users about the probability and consequences of failure, and instructions on what the user might do to avoid it. The good practices are still emerging.
With machine learning, our vision of tomorrow is quickly becoming today’s reality.
Overall, there hardly seems to be an application that machine learning could not be used to detect or predict. I’ve introduced plenty of examples of deploying machine learning to varying success. In all of the examples, I’ve tried to illustrate some core learning challenge to help you understand what sorts of tasks machines respond to. Now it is time to face the question of what machine learning can do for you!
I don’t expect that this guide alone will suffice for radical innovation in the sphere of intelligent products and services. I do hope that it will open the eyes of several designers to the opportunities that machine learning solutions afford us today. It is important to understand approximately what you can realistically ask from a machine. In the near future, good questions will become ever more important, so that the answer will be, “Yes, Dave. I can do that.”
Thanks to Max Pagels, Janne Aukia, Antti Rauhala, Teemu Kinnunen and Patrick Hebron for discussing the topic.
Article by Fabien Girardin: Experience Design in the Machine Learning Era67
Article by Neal Lathia: Machine learning for product managers68
Video of Andrew Ng: Artificial Intelligence is the New Electricity69 (Stanford GSB). Ng talks of product management, but his insights are relevant to design as well.
(rb, yk, al, il)
CSS Custom Properties * Forall.js * Pointer Events API * Yeah, redesign * Comments in code * Top web browsers * Variable fonts
Bots and Artificial Intelligence are probably the most hyped concepts right now. And while some people praise the existing technologies, others claim they don’t fear AI at all, citing examples where it fails horribly. Examples of Facebook or Amazon advertising (both claim to use machine learning) that don’t match our interests at all are quite common today.
But what happens if we look at autonomous cars, trains or planes that have the very same machine learning technologies in place? How about the military using AI for its actions? While we’re still experimenting with these capable technologies, we also need to consider the possible consequences, the responsibilities that we have as developers and how all of this might affect the people the technology is being served to.
display: flow-root value that effectively replaces our common clearfix methods6. The update also comes with a revamped media player design. Finally, this is the first Firefox version without Windows XP and Vista support, so if you rely on one of these operating systems, consider switching to the ESR version of Firefox and upgrade to a newer system as soon as possible (the OS are not supported by Microsoft anymore).display: flow-root, the new clearfix replacement8. There’s also PointerEvents.getCoalescedEvents() as a new method to give you access to all input events that took place since the last time a PointerEvent was delivered — a useful feature for drawing applications but also quite risky when we look at it from a privacy and user tracking perspective.
20And with that, I’ll close for this week. If you like what I write each week, please support me with a donation26 or share this resource with other people. You can learn more about the costs of the project here27. It’s available via email, RSS and online.
— Anselm
Jekyll is gaining popularity as a lightweight alternative to WordPress. It often gets pigeonholed as a tool developers use to build their personal blog. That’s just the tip of the iceberg — it’s capable of so much more!
In this article, we’ll take on the role of a web developer building a website for a fictional law firm1. WordPress is an obvious choice for a website like this, but is it the only tool we should consider? Let’s look at a completely different way of building a website, using Jekyll.
Jekyll6 is a static website generator. Instead of software and a database being installed on our server, a Jekyll website is simply a directory of files on our computer. When we run Jekyll on that directory, it generates a static website, which we upload to a hosting provider.
We need to consider a number of tradeoffs when deciding whether Jekyll is right for a project.
Jekyll is a great tool for largely informational websites, like this project. If the project is more of an application, we could add dynamic elements using JavaScript, but at some point we would probably need a back end like WordPress’.
WordPress and Jekyll take different approaches to building a website yet share a lot of functionality. Let’s get started building our Jekyll website.
A typical development environment for WordPress requires installation of Apache or NGINX, PHP and MySQL. Then, we would install WordPress and configure the web server.
For Jekyll, we need to make sure we have Ruby installed (sometimes this is harder than it sounds). Then we install the Jekyll gem:
gem install jekyll
If you’re on macOS make sure you have Xcode developer installed first.
xcode-select --install
Running a WordPress site usually consists of starting the database and web server.
In Jekyll, we navigate to our site files (an empty directory at this point) in the terminal and run:
jekyll serve
This starts a local web server on port 4000 and rebuilds the site whenever a file changes.
It’s time to create our first page. Let’s start with the home page. Pages are for standalone content without an associated date. WordPress stores page content in the database.
In Jekyll, pages are HTML files. We’ll start with pure HTML and then add Jekyll features as they’re needed. Here’s index.html in its current state:
<html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="http://www.smashingmagazine.com/css/main.css"> </head> <body> <div> <h1><a href="http://www.smashingmagazine.com/">Justice</a></h1> <nav> <ul> <li><a href="http://www.smashingmagazine.com/about/">About</a></li> <li><a href="http://www.smashingmagazine.com/services/">Services</a></li> <li><a href="http://www.smashingmagazine.com/contact/">Contact</a></li> <li><a href="http://www.smashingmagazine.com/advice/">Advice</a></li> </ul> </nav> </div> <section> <div> <p>Justice Law is professional representation. Practicing for over 50 years, our team have the knowledge and skills to get you results.</p> <blockquote> <p>Justice Law are the best of the best. Being local, they care about people and have strong ties to the community.</p> <p> <img src="/images/peter.jpeg" alt="Photo of Peter Rottenburg"> Peter Rottenburg </p> </blockquote> </div> </section> <footer> <p> © 2016 </p> </footer> </body> </html>
In WordPress, we can write PHP to do almost anything. Jekyll takes a different approach. Instead of providing a full programming language, it uses a templating language named Liquid15. (WordPress has templating languages, too, such as Timber16.)
The footer of index.html contains a copyright notice with a year:
<p> © 2016 </p>
We’re unlikely to remember to update this every year, so let’s use Liquid to output the current year:
<p> © {{ site.time | date: "%Y" }} </p>
We’re building a static website in Jekyll, so this date won’t change until we rebuild the website. If we wanted the date to change without having to rebuild the website, we could use JavaScript.
The bulk of the HTML in index.html is for setting up the overall layout and won’t change between pages. This repetition will lead to a lot of maintenance, so let’s reduce it.
Includes were one of the first things I learned in PHP. Using includes, we can put the header and footer content in different files, then include the same content on multiple pages.
Jekyll has exactly the same feature. Includes are stored in a folder named _includes. We use Liquid to include them in index.html:
{% include header.html %} <p>Justice Law is professional representation. Practicing for over 50 years, our team have the knowledge and skills to get you results.</p> <blockquote> <p>Justice Law are the best of the best. Being local, they care about people and have strong ties to the community.</p> <p> <img src="/images/peter.jpeg" alt="Photo of Peter Rottenburg"> Peter Rottenburg </p> </blockquote> {% include footer.html %}
Includes reduce some of the repetition, but we still have them on each page. WordPress solves this problem with template files that separate a website’s structure from its content.
The Jekyll equivalent to template files is layouts. Layouts are HTML files with a placeholder for content. They are stored in the _layouts directory. We’ll create _layouts/default.html to contain a basic HTML layout:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="http://www.smashingmagazine.com/css/main.css"> </head> <body> <div> <h1><a href="http://www.smashingmagazine.com/">Justice</a></h1> <nav> <ul> <li><a href="http://www.smashingmagazine.com/about/">About</a></li> <li><a href="http://www.smashingmagazine.com/services/">Services</a></li> <li><a href="http://www.smashingmagazine.com/contact/">Contact</a></li> <li><a href="http://www.smashingmagazine.com/advice/">Advice</a></li> </ul> </nav> </div> <section> <div> {{ content }} </div> </section> <footer> <p> © {{ site.time | date: "%Y-%m-%d" }} </p> </footer> </body> </html>
Then, replace the includes in index.html by specifying the layout. We specify the layout using front matter, which is a snippet of YAML17 that sits between two triple-dashed lines at the top of a file (more on this soon).
--- layout: default --- <p>Justice Law is professional representation. Practicing for over 50 years, our team have the knowledge and skills to get you results.</p> <blockquote> <p>Justice Law are the best of the best. Being local, they care about people and have strong ties to the community.</p> <p> <img src="/images/peter.jpeg" alt="Photo of Peter Rottenburg"> Peter Rottenburg </p> </blockquote>
Now we can have the same layout on all of our pages.
In WordPress, custom fields allow us to set meta data on a post. We can use them to set SEO tags or to show and hide sections of a page for a particular post.
This concept is called front matter18 in Jekyll. Earlier, we used front matter to set the layout for index.html. We can now set our own variables and access them using Liquid. This further reduces repetition on our website.
Let’s add multiple testimonials to index.html. We could copy and paste the HTML, but once again, that leads to increased maintenance. Instead, let’s add the testimonials in front matter and iterate over them with Liquid:
--- layout: default testimonials: - message: We use Justice Law in all our endeavours. They offer an unparalleled service when it comes to running a business. image: "/images/joice.jpeg" name: Joice Carmold - message: Justice Law are the best of the best. Being local, they care about people and have strong ties to the community. image: "/images/peter.jpeg" name: Peter Rottenburg - message: Justice Law were everything we could have hoped for when buying our first home. Highly recommended to all. image: "/images/gibblesto.jpeg" name: D. and G. Gibbleston --- <p>Justice Law is professional representation. Practicing for over 50 years, our team have the knowledge and skills to get you results.</p> <div> {% for testimonial in page.testimonials %} <blockquote> <p>{{ testimonial.message }}</p> <p> <img src="{{ testimonial.image }}" alt="Photo of {{ testimonial.name }}"> {{ testimonial.name }} </p> </blockquote> {% endfor %} </div>
WordPress stores the HTML content, date and other meta data for posts in the database.
In Jekyll, each post is a static file stored in a _posts directory. The file name has the publication date and title for the post — for example, _posts/2016-11-11-real-estate-flipping.md. The source code for a blog post takes this structure:
--- layout: post categories: - Property --- Flipping is a term used primarily in the US to describe purchasing a revenue-generating asset and quickly reselling it for profit. 
We can also use front matter to set categories and tags.
Below the front matter is the body of the post, written in Markdown19. Markdown is a simpler alternative to HTML.
Jekyll allows us to create layouts that inherit from other layouts. You might have noticed this post has a layout of post. The post layout inherits from the default layout and adds a date and title:
--- layout: default --- <h3>{{ page.title }}</h3> <p>{{ page.date | date: '%B %d, %Y' }}</p> {{ content }}
Let’s create blog.html to iterate over the posts and link to them:
--- layout: default --- <ul> {% for post in site.posts %} <li><a href="{{ post.url }}">{{ post.title }}</a></li> {% endfor %} </ul>
In WordPress, custom post types are useful for managing groups of content. For example, you might use custom post types for testimonials, products or real-estate listings.
Jekyll has this feature and calls it collections20.
The about.html page shows profiles of staff members. We could define the meta data for the staff (name, image, phone number, bio) in the front matter, but then we could only reference it on that page. In the future, we want to use the same data to display information about authors on blog posts. A collection enables us to refer to staff members anywhere on the website.
Configuration of our website lives in _config.yml. Here, we set a new collection:
collections: staff_members: output: false
Now we add our staff members. Each staff member is represented in a Markdown file stored in a folder with the collection name; for example, _staff_members/jane-doe.md.
We add the meta data in the front matter and the blurb in the body:
--- name: Jane Doe image: "/images/jane.jpeg" phone: "1234567" --- Jane has 19 years of experience in law, and specialises in property and business.
Similar to posts, we can iterate over the collection in about.html to display each staff member:
--- layout: default --- <ul> {% for member in site.staff_members %} <li> <div><img src="{{ member.image }}" alt="Staff photo for {{ member.name }}"></div> <p>{{ member.name }} - {{ member.phone }}</p> <p>{{ member.content | markdownify }}</p> </li> {% endfor %} </ul>
WordPress has powerful built-in search and even more powerful search plugins.
Jekyll doesn’t have search built in, but there are solutions:
Plugins are an appealing part of WordPress. It’s easy to load in functionality to get WordPress to do almost anything.
On our Jekyll website, many popular WordPress plugins aren’t necessary:
Other WordPress plugins have third-party equivalents that we can drop into the website:
Some WordPress plugins can be emulated with core Jekyll. Here’s a photo gallery using front matter and Liquid:
--- layout: default images: - image_path: /images/bill.jpg title: Bill - image_path: /images/burt.jpg title: Burt - image_path: /images/gary.jpg title: Gary - image_path: /images/tina.jpg title: Tina - image_path: /images/suzy.jpg title: Suzy --- <ul> {% for image in page.images %} <li><img src="{{ image.image_path }}" alt="{{ image.title }}"/></li> {% endfor %} </ul>
We just need to add our own JavaScript and CSS to complete it.
Jekyll plugins can emulate the functionality of other WordPress plugins. Keep in mind that Jekyll plugins only run while the website is being generated — they don’t add real-time functionality:
One of the major benefits of using a static site generator like Jekyll is the entire site and content can live in Git. At a basic level, Git gives you a history of all the changes on the site. For teams, it opens up all sorts of workflows and approval processes.
GitHub has a fantastic interactive tutorial46 for beginners learning Git.
That covers the nuts and bolts of creating the website. If you’re curious to see how an entire Jekyll website fits together, have a look at the Justice template47. It’s a free MIT-licensed template for Jekyll. The snippets above are based on this template.
The WordPress CMS is built into the platform, so we would need to set up an account for the client.
With our Jekyll website, we’d link our Git repository to one of the Jekyll GUIs mentioned earlier. One of the nice things about this workflow is that clients changes are committed back to the repository. As developers, we can continue to use local workflows even with non-developers updating the website.
Some Jekyll GUIs offer hosting, while others have a way to output to an Amazon S3 bucket or to GitHub Pages48.
At this point, our Jekyll website is live and editable by the client. If we need to make any changes to the website, we simply push to the repository and it will automatically deploy live.
Now it’s your turn. Plenty of resources are available to help you build your first Jekyll website:
If you’re migrating, Jekyll has tools to import posts54 from WordPress and WordPress.com websites. After importing, you’ll need to manually migrate or create the layouts, pages, CSS, JavaScript and other assets for the website.
The beauty of Jekyll is in its simplicity. While WordPress can match many of the features of Jekyll, it often comes at the cost of complexity through extra plugins or infrastructure.
Ultimately, it’s about finding the tool that works best for you. I’ve found Jekyll to be a fast and efficient way to build websites. I encourage you to try it out and post your experience in the comments.
(al)
No matter whether you are designing a whole design system or just a couple of screens, symbols in Sketch will help you keep your file organized and will save you a lot of time in the long run. In this article, I’ll share with you a few best practices and tricks to help you unleash symbols’ full potential.
But first, a bit of a backstory. I started using Sketch a few years ago, as a replacement for my favorite design software back then, Fireworks1, which had been discontinued2 by Adobe — leaving a whole generation of designers3 broken-hearted. Since my first days of using Sketch, I was very surprised by how easy and straightforward it is to use. I had, once again, found an application focused on user interface (and icon) design — and nothing else.
The apparent lack of features in Sketch, compared to the alternatives full of menus and stacked panels4 that I was used to, was in fact one of its major advantages and helped me to design faster. Among those few features, symbols were the thing that I used very frequently, and still do, practically every day (yes, even on Sundays… you know, a freelancer’s life).
What are symbols? In a nutshell, symbols enable you to use and reuse an element across a project, keeping a master symbol that automatically updates other instances of the symbol when changes are made to it.
This concept is not exactly new (nor is it exclusive to Sketch, to be honest). However, if you design interfaces, then you’ll find it extremely useful, especially when using components as part of a design system8.
In this article, I’ll outline how to make use of symbols in Sketch in order to unleash their full potential, going from the most basic situations to some more advanced use cases. I’ll also include some tips and tricks that I have learned along the way.
Before digging deeper, and in case you are new to Sketch, let me give you a short introduction to how symbols work.
Symbols can be made from almost any elements in Sketch: text objects, shapes, bitmap images, even other symbols (we’ll talk about this later). Inside every symbol (double-click a symbol to enter edit mode), you’ll find one main artboard containing the symbol’s layers. This artboard also defines the symbol’s boundaries.
Usually, symbols are created for those elements in an interface that you expect to reuse later on (such as buttons, list items, tabs, etc.) and that will be spread across different screens, pages and artboards in your designs.
Note: For future reference, keep in mind that “copies” of one symbol are called instances.
The best thing about using symbols (instead of grouped, independent and disconnected objects) is that if at some point you decide to change some property in a particular symbol (for example, the color, shape, text size, dimensions or whatever else you want), you’ll just need to edit the symbol’s master once, and this change will be automatically replicated to all of the master’s instances, wherever they are. I don’t know about you, but I find this super-convenient!
Just like in life itself, it’s fundamental to keep everything in order. Always design as if someone else will later need to open and work with your design file and understand it without your help! This also applies to the way you name symbols — naming should meet certain criteria.
One recommendation is to use a slash (/) in the symbol’s name. Sketch will automatically create a category with the part before the slash, and will name and place the symbol inside it, using the part of the name following the slash. For example, if you have two symbols named “Button/Primary” and “Button/Secondary,” here is how they will look like when you try to insert them from the toolbar:
13You can repeat this many times to have several symbols under the same root, grouped by similar logic, making them easier to find. And if your “tree” grows too big, take a moment to reconsider your naming system and see if there’s any possible way to optimize it and make it more manageable.
There are many different conventions for how symbols should be named, perhaps one convention for every designer out there. Personally, I prefer not to use names that refer to the visual properties of the elements — for example, “Red Button” would be a bad choice in my opinion because if the color of the button changes later on for some reason, the name of the symbol will become incorrect. Instead, I try to differentiate the symbol’s function and state (such as “Primary/Disabled”).
In any case, just be consistent and find something that works for both you and your team, then stick to it; don’t switch the naming system according to the case! This also applies to layers inside symbols: some designers even use emojis15 to mark which of them are meant to be editable (for example, by adding a pencil emoji to the name). To do this, press Control + Command + Space to open a dialog to select emojis16.
17Note: Regarding symbols’ names, bear in mind that instances will take their names from the master symbol, but you can change them afterwards to whatever you want. This way, instances of the same symbol can have different names from each other.
When you create a symbol, Sketch asks whether you want to send it to the symbols page18. My advice is to check this box, even if after a while (and a few symbols later) this dedicated page turns into a mess. (Sketch places one symbol next to the other as they are being created, and when you delete a symbol, you’ll notice the blank space left in its spot.)
Instead, what I do to sort this out is to create my own symbols page (which is just a regular page, which I would usually name “Symbols”) where I can arrange symbol instances in the order I want and, thus, ignore the official symbols page.
19This way, I can create artboards that follow categories (such as lists, buttons, inputs and so on) and place symbols in a way that I find convenient and that makes sense to me. You’ll still need to invest some time to update this page from time to time, but once it is created, it will make everything much easier and you’ll be able to build a new screen in no time.
Note: If you prefer to use the symbols page instead, there’s the Symbol Organizer plugin21, which could help you keep everything arranged.
Replacing an existing symbol with another is easy. Just select the symbol and choose “Replace with” from the contextual menu that appears when you right-click over the symbol instance. Then, select the new symbol that you want to use. Keep in mind that the new symbol will keep the same size and position as its predecessor; you can fix this by selecting “Set to original size” from the same contextual menu.
22Once you’ve made a symbol, you can detach it to recover the elements that form it as a group. To do this, just select “Detach from symbol” in the same contextual menu that I mentioned earlier.
Symbols, like other elements, can also be exported as bitmap images. To do this, you’ll need to mark elements as exportable. (Select the symbol instance, and then choose “Make Exportable” at the bottom of the Inspector.)
The problem that I found during this process is that if the symbol has some padding (for example, if the shapes inside are smaller than the symbol’s total size), when doing the export, Sketch will omit the blank space and will just create an image with the visible content only.
23One way to work this around is by using a slice25. When creating the slice, place it over the instance and make sure it matches the size of the instance’s boundaries (width and height); then, select the slice and use the exporting options as needed.
Side note: This same trick also applies to other tools, such as Zeplin26.
In this world full of screens with multiple sizes and aspect ratios, it’s important to make sure your design adapts to many different scenarios. This is easier to accomplish if you don’t have to design everything from scratch every time, by reusing elements (or symbols, as I’m sure you’ve already guessed).
29This is where the resizing options in symbols come in handy, helping you to use the same element with different widths and heights with no hassle: If you resize just one instance by selecting it, this won’t affect the other instances. (But remember that resizing options are applied to individual layers inside the master symbol, not to the instance itself. So, even while you can adjust sizes individually from instance to instance, elements inside will always maintain the same behavior.)
Note: The options outlined below apply not only to symbols, but to groups as well. Behaviors are not always predictable, so chances are that you’ll have to play around and explore a bit before finding what you need, combining one or two different settings in most cases.
30When the Stretch option is used, a shape that has specified, let’s say, 50% of the symbol’s total width will keep this same relationship when the instance is extended vertically or horizontally. This is the default behavior.
31“Pin to Corner” will (as the name suggests) pin an element to the nearest corner, and the element will not resize, keeping the same distance to this corner. Keep in mind that if the object is centered (with equal spacing from both sides), it won’t know which one is the nearest corner, so it’ll stay in the middle.
32When “Resize Object” is used, elements will grow while keeping an equal (or fixed) spacing from the sides.
33“Float in Place” will make an object stay the same size and will keep its relative position to the boundaries of the symbol.
If you have resized your symbol but aren’t satisfied with the result, you can always go back to the beginning by choosing “Set to original size” from the contextual menu.
Keep in mind that symbols have dedicated artboards, and these will define the symbols’ boundaries (even when shapes inside overflow on them). You can make the symbol’s artboard the same size as of its contents by selecting it and choosing “Resize to fit” from the Inspector.
In the width and height input fields in the Inspector, you can use operators to change values. For instance, you can use 100*2 to set an element’s dimensions to 200 pixels. Other operators are + (add), - (subtract) and / (divide).
Besides mathematical operators, in the same input fields you can also use L to scale an object from the left (this is the default), R to scale it from the right, T to scale it from the top (this is the default), B to scale it from the bottom, and C and M to scale it from the center or middle.
34For example, if you have a shape that has a width of 200 pixels and want to resize it so that it scales from the right to the left side, you can use something like 300r in the width input field.
What could be better than one symbol? Perhaps a symbol with another one inside it!
This feature is kind of new in Sketch, and it gives you a lot of possibilities when combining symbols together. You can place one symbol on top of another, select both, and then create a new symbol that contains the two instances. You can repeat this as much as you’d like. Be moderate, though, or else you’ll find yourself digging into levels and levels of nested symbols, one inside another. This could make maintenance much harder and could also be a symptom of bigger organizational problems.
Nesting symbols can be especially useful when you need to create variations of one symbol. For example, you could follow a process like this:
In the image below, you can see that all rows share the same characteristics (they have the same size, text properties and amount of padding on the left), so I created a base symbol that contains only these elements (i.e. elements that will be shared with the other symbols). Using this symbol as a starting point, I then created some overlapping elements that are different, saving the result in each case as a different symbol; so, all of the symbols under “Variations” are actually different symbols.
36But you don’t — necessarily — need to create a new symbol for every state of the row. There may be a simpler way: using overrides.
If you had to create a lot of different symbols just because one part of their content changes, you’d probably go nuts. One of the main purposes of symbols is precisely to have to design as little as possible and to have fewer elements — and, therefore, more control over them. Enter nested overrides!
37One practical example of this workflow could be designing a tab bar with different states. In this case, the main symbol with the inactive tabs would act as the base, and then there would be a different symbol for each one of the highlighted tabs. Just choose the one that you want from the “Overrides” options in the Inspector.
Note: For this technique to work, keep in mind that the inactive tabs inside the main symbol (the navigation bar) need to be symbols as well. Also, be sure that all symbols (both inactive and active ones) have the exact same dimensions (width, height). Otherwise, they won’t appear as available options in the “Overrides” dropdown menu.
Let’s look at another use case. If you have multiple buttons in a design but with different text labels on them, then the Overrides option will enable you to change the text value (not the font family or font size — you have to modify those inside the symbol itself, when editing the symbol master), without having to create a new symbol each time. This is as easy to do as selecting the instance and changing the text content in the Inspector.
Overrides apply not only to text; you can also use them for bitmap images and even for other symbols, as mentioned before. This way, you can have several instances of a symbol, with a different image in each one of them — and all of this without having to modify the symbol’s master.
38There are cases when I don’t want to have any particular image as part of a symbol’s master. So, what I usually do is to create an empty PNG file with no visible content, create a shape, and use this image as a pattern fill (you can find this option in the “Fill Options” when selecting a shape). Then, when doing the symbol overriding, I just replace this transparent image with the one that I want in each case!
To get the most out of this practice, I also use a layering system with an icon or element that acts as a placeholder underneath the image and that will be visible only if I keep the original transparent bitmap. One benefit of doing this is that I can simulate this empty state that will appear when images are loading in the finished product, something that I consider necessary to design anyway.
One of the reasons why being organized is a good idea is because the way you name and order layers will affect the way they are displayed in the “Overrides” panel. The labels to the left of the input fields in the Inspector will respect the name and order you’ve previously defined inside the symbol itself, so you’d better pay attention to this order if you want to have a more efficient workflow.
You can replace a nested symbol with another symbol only if the new symbol has the exact same width and height as the current element.
When changing the text’s value in the Overrides options, you can make an element move as needed when the one to its left is longer (see the following illustration).
39The secondary text or shape necessarily needs to be to the right of the text for this to work. Also, both elements should have no more than 20 pixels of distance between them (see the “Further Reading” below).
A symbol can look a bit messy because of the options in the Overrides section. If you don’t want an element inside it to be able to be overridden, just lock or hide this layer and it won’t appear in the list.
Just select “None” from the Overrides section to hide a nested symbol. Of course, it will only be invisible in that particular instance.
There’s one way to quickly make a text element disappear in an instance, by using overrides. To do this, just set the text value to a blank space, pressing the space bar and the return key in the Overrides options.
If you have bitmap images inside a symbol, they can be changed by others using the options in the Overrides section. It’s also possible to recover the original image (the one that forms part of the editable symbol) by choosing “Remove image override” — just right-click over the image box next to “Choose Image” in the Inspector.
One good thing about Sketch is that when it falls short of a feature, there’s usually a plugin to make up for it. And some of them work especially well with symbols, making them even more powerful! Some of these plugins have been mentioned, but in case you missed any of them, here’s a list with some additions.
50Among its many other features, the Sketch Runner52 plugin will help you easily insert symbols in a document using just a combination of keys. The “go to” option is very useful for jumping right to a particular symbol — very useful if your project has a lot of them and if it’s difficult to find symbols using other means.
If you are working with a team, InVision Craft Library53 will make it easy to create a shared library with assets that everybody can use, allowing you to sync changes when you need to update a symbol, so that you are always sure you’re using the symbols’s latest version.
Automate54 is very powerful and will likely make your work more efficient. Options for managing symbols include ones to remove unused symbols, to select all instances of a symbol, and much more.
Symbol Instance Renamer55 renames all instances to match the name of their master symbols.
With Symbol Organizer56, organize your symbols page alphabetically (including the layers list) and into separate groups determined by your symbol names.
Auto Layout57 integrates seamlessly in Sketch and enables defining and viewing different iPhone and iPad sizes including portrait and landscape. It also supports more advanced features, such as stacks (a special type of group that defines the layouts of its child layers), and presets for both Android and iOS. Look at their “Examples” page58 for more information.
Note: These are only some of the plugins that I think might be most helpful to you, but there are many others. To know more, just visit Sketch’s official plugin page59 or the Sketch App Sources60 website regularly.
Sketch symbols are constantly evolving, so we can expect further improvements that will make them even more valuable and relevant. However, if I had to name just one thing that I would like them to have, that would be the possibility to have shared symbols’ libraries, something like Figma is doing61. This could be extremely useful, especially for team work, when several designers working on the same project need to pick elements from a primary, always up-to-date document stored in the cloud.
(Note: Regarding this feature, I’m aware that Sketch’s team is working on it, so hopefully we’ll see it soon. The more open format in version 4362 is probably laying the groundwork for this feature. In any case, I’m looking forward to it, because this could be a game-changer in many designer workflows.)
Truth be told, there are currently some plugins that help you accomplish more or less the same behavior mentioned above, but I always find it more reliable when they are made a part of Sketch’s core functionality — which ensures that the feature will keep working when the software is updated to the next version.
I’m aware that there are many more techniques and tricks. The way one works tends to be kind of personal sometimes, and there’s no single right way to do something. Here, I’ve shared the techniques that I think are reliable, interesting and don’t require much hacking. That’s why some techniques were left out of this article.
I hope this was a useful read! If it was, then symbols will probably become the backbone of your designs, and you’ll use them quite often. Feel free to share your thoughts and other tips and tricks in the comments below. You can also always reach me on Twitter63 if you need help!
(mb, al)
Today, CSS preprocessors are a standard for web development. One of the main advantages of preprocessors is that they enable you to use variables. This helps you to avoid copying and pasting code, and it simplifies development and refactoring.
We use preprocessors to store colors, font preferences, layout details — mostly everything we use in CSS.
But preprocessor variables have some limitations:
As a silver bullet for these and other problems, the community invented CSS custom properties. Essentially, these look and work like CSS variables, and the way they work is reflected in their name.
Custom properties are opening new horizons for web development.
The usual problem when you start with a new preprocessor or framework is that you have to learn a new syntax.
Each preprocessor requires a different way of declaring variables. Usually, it starts with a reserved symbol — for example, $ in Sass and @ in LESS.
CSS custom properties have gone the same way and use -- to introduce a declaration. But the good thing here is that you can learn this syntax once and reuse it across browsers!
You may ask, “Why not reuse an existing syntax?”
There is a reason5. In short, it’s to provide a way for custom properties to be used in any preprocessor. This way, we can provide and use custom properties, and our preprocessor will not compile them, so the properties will go directly to the outputted CSS. And, you can reuse preprocessor variables in the native ones, but I will describe that later.
(Regarding the name: Because their ideas and purposes are very similar, sometimes custom properties are called the CSS variables, although the correct name is CSS custom properties, and reading further, you will understand why this name describes them best.)
So, to declare a variable instead of a usual CSS property such as color or padding, just provide a custom-named property that starts with --:
.box{ --box-color: #4d4e53; --box-padding: 0 10px; }
The value of a property may be any valid CSS value: a color, a string, a layout value, even an expression.
Here are examples of valid custom properties:
:root{ --main-color: #4d4e53; --main-bg: rgb(255, 255, 255); --logo-border-color: rebeccapurple; --header-height: 68px; --content-padding: 10px 20px; --base-line-height: 1.428571429; --transition-duration: .35s; --external-link: "external link"; --margin-top: calc(2vh + 20px); /* Valid CSS custom properties can be reused later in, say, JavaScript. */ --foo: if(x > 5) this.width = 10; }
In case you are not sure what :root6 matches, in HTML it’s the same as html but with a higher specificity.
As with other CSS properties, custom ones cascade in the same way and are dynamic. This means they can be changed at any moment and the change is processed accordingly by the browser.
To use a variable, you have to use the var() CSS function and provide the name of the property inside:
.box{ --box-color:#4d4e53; --box-padding: 0 10px; padding: var(--box-padding); } .box div{ color: var(--box-color); }
The var() function is a handy way to provide a default value. You might do this if you are not sure whether a custom property has been defined and want to provide a value to be used as a fallback. This can be done easily by passing the second parameter to the function:
.box{ --box-color:#4d4e53; --box-padding: 0 10px; /* 10px is used because --box-margin is not defined. */ margin: var(--box-margin, 10px); }
As you might expect, you can reuse other variables to declare new ones:
.box{ /* The --main-padding variable is used if --box-padding is not defined. */ padding: var(--box-padding, var(--main-padding)); --box-text: 'This is my box'; /* Equal to --box-highlight-text:'This is my box with highlight'; */ --box-highlight-text: var(--box-text)' with highlight'; }
As we got accustomed to with preprocessors and other languages, we want to be able to use basic operators when working with variables. For this, CSS provides a calc() function, which makes the browser recalculate an expression after any change has been made to the value of a custom property:
:root{ --indent-size: 10px; --indent-xl: calc(2*var(--indent-size)); --indent-l: calc(var(--indent-size) + 2px); --indent-s: calc(var(--indent-size) - 2px); --indent-xs: calc(var(--indent-size)/2); }
A problem awaits if you try to use a unit-less value. Again, calc() is your friend, because without it, it won’t work:
:root{ --spacer: 10; } .box{ padding: var(--spacer)px 0; /* DOESN'T work */ padding: calc(var(--spacer)*1px) 0; /* WORKS */ }
Before talking about CSS custom property scopes, let’s recall JavaScript and preprocessor scopes, to better understand the differences.
We know that with, for example, JavaScript variables (var), a scope is limited to the functions.
We have a similar situation with let and const, but they are block-scope local variables.
A closure in JavaScript is a function that has access to the outer (enclosing) function’s variables — the scope chain. The closure has three scope chains, and it has access to the following:
The story with preprocessors is similar. Let’s use Sass as an example because it’s probably the most popular preprocessor today.
With Sass, we have two types of variables: local and global.
A global variable can be declared outside of any selector or construction (for example, as a mixin). Otherwise, the variable would be local.
Any nested blocks of code can access the enclosing variables (as in JavaScript).
9This means that, in Sass, the variable’s scopes fully depend on the code’s structure.
However, CSS custom properties are inherited by default, and like other CSS properties, they cascade.
You also cannot have a global variable that declares a custom property outside of a selector — that’s not valid CSS. The global scope for CSS custom properties is actually the :root scope, whereupon the property is available globally.
Let’s use our syntax knowledge and adapt the Sass example to HTML and CSS. We’ll create a demo using native CSS custom properties. First, the HTML:
global <div> enclosing <div> closure </div> </div>
And here is the CSS:
:root { --globalVar: 10px; } .enclosing { --enclosingVar: 20px; } .enclosing .closure { --closureVar: 30px; font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar)); /* 60px for now */ }
See the Pen css-custom-properties-time-to-start-using 111 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.
So far, we haven’t seen how this is any different from Sass variables. However, let’s reassign the variable after its usage:
In the case of Sass, this has no effect:
.closure { $closureVar: 30px; // local variable font-size: $closureVar +$enclosingVar+ $globalVar; // 60px, $closureVar: 30px is used $closureVar: 50px; // local variable }
See the Pen css-custom-properties-time-to-start-using 314 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.
But in CSS, the calculated value is changed, because the font-size value is recalculated from the changed --closureVar value:
.enclosing .closure { --closureVar: 30px; font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar)); /* 80px for now, --closureVar: 50px is used */ --closureVar: 50px; }
See the Pen css-custom-properties-time-to-start-using 217 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.
That’s the first huge difference: If you reassign a custom property’s value, the browser will recalculate all variables and calc() expressions where it’s applied.
Suppose we wanted to use the default font-size for the block, except where the highlighted class is present.
Here is the HTML:
<div> default </div> <div> default highlighted </div>
Let’s do this using CSS custom properties:
.highlighted { --highlighted-size: 30px; } .default { --default-size: 10px; /* Use default-size, except when highlighted-size is provided. */ font-size: var(--highlighted-size, var(--default-size)); }
Because the second HTML element with the default class carries the highlighted class, properties from the highlighted class will be applied to that element.
In this case, it means that --highlighted-size: 30px; will be applied, which in turn will make the font-size property being assigned use the --highlighted-size.
Everything is straightforward and works:
See the Pen css-custom-properties-time-to-start-using 420 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.
Now, let’s try to achieve the same thing using Sass:
.highlighted { $highlighted-size: 30px; } .default { $default-size: 10px; /* Use default-size, except when highlighted-size is provided. */ @if variable-exists(highlighted-size) { font-size: $highlighted-size; } @else { font-size: $default-size; } }
The result shows that the default size is applied to both:
See the Pen css-custom-properties-time-to-start-using 523 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.
This happens because all Sass calculations and processing happen at compilation time, and of course, it doesn’t know anything about the DOM’s structure, relying fully on the code’s structure.
As you can see, custom properties have the advantages of variables scoping and add the usual cascading of CSS properties, being aware of the DOM’s structure and following the same rules as other CSS properties.
The second takeaway is that CSS custom properties are aware of the DOM’s structure and are dynamic.
all Property LinkCSS custom properties are subject to the same rules as the usual CSS custom properties. This means you can assign any of the common CSS keywords to them:
inheritinitialunsetrevertHere is an example:
.common-values{ --border: inherit; --bgcolor: initial; --padding: unset; --animation: revert; }
Let’s consider another case. Suppose you want to build a component and want to be sure that no other styles or custom properties are applied to it inadvertently (a modular CSS solution would usually be used for styles in such a case).
But now there is another way: to use the all CSS property26. This shorthand resets all CSS properties.
Together with CSS keywords, we can do the following:
.my-wonderful-clean-component{ all: initial; }
This reset all styles for our component.
Unfortunately, the all keyword doesn’t reset custom properties27. There is an ongoing discussion about whether to add the -- prefix28, which would reset all CSS custom properties.
So, in future, a full reset might be done like this:
.my-wonderful-clean-component{ --: initial; /* reset all CSS custom properties */ all: initial; /* reset all other CSS styles */ }
There are many uses of custom properties. I will show the most interesting of them.
The name of these CSS variables is “custom properties,” so why not to use them to emulate non-existent properties?
There are many of them: translateX/Y/Z, background-repeat-x/y (still not cross-browser compatible), box-shadow-color.
Let’s try to make the last one work. In our example, let’s change the box-shadow’s color on hover. We just want to follow the DRY rule (don’t repeat yourself), so instead of repeating box-shadow’s entire value in the :hover section, we’ll just change its color. Custom properties to the rescue:
.test { --box-shadow-color: yellow; box-shadow: 0 0 30px var(--box-shadow-color); } .test:hover { --box-shadow-color: orange; /* Instead of: box-shadow: 0 0 30px orange; */ }
See the Pen Emulating “box-shadow-color” CSS property using CSS Custom Properties29 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.
One of the most common use cases of custom properties is for color themes in applications. Custom properties were created to solve just this kind of problem. So, let’s provide a simple color theme for a component (the same steps could be followed for an application).
Here is the code for our button component32:
.btn { background-image: linear-gradient(to bottom, #3498db, #2980b9); text-shadow: 1px 1px 3px #777; box-shadow: 0px 1px 3px #777; border-radius: 28px; color: #ffffff; padding: 10px 20px 10px 20px; }
Let’s assume we want to invert the color theme.
The first step would be to extend all of the color variables to CSS custom properties and rewrite our component. So, the result would be the same33:
.btn { --shadow-color: #777; --gradient-from-color: #3498db; --gradient-to-color: #2980b9; --color: #ffffff; background-image: linear-gradient( to bottom, var(--gradient-from-color), var(--gradient-to-color) ); text-shadow: 1px 1px 3px var(--shadow-color); box-shadow: 0px 1px 3px var(--shadow-color); border-radius: 28px; color: var(--color); padding: 10px 20px 10px 20px; }
This has everything we need. With it, we can override the color variables to the inverted values and apply them when needed. We could, for example, add the global inverted HTML class (to, say, the body element) and change the colors when it’s applied:
body.inverted .btn{ --shadow-color: #888888; --gradient-from-color: #CB6724; --gradient-to-color: #D67F46; --color: #000000; }
Below is a demo in which you can click a button to add and remove a global class:
See the Pen css-custom-properties-time-to-start-using 934 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.
This behavior cannot be achieved in a CSS preprocessor without the overhead of duplicating code. With a preprocessor, you would always need to override the actual values and rules, which always results in additional CSS.
With CSS custom properties, the solution is as clean as possible, and copying and pasting is avoided, because only the values of the variables are redefined.
Previously, to send data from CSS to JavaScript, we often had to resort to tricks37, writing CSS values via plain JSON in the CSS output and then reading it from the JavaScript.
Now, we can easily interact with CSS variables from JavaScript, reading and writing to them using the well-known .getPropertyValue() and .setProperty() methods, which are used for the usual CSS properties:
/** * Gives a CSS custom property value applied at the element * element {Element} * varName {String} without '--' * * For example: * readCssVar(document.querySelector('.box'), 'color'); */ function readCssVar(element, varName){ const elementStyles = getComputedStyle(element); return elementStyles.getPropertyValue(`--${varName}`).trim(); } /** * Writes a CSS custom property value at the element * element {Element} * varName {String} without '--' * * For example: * readCssVar(document.querySelector('.box'), 'color', 'white'); */ function writeCssVar(element, varName, value){ return element.style.setProperty(`--${varName}`, value); }
Let’s assume we have a list of media-query values:
.breakpoints-data { --phone: 480px; --tablet: 800px; }
Because we only want to reuse them in JavaScript — for example, in Window.matchMedia()38 — we can easily get them from CSS:
const breakpointsData = document.querySelector('.breakpoints-data'); // GET const phoneBreakpoint = getComputedStyle(breakpointsData) .getPropertyValue('--phone');
To show how to assign custom properties from JavaScript, I’ve created an interactive 3D CSS cube demo that responds to user actions.
It’s not very hard. We just need to add a simple background, and then place five cube faces with the relevant values for the transform property: translateZ(), translateY(), rotateX() and rotateY().
To provide the right perspective, I added the following to the page wrapper:
#world{ --translateZ:0; --rotateX:65; --rotateY:0; transform-style:preserve-3d; transform: translateZ(calc(var(--translateZ) * 1px)) rotateX(calc(var(--rotateX) * 1deg)) rotateY(calc(var(--rotateY) * 1deg)); }
The only thing missing is the interactivity. The demo should change the X and Y viewing angles (--rotateX and --rotateY) when the mouse moves and should zoom in and out when the mouse scrolls (--translateZ).
Here is the JavaScript that does the trick:
// Events onMouseMove(e) { this.worldXAngle = (.5 - (e.clientY / window.innerHeight)) * 180; this.worldYAngle = -(.5 - (e.clientX / window.innerWidth)) * 180; this.updateView(); }; onMouseWheel(e) { /*…*/ this.worldZ += delta * 5; this.updateView(); }; // JavaScript -> CSS updateView() { this.worldEl.style.setProperty('--translateZ', this.worldZ); this.worldEl.style.setProperty('--rotateX', this.worldXAngle); this.worldEl.style.setProperty('--rotateY', this.worldYAngle); };
Now, when the user moves their mouse, the demo changes the view. You can check this by moving your mouse and using mouse wheel to zoom in and out:
See the Pen css-custom-properties-time-to-start-using 1039 by Serg Hospodarets (@malyw4035302421181512) on CodePen4136312522191613.
Essentially, we’ve just changed the CSS custom properties’ values. Everything else (the rotating and zooming in and out) is done by CSS.
Tip: One of the easiest ways to debug a CSS custom property value is just to show its contents in CSS generated content (which works in simple cases, such as with strings), so that the browser will automatically show the current applied value:
body:after { content: '--screen-category : 'var(--screen-category); }
You can check it in the plain CSS demo42 (no HTML or JavaScript). (Resize the window to see the browser reflect the changed CSS custom property value automatically.)
CSS custom properties are supported in all major browsers43:
44This means that, you can start using them natively.
If you need to support older browsers, you can learn the syntax and usage examples and consider possible ways of switching or using CSS and preprocessor variables in parallel.
Of course, we need to be able to detect support in both CSS and JavaScript in order to provide fallbacks or enhancements.
This is quite easy. For CSS, you can use a @supports condition46 with a dummy feature query:
@supports ( (--a: 0)) { /* supported */ } @supports ( not (--a: 0)) { /* not supported */ }
In JavaScript, you can use the same dummy custom property with the CSS.supports() static method:
const isSupported = window.CSS && window.CSS.supports && window.CSS.supports('--a', 0); if (isSupported) { /* supported */ } else { /* not supported */ }
As we saw, CSS custom properties are still not available in every browser. Knowing this, you can progressively enhance your application by checking if they are supported.
For instance, you could generate two main CSS files: one with CSS custom properties and a second without them, in which the properties are inlined (we will discuss ways to do this shortly).
Load the second one by default. Then, just do a check in JavaScript and switch to the enhanced version if custom properties are supported:
<!-- HTML --> <link href="without-css-custom-properties.css" rel="stylesheet" type="text/css" media="all" />
// JavaScript if(isSupported){ removeCss('without-css-custom-properties.css'); loadCss('css-custom-properties.css'); // + conditionally apply some application enhancements // using the custom properties }
This is just an example. As you’ll see below, there are better options.
According to a recent survey47, Sass continues to be the preprocessor of choice for the development community.
So, let’s consider ways to start using CSS custom properties or to prepare for them using Sass.
We have a few options.
One advantage of this method of manually checking in the code whether custom properties are supported is that it works and we can do it right now (don’t forget that we have switched to Sass):
$color: red; :root { --color: red; } .box { @supports ( (--a: 0)) { color: var(--color); } @supports ( not (--a: 0)) { color: $color; } }
This method does have many cons, not least of which are that the code gets complicated, and copying and pasting become quite hard to maintain.
The PostCSS ecosystem provides dozens of plugins today. A couple of them process custom properties (inline values) in the resulting CSS output and make them work, assuming you provide only global variables (i.e. you only declare or change CSS custom properties inside the :root selector(s)), so their values can be easily inlined.
An example is postcss-custom-properties48.
This plugin offers several pros: It makes the syntax work; it is compatible with all of PostCSS’ infrastructure; and it doesn’t require much configuration.
There are cons, however. The plugin requires you to use CSS custom properties, so you don’t have a path to prepare your project for a switch from Sass variables. Also, you won’t have much control over the transformation, because it’s done after the Sass is compiled to CSS. Finally, the plugin doesn’t provide much debugging information.
I started using CSS custom properties in most of my projects and have tried many strategies:
As a result of that experience, I started looking for a solution that would satisfy my criteria:
As a result, I created css-vars, a Sass mixin that you can find on Github51. Using it, you can sort of start using CSS custom properties syntax.
To declare variable(s), use the mixin as follows:
$white-color: #fff; $base-font-size: 10px; @include css-vars(( --main-color: #000, --main-bg: $white-color, --main-font-size: 1.5*$base-font-size, --padding-top: calc(2vh + 20px) ));
To use these variables, use the var() function:
body { color: var(--main-color); background: var(--main-bg, #f00); font-size: var(--main-font-size); padding: var(--padding-top) 0 10px; }
This gives you a way to control all of the CSS output from one place (from Sass) and start getting familiar with the syntax. Plus, you can reuse Sass variables and logic with the mixin.
When all of the browsers you want to support work with CSS variables, then all you have to do is add this:
$css-vars-use-native: true;
Instead of aligning the variable properties in the resulting CSS, the mixin will start registering custom properties, and the var() instances will go to the resulting CSS without any transformations. This means you’ll have fully switched to CSS custom properties and will have all of the advantages we discussed.
If you want to turn on the useful debugging information, add the following:
$css-vars-debug-log: true;
This will give you:
Now you know more about CSS custom properties, including their syntax, their advantages, good usage examples and how to interact with them from JavaScript.
You have learned how to detect whether they are supported, how they are different from CSS preprocessor variables, and how to start using native CSS variables until they are supported across browsers.
This is the right time to start using CSS custom properties and to prepare for their native support in browsers.
(rb, vf, al, il)
Ponzu is an open-source HTTP server framework and CMS written in Go. It provides automatic, free, and secure HTTP/2 over TLS.
Unsplash API * Design Better Data Tables * Fair Analytics * Pingy CLI * Archetype * Button Styles * gofccyours…
Looking at recent discussions, I feel that more and more people are starting to think about ethically and morally correct work. Many of us keep asking themselves if their work is meaningful or if it matters at all. But in a well-functioning society, we need a variety of things to live a good life. The people writing novels that delight us are just as important as those who fight for our civil rights.
It’s important that we have people building services that ease other people’s lives and it’s time to set our sense of urgency right again. Once we start to value other people’s work, the view we have on our own work will start to change, too. As we rely on book authors, for example, other people rely on us to be able to buy the books via a nice, fast and reliable web service.
async/await, and Intersection Observer.CAA. The Certificate Authority Authorization Record10 lets you specify which certificate authority is allowed to issue a certificate for your domain. From September 2017 on, CAs are required to check against these records, so you should consider adding that record to your DNS records as soon as possible.
18Last but not least, if you’re in Europe or Germany, how about joining the awesome CSSconf EU in Berlin on May, 5th25? There are still tickets available. I’ll be around at the sold-out beyondtellerrand in Düsseldorf again, and I’d love to meet you there. If you don’t have a ticket, maybe join one of the side events26? Or consider the Material Conference27 which will take place on August 17th in Iceland, a lovely island, and I’m sure the event will be great as well.
And with that, I’ll close for this week. If you like what I write each week, please support me with a donation28 or share this resource with other people. You can learn more about the costs of the project here29. It’s available via email, RSS and online.
— Anselm
For the past few months, I’ve been building a software-as-a-service (SaaS) application, and throughout the development process I’ve realized what a powerful tool Slack (or team chat in general) can be to monitor user and application behavior. After a bit of integration, it’s provided a real-time view into our application that previously didn’t exist, and it’s been so invaluable that I couldn’t help but write up this show-and-tell.
It all started with a visit to a small startup in Denver, Colorado. During my visit, I started hearing a subtle and enchanting “ding” in the corner of the office every few minutes. When I went to investigate this strange noise, I found a service bell hooked up to a Raspberry Pi, with a tiny metal hammer connected to the circuit board. As it turned out, the Pi was receiving messages from the team’s server, and it swung that little hammer at the bell every time a new customer signed up.
I always thought that was a great team motivator, and it got me thinking of how I could use team chat to achieve a similar experience.
Because we were already using Slack for team chat, and because it has a beautifully documented API1, it was an obvious choice for the experiment.
First, we had to obtain a “webhook URL” from Slack in order to programmatically post messages to our Slack channel.
Now that we had a webhook URL, it was time to integrate Slack messages into our Node.js application. To do this, I found a handy Node.js module named node-slack8.
First, we installed the Node.js module:
npm install node-slack --save
Now, we could send Slack messages to our channel of choice with a few lines of code.
// dependency setup var Slack = require('node-slack'); var hook_url = 'hook_url_goes_here'; var slack = new Slack(hook_url); // send a test Slack message slack.send({ text: ':rocket: Nice job, I'm all set up!', channel: '#test', username: 'MyApp Bot' });
(You can find similar Slack integration packages for Ruby9, Python10 and just about any other language.)
When executed, this code produced the following message in our #test Slack channel:
11The code above is minimal, but it’s specific to the Slack API and the node-slack module. I didn’t want to be locked into any particular messaging service, so I created a generic Node.js module function to execute the service-specific code:
// Messenger.js // dependency setup var hook_url = my_hook_url; var Slack = require('node-slack'); var slack = new Slack(hook_url); module.exports = { sendMessage: function(message, channel, username) { if (!message){ console.log('Error: No message sent. You must define a message.') } else { // set defaults if username or channel is not passed in var channel = (typeof channel !== 'undefined') ? channel : "#general"; var username = (typeof username !== 'undefined') ? username : "MyApp"; // send the Slack message slack.send({ text: message, channel: channel, username: username }); return; } } };
Now we can use this module anywhere in the application with two lines of code, and if we ever decide to send messages to another service in the future, we can easily swap that out in Messenger.js.
var messenger = require('./utilities/messenger'); messenger.sendMessage(':rocket: Nice job, I'm all set up!', '#test');
Now that we had the basics set up, we were ready to start firing off messages from within the application.
The first order of business was to achieve service-bell parity. I located the success callback of the user registration function, and I added this code:
messenger.sendMessage('New user registration! ' + user.email);
Now, when someone registered, we’d get this message:
12It even dings! This was a good start, and it gave me that satisfying service-bell feeling, but it made me thirsty for more.
As my curiosity grew with each ding, I began to wonder things like, What if there was a failure to create a new user? What if a user registered, logged in but didn’t complete the onboarding process? What is the result of our scheduled tasks? Now that the groundwork was in place, answering these questions was a piece of cake.
One of the most important errors we wanted to know about was if there was a failure to create a new user. All we had to do was find the error callback in the user registration function, and add this code:
messenger.sendMessage(':x: Error While adding a new user ' + formData.email + ' to the DB. Registration aborted!' + error.code + ' ' + error.message);
Now we knew instantly when registrations failed, why they failed and, more importantly, who they failed for:
13There were all kinds of interesting places where we could send messages (pretty much anywhere with an error callback). One of those places was this generic catch-all error function:
app.use(function(err, req, res, next) { var message = ':x: Generic Server Error! '+ err + 'n Request: n' + req.protocol + '://' + req.get('host') + req.originalUrl + 'n' + JSON.stringify(req.headers) + 'Request Payload:n' + JSON.stringify(req.body); messenger.sendMessage(message, '#server-errors'); res.status(err.status || 500); res.json({'error': true }); });
This code helped us to uncover what a request looks like for unhanded exceptions. By looking at the request that triggered these errors, we could track down the root causes and fix them until there were no more generic errors.
With all of these error notifications in place, we now had comfort in knowing that if something major failed in the app, we would know about it instantly.
Next, I wanted to send a notification when a financial event happens in the application. Because our SaaS product integrates with Stripe, we created a webhook endpoint that gets pinged from Stripe when people upgrade their plan, downgrade their plan, add payment info, change payment info and many other events related to subscription payments, all of which are sent to Slack:
15There were a few cases on the front end where we wanted to understand user behavior in ways that the back end couldn’t provide, so we created an endpoint to send Slack messages directly from the front end. Because our Slack webhook URL is protected behind a POST endpoint, it was a minimal risk to expose sending Slack messages to our team via an endpoint.
With the endpoint in place, we could now fire off Slack messages with a simple AngularJS $http.post call:
// send Slack notification from the front end var message = ":warning: Slack disconnected by " + $scope.user.username; $http.post('/endpoint', message);
This helps us to answer important questions about the business: Are people registering and adding a domain name? Are they not? If someone is, is it for a really high-profile domain whose owner we would want to reach out to personally soon after they’ve added it. We can now tap into this:
16At one point, we saw a pattern of people adding a domain, removing it, then readding it within a few minutes, which clued us into an obscure bug that we probably would never have discovered otherwise.
There are also signals that a user is unhappy with the service, and these are valuable to know about. Did someone remove a domain name? Did they disconnect Slack?
18This feedback gives us an opportunity to proactively reach out and offer delightful customer support when it matters most.
One of the most interesting things to see in Slack is the result of scheduled tasks. Our SaaS product runs tasks to notify people about their website’s performance (our core service), to send transactional emails, to clean up the database and a few other things. The firing and results of these tasks sends a message to Slack:
20Now we know when a task function fires, what the result of that function is (in this case, it sends out several emails) and whether it fails for any reason.
The case study above is a practical example of what we did to monitor the GoFaster.io22 application and service. It has worked fantastic for us, but how would this concept scale to large applications that send hundreds, maybe even thousands, of messages per day? As you can imagine, this would quickly turn into a “Slackbot who cried wolf” situation, and the value would get lost in the noise.
Some notifications are more important than others, and importance will vary depending on the employee and their role. For example, software development and IT operations (DevOps) folk might only care about the server messages, whereas customer service folk would care most about what’s going on with users.
Luckily, Slack has a great solution to this problem: channels.
Channels can be created by anyone, made public or private to your organization, and shared with anyone. Once you’ve subscribed to a channel, you can control how that channel’s activities alert you. Does a new message in the channel ding every time? Does it alert your phone, too? Does it only bold the channel? All of this can be controlled for each channel by each team member to suit their needs.
Putting this idea into practice, here’s how a larger organization might organize monitor-based notifications in Slack via channels:
Having built on this idea for a few months and digested the results, we’ve found it to be an invaluable extension of our application. Without it, we would feel out of touch with what is going on with the service and would have to manually hunt down the same information via the dashboard, or database queries would be a chore.
Every application and user base is different, which means that this concept cannot be built into a service and offered to the masses. In order to be valuable, it requires a small up-front investment of time and resources to deeply integrate in your application. Once it’s up and running, the investment will pay off in the form of your team’s connectedness to your application and its users.
In conclusion, here’s a recap of the benefits of using team chat to monitor your application:
Having a real-time live feed of the metrics that matter most to you and your business will keep you closely connected to what users are doing and how the server is responding.
You will be able to react faster than ever before. You will know about failures at the same time your users do. You can immediately react to that failing endpoint, lost database connection or DDoS attack.
Reach out to that customer who has just disabled their account to offer them a discount, give personal thanks to customers who have upgraded, or just follow up with people to understand their intentions. When you know what users are doing and when they are doing it, you can easily find out why.
When your team is on the same page with the application, collaboration can center on solving problems as they arise, rather than on trying to figure out what happened, where it happened or who it happened to.
As your application and team grow, so will your monitoring needs. Slack does a great job of giving you all of the permission and notification controls necessary to ensure that the right information gets to the right people.
By logging a user name in your Slack messages, you can track every error, success message or event that a user has generated while interacting with your application simply by searching for their user name in Slack. Just know that, with a free Slack account, this is limited to the last 10,000 messages.
23I hope you’ve found this concept to be useful, and I’d love to hear other stories of teams that have implemented similar forms of monitoring, or just other interesting ways to use and build on it.
(rb, vf, yk, al, il)
Jeremy Wagner’s complete guide to HTTP/2 Server Push, what it is and how to use it.
Big news from Google: Within a few months, the infamous search engine will divide its index1 to give users better and fresher content. The long-term plan is to make the mobile search index the primary one. Why does this matter for e-commerce website owners?
Well, it will enable Google to run its ranking algorithm differently for purely mobile content. This means that mobile content won’t be extracted from desktop content to determine mobile rankings. That’s definitely something that retailers can leverage, thanks to AMP. This article outlines how to get started with AMP and how to gain an edge over the competition with your e-commerce website.
So, how do online retailers go about leveraging this big Google announcement? With AMP content! AMP (Accelerated Mobile Pages) just celebrated its one-year anniversary. It is an open-source project supported by Google that aims to reduce page-loading times on mobile. AMP pages are similar to HTML pages, with a few exceptions: Some tags are different, some rules are new, and there are plenty of restrictions on the use of JavaScript and CSS.
AMP pages get their own special carousel in Google mobile search results. No official statement has been made yet about whether these AMP pages will be getting an SEO boost.
While initially geared to blogs and news websites, AMP has introduced components that make it easy to adapt to an e-commerce website. To date, more than 150 million AMP documents are in Google’s index, with over 4 million being added every week. AMP isn’t meant purely for mobile traffic; it renders well on mobile, tablet and desktop. The AMP project’s website9 is actually coded in AMP HTML, in case you are curious to see what AMP looks like on a desktop. eBay was one of the most notable early adopters in the e-commerce realm; by July 2016, it took more than 8 million product pages live in AMP format and plans on going further.
Google is touting a reduction of 15 to 85% in page-loading time on mobile. The main advantage of AMP for retailers is that slow loading times kill conversions. Selling products to people when they want them makes a huge difference to a business’ bottom line. Many shoppers will go to a competitor’s website if yours is too slow to load. Put that in a mobile context, and a slow loading time means losing 40% of visitors — potential customers who will take their dollars elsewhere.
In brick and mortar stores, shop fronts are a big deal in attracting customers. It’s the same online, except that your storefront is supported by the speed of your customers’ Internet connection and the visibility you get on various channels (such as search engines, social media and email). Visibility is another way retailers can leverage AMP. Visibility is also a major element of the AMP equation. This is especially true in countries with limited mobile broadband speed10. And before you think this particular challenge is exclusive to developing nations, keep in mind that the US is not ranked in the top 10 countries in mobile broadband speed.
AMP pages feel like they load blazingly fast. Here’s a comparison:
User experience is central to most online retailers. A slow website with bloated code, an overwrought UI and plenty of popups is everyone’s nightmare, especially on a mobile device.
The “mobile-friendly” label was introduced by Google in late 2014 as an attempt to encourage websites to ensure a good mobile user experience. After widespread adoption of responsive design, the mobile-friendly label is being retired by Google in favor of the AMP label.
11AMP pages could be featured in a carousel and are labelled with a dedicated icon, highlighting them in search results. The search giant has recently stated that AMP would take precedence over other mobile-friendly alternatives such as in-app indexing. However, AMP is still not a ranking signal13, according to Google Webmaster Trends analyst John Mueller.
Media queries adapt the presentation of content to the device. However, the content of the page itself isn’t affected. In contrast, AMP helps make mobile web pages truly fast to load, but at a cost. Developers, designers and marketers will have to learn how to create beautiful web pages that convert using a subset of HTML with a few extensions.
The premise of AMP14 is that mobile-optimized content should load instantly anywhere. It’s a very accessible framework for creating fast-loading mobile web pages. However, compatibility with the AMP format is not guaranteed for all types of websites. This is one of the realities of a constantly evolving project such as AMP. The good news is that many of the arguments against AMP for online retailers no longer hold up.
AMP pages are now able to handle e-commerce analytics thanks to the amp-analytics variable. With this variable, statistics are available to analyze an AMP page’s performance in terms of traffic, revenue generated, clickthrough rate and bounce rate. According to the AMP project’s public roadmap15, better mobile payments are planned, after the addition of login-based access, slated for the fourth quarter of 2016.
Product and listing pages are supported in AMP, and they show great potential to add real value to the online customer journey. Keep in mind that 40% of users will abandon a website if it takes longer than 3 seconds to load16. Worse yet, 75% of consumers would rather visit a competitor website than deal with a slow-loading page.
Some of the drawbacks that have been noted are mostly due to the fact that AMP for e-commerce is rather new. There are a few concerns about the quality of the user experience offered by AMP e-commerce pages because some e-commerce functionality is not yet available, such as search bars, faceted search filters, login and cart features. However, frequent updates to the AMP format are planned, so this shouldn’t be a deterrent to those looking to implement it.
17There has been some grumbling about the format among marketers. AMP relies on simplified JavaScript and CSS. As a consequence, tracking and advertising on AMP pages is less sophisticated than on traditional HTML pages. That being said, the main drawback is that implementing AMP pages effectively will take time and effort. The code is proprietary, heavily restricts JavaScript (iframes are not allowed, for example) and even limits CSS (with some properties being outright banned).
To ensure that your website is AMP-compliant20, check the instructions provided in the AMP project’s documentation21. Keep in mind that AMP pages should be responsive22 or mobile-friendly. A best practice would be to test the implementation of AMP pages against your current mobile website using a designated subset of pages. This will give you a sample to determine whether AMP adds value to your business.
You don’t have to make your entire website AMP-compliant. Start upgrading the website progressively: Pick simple static-content pages first, such as product pages, and then move on to other types of content. This way, you can target highly visible pages in SEO results, which will lead to a big payoff for the website without your having to deal with pages that require advanced functionality not yet supported by AMP.
If your website uses a popular CMS, then becoming AMP-compliant could be as easy as installing a plugin.
Let’s break down the process according to the customer journey. AMP offers a selection of prebuilt components to help you craft an enjoyable user experience on an e-commerce website (along with some evolving tools to help you collect data in order to improve it). You can implement four major AMP elements along key points in the customer’s purchasing journey, including on the home page, browsing pages, landing pages, product pages and related product widgets:
The entire purchasing flow can’t be 100% AMP-compliant yet, so you’ll have to plan a gateway to a regular non-AMP page for ordering and completing purchases.
Users will often start their purchasing journey on a website’s home page or a product category page, because these pages are prominent in search engine results. These pages are great candidates for AMP, as eBay has shown25 by making many of its category pages AMP-compliant. Typically, category pages are static and showcase products for sale. The amp-carousel feature26 offers a way to browse other products in a mobile-optimized way. These products can be organized into subcategories that fit the user’s needs. You can view the annotated code to create a product page over on AMP by Example27.
28After browsing to a category page, the next step for our user would be to find an interesting product and click on it. In an AMP-compliant flow, this would lead the user to an AMP product page29.
30Your AMP product page could include the following:
amp-carousel31 and amp-video elements32;amp-accordion tag33 and which enable the user to easily share the product’s URL via the amp-social-share element34;amp-sidebar35.Here is a preview of what the AMP carousel looks like on mobile:
Showing related products36 benefits the retailer’s bottom line and the user’s experience. The first product that a user browses to isn’t always the one that fits their need. You can show related products in AMP in two ways:
amp-list37 to fire a CORS request to a JSON endpoint that supplies the list of related products. These related products can be populated in an amp-mustache4138 template on the client. This approach is personalized because the content is dynamically generated server-side for each request.Personalization is a big deal in e-commerce because it increases conversions. To dip into personalization in the AMP format, you can leverage the amp-access39 component to display different blocks of content according to the user’s status. To make it all work, you have to follow the same method as we did with the amp-list40 component: Fire a request at a JSON endpoint, and then present the data in an amp-mustache4138 template. Keep in mind that personalization doesn’t have a leg to stand on without reliable data. Google has been actively extending the tracking options available in AMP.
You can track users at an aggregate level using the amp-analytics component6242; AMP supports several analytics vendors.43
Sidenote: In case you see cdn.ampproject.org in your Google Analytics data, this is normal for AMP pages; cdn.ampproject.org is a cache that belongs to Google. No need to worry about this strange newcomer to your Google Analytics data!
AMP now supports some analytics products, such as Adobe’s and Google’s own. The type attribute will quickly configure the respective product within the code. Here’s an example of type being used for Google Analytics:
<amp-analytics type="googlenalytics">
And here are the types for some of the most common analytics vendors:
adobeanalyticsgoogleanalyticssegmentwebtrekkmetrikaGoogle Tag Manager44 has taken AMP support one step further with AMP containers. You can now create a container for your AMP pages.
45More than 20 tag types are available out of the box, including third-party vendor tags. Alongside a wider selection of tags, Google has provided built-in variables dedicated to AMP tracking, making it easier for marketers and developers to tag their pages.
If you are not using Google Tag Manager, you can implement your tag management service in one of two ways:
amp-analytics and conducts marketing management in the back end.The endpoint approach is the same as the standard approach. The config approach consists of creating a unique configuration for amp-analytics that is specific to each publisher and that includes all of the publisher’s compatible analytics packages. A publisher would configure using a syntax like this:
<amp-analytics config="https://your-dream-tag-manager.example.com/user-id.json">
Many online retailers rely on advertising or showing related products throughout their website to boost revenue. The AMP format is bootstrapped to show ads through <amp-ad> and <amp-embed>. The documentation is quite clear47 on how to implement ads, and the good news is that a wide variety of networks are already supported. Although iframes are not allowed in AMP, two embed types support ads with <amp-embed>: Taboola and Zergnet. If you plan on using ads in AMP, follow these principles48 in your development work:
The previous step was a tricky one because it entails maintaining a seamless user experience while the user transitions to a full HTML page. The process should be fast and consistent for the user. An experience that isn’t consistent with the preceding AMP journey could hurt conversions. If your website is a progressive web app, then amp-install-serviceworker49 is an ideal way to bridge both types of pages within the customer journey, because it allows your AMP page to install a service worker on your domain, regardless of where the user is viewing the AMP page. This means that caching content from your progressive web app can be done preemptively to ensure that the transition is smooth for the customer, because all of the content needed is cached in advance. An easy way to experience the entire AMP e-commerce experience is to head on over to eBay50; see how the company handles the transition from AMP to an HTML checkout process.
AMP works within a smart caching model that enables platforms that refer traffic to AMP pages to use caching and prerendering in order to load web pages super-fast. Be aware of this when analyzing traffic and engagement because you might see less traffic to your own origin when AMP pages are originally hosted (this is why we referred to cdn.ampproject.org in Google Analytics data). The balance of traffic will most likely show up through proxied versions of your pages served by AMP caches.
52A whole host of useful resources are available if you have any questions:
eBay has shared its experience57 in implementing AMP for its own e-commerce platform:
Mind you, there are some complex parts:
amp-analytics component6242. The component can be configured in various ways, but it is still not sufficient for the granular tracking needs of most online retailers.However, once you get past the internal hurdles, the payoff can be great. Check out the examples provided by eBay for camera drones63 and the Sony PlayStation64. (Use a mobile device, of course, otherwise you will be redirected to the desktop version.)
SEO experts are pushing for AMP adoption because some see it as a mobile-visibility asset to be leveraged. Here are some SEO points to ensure you get the most out of AMP:
<link rel="canonical" href="[canonical URL]" /> tag on the AMP page and <link rel="amphtml" href="[AMP URL]" /> on the regular page. For a standalone AMP page (one that doesn’t have a non-AMP version), specify it as the canonical version: <link rel="canonical" href="https://www.example.com/url/to/amp-document.html" />./amp/ to the path of the URL.An e-commerce website can’t be 100% compliant with AMP, but there are benefits to adopting the format early on. Online retailers looking for an edge against fierce competition might be wise to turn to this format to grab the attention of mobile customers and nudge open their wallets. More and more websites are converting to the AMP format to increase or maintain their mobile traffic. For an online retailer that has a multi-channel or mobile-first strategy to acquire and retain customers, AMP might be a great way to future-proof their online marketing efforts.
(da, vf, al, yk, il)
The world is constantly evolving with frameworks, such as the Internet of Things (IoT) and virtual reality (VR). These and many others are opening opportunities to rethink how we approach prototyping: They introduce avenues to marry the digital software with the tangible aspect of the overall user engagement.
This two-article series will introduce readers of different backgrounds to prototyping IoT experiences with minimum code knowledge, starting with affordable proof of concept platforms, before moving to costly commercial offerings.
We will do this by going over a personal experience I had as a user experience designer while learning the basics of an IoT platform named “Adafruit IO”. This will be a nice introductory case study.
The following are some assumptions about you:
Disclaimer:I am not an electronics engineer or a developer. Please always be careful when exploring electricity and hardware. This tutorial is meant to inspire you to do additional research before finding what works for your circumstances!
If this sounds appealing, let’s dive into part 1!
IoT talk is sometimes unnecessary complex. To reduce the jargon, I will use some reader-friendly terms, as defined below.
On a cold winter day, I read an article on smart homes being the future, which immediately inspired me to turn my home into a smart one. This translated into several commercial product purchases, including devices from the Nest family, which just whet my appetite.
Controlling my air conditioning and furnace and detecting possible carbon monoxide emissions were not enough! I wanted to go further by having monitoring capabilities over my home security. This includes:
Getting to the point of picking Adafruit IO as the solution was not a simple journey. Before deciding on that platform and the HUZZAH ESP8266 board, I tried several other solutions, with varying success:
My vision was to have multiple sensors that could be viewed and controlled from a computer or mobile device independently at any time. To accomplish this, I needed both a Wi-Fi-enabled hardware board and a software platform that could talk to it and any attached sensors.
I decided to be more strategic in my choices, so I came up with a list of criteria, in order of priority:
After exploring the three approaches mentioned further above, I ruled out the following additional equipment, based on the five criteria. Keep in mind that I am giving you the high-level details — a whole article could be written on selecting a board!
| Board | What it is | Why I ruled it out |
|---|---|---|
| Arduino Yun10 | Offers both wired and wireless Internet connectivity, expandable RAM and onboard memory. The board has a Linux-based distribution, making it a powerful networked computer. | The price of the controller ($69) and the bulky size proved to be too limiting. Also, I didn’t need something so powerful. I ended up buying one to test out for a garden watering project. |
| Raspberry Pi 3 Model B11 | In addition to offering wired and wireless connectivity, it has HDMI and audio ports, Bluetooth integration and support for use of a custom-sized SD card with different operating systems. | While I could load a Linux-based operating system and use the Python language to accomplish anything, that’s not what I needed. I ended up using this platform for other projects. The $40 price tag and the bulky size were also limiting factors. |
| Raspberry Pi Zero12 | This $5 board packs a big punch. The powerful CPU and large RAM made it a strong contender, as did the small size and large number of GPIO pins. | Two things nixed this board. It doesn’t have on-board Wi-Fi, and so requires additional equipment. And because it is very popular, finding this board is hard. In the US, it is sold only at Micro Center13, which limits it to one per home per month. (Note: At the time of writing v1.3 of the board with on board Wi-Fi and Bluetooth was not yet available.) |
Side note: For more information on choosing a board for your hardware prototyping project, you can consult the excellent “Makers’ Guide to Boards14.”
Further researching led me to Adafruit’s HUZZAH ESP826615 board, which is but one variation of the ESP8266 chipset; there are others, such as the NodeMCU LUA16. Each has unique capabilities, so choose wisely. Here is why I selected the HUZZAH:
Deciding to start small, I wanted to build a sensor that tracks whether a door is open. The rest of this first article will focus on the hardware for this use case, but much of the wiring will scale to other types of sensors.
The following I am assuming you having just laying around: a computer, a solder wire and a solder iron cleaner.
Total: $30 to $40 on average (using US parts)
Before getting to the details of how to put the rig together, let’s talk about what the goal is. By the end of this first article, you should have something similar to what you see below. With this setup, you will have a mini-computer (the board) capable of collecting sensor data from your environment and communicating it to the cloud (Adafruit IO) over Wi-Fi.

The first step is to assemble the HUZZAH board by soldering on its pin headers, including both the board leg headers and the FTDI header. Adafruit has a step-by-step tutorial30 on this.
When you are soldering the first leg header, ensure that the board is not tilting one way, which would result in the pins being soldered at an angle. A trick I used is to put a bit of putty under the board to even it out as it is being plugged into the breadboard.
Once you have soldered all of the headers, insert the board in the breadboard with the antenna (the wavy gold line) facing outwards.
Insert the supply at the opposite end of the breadboard, with the top and bottom legs fitting in the + and – breadboard rails. This is how power will be passed to the breadboard.
Next, set the yellow jumpers for both rails to 3.3 volts, which is the voltage used by the HUZZAH board.
Note: Depending on your breadboard, the – and + might not match the alignment of the power supply jumpers. That’s fine as long as you remember that the power supply dictates which breadboard rail carries which electrical signal!
Connect the HUZZAH board to the power:
Connecting the sensor is very easy:



If you are curious to learn how reed switches operate, Chris Woodford has more information34 on the subject.
As a last step, plug the 9-volt adapter into a power outlet, then into the breadboard power supply. Push the white button. If everything is correctly wired, you should see several lights flicker on, including for the power supply (the green one), the board power (red), the Wi-Fi (blue) and the sensor (red if the magnet is touching the sensor).
At this point, you can start writing the code for the rig, but I find this is a good opportunity to test the mounting of the container box. This is not a permanent mounting, but a trial run to gauge the rig’s overall dimensions and the best fit. Before doing that, you need to take some steps first.
Step 1: Using your soldering iron, melt one hole in the left side of the container for the power adapter plug, and three smaller ones on the right for the individual sensor wires.
Warning: Make sure to do this in a well-ventilated area, so that you don’t breathe in fumes. After that, clean your soldering iron’s tip with a nonabrasive sponge.


Step 2: Put the entire rig in the container, and pass the cables through the holes.

Step 3: Close the container, and mount it on the wall with the putty. Tapes of various types won’t work well. Alternatively, you could punch holes in the bottom of the container to mount with screws, but make sure they are insulated with electrical tape to avoid short-circuiting any electronics.
I have also tried using hot glue. I think it is messy, but it is not all that more expensive, and you can pick one up on the cheap38 if you prefer that method.


Step 4: Use a combination of LEGO pieces and putty to mount the sensor and the accompanying magnet to the door.

Now that the rig is all wired up, you can connect to it with the FTDI cable and start adding the code that will make the sensor work.
In this first article of our two-part series, we’ve identified the problem (home security), assessed the merit of an IoT setup, and discussed the rationale involved in selecting a particular board. This was followed by a step-by-step guide on how to put together all of the hardware components into a working rig.
In doing so, we’ve learned the basics of electronics. In the second and final article in this series, we will add code to the rig we’ve built here, so that we can start interacting with the environment. Then, we will build custom user interfaces to view the data from anywhere, while discussing at a high level the security implications of the software configuration.
Stay tuned!
(da, vf, al, il)
JavaScript Basics course * Boilrplate * LabWorm * The History of the Web * React Conf 2017 * AutoDraw * Griddy…
The landscape for the performance-minded developer has changed significantly in the last year or so, with the emergence of HTTP/2 being perhaps the most significant of all. No longer is HTTP/2 a feature we pine for. It has arrived, and with it comes server push!
Aside from solving common HTTP/1 performance problems (e.g., head of line blocking and uncompressed headers), HTTP/2 also gives us server push! Server push allows you to send site assets to the user before they’ve even asked for them. It’s an elegant way to achieve the performance benefits of HTTP/1 optimization practices such as inlining, but without the drawbacks that come with that practice.
In this article, you’ll learn all about server push, from how it works to the problems it solves. You’ll also learn how to use it, how to tell if it’s working, and its impact on performance. Let’s begin!
Accessing websites has always followed a request and response pattern. The user sends a request to a remote server, and with some delay, the server responds with the requested content.
The initial request to a web server is commonly for an HTML document. In this scenario, the server replies with the requested HTML resource. The HTML is then parsed by the browser, where references to other assets are discovered, such as style sheets, scripts and images. Upon their discovery, the browser makes separate requests for those assets, which are then responded to in kind.
The problem with this mechanism is that it forces the user to wait for the browser to discover and retrieve critical assets until after an HTML document has been downloaded. This delays rendering and increases load times.
With server push, we have a solution to this problem. Server push lets the server preemptively “push” website assets to the client without the user having explicitly asked for them. When used with care, we can send what we know the user is going to need for the page they’re requesting.
Let’s say you have a website where all pages rely on styles defined in an external style sheet named styles.css. When the user requests index.html from the server, we can push styles.css to the user just after we begin sending the response for index.html.
3Rather than waiting for the server to send index.html and then waiting for the browser to request and receive styles.css, the user only has to wait for the server to respond with bothindex.html and styles.css on the initial request. This means that the browser can begin rendering the page faster than if it had to wait.
As you can imagine, this can decrease the rendering time of a page. It also solves some other problems, particularly in front-end development workflows.
While reducing round trips to the server for critical content is one of the problems that server push solves, it’s not the only one. Server push acts as a suitable alternative for a number of HTTP/1-specific optimization anti-patterns, such as inlining CSS and JavaScript directly into HTML, as well as using the data URI scheme5 to embed binary data into CSS and HTML.
These techniques found purchase in HTTP/1 optimization workflows because they decrease what we call the “perceived rendering time” of a page, meaning that while the overall loading time of a page might not be reduced, the page will appear to load faster for the user. It makes sense, after all. If you inline CSS into an HTML document within <style> tags, the browser can begin applying styles immediately to the HTML without waiting to fetch them from an external source. This concept holds true with inlining scripts and inlining binary data with the data URI scheme.

Seems like a good way to tackle the problem, right? Sure — for HTTP/1 workflows, where you have no other choice. The poison pill we swallow when we do this, however, is that the inlined content can’t be efficiently cached. When an asset like a style sheet or JavaScript file remains external and modular, it can be cached much more efficiently. When the user navigates to a subsequent page that requires that asset, it can be pulled from the cache, eliminating the need for additional requests to the server.
7When we inline content, however, that content doesn’t have its own caching context. Its caching context is the same as the resource it’s inlined into. Take an HTML document with inlined CSS, for instance. If the caching policy of the HTML document is to always grab a fresh copy of the markup from the server, then the inlined CSS will never be cached on its own. Sure, the document that it’s a part of may be cached, but subsequent pages containing this duplicated CSS will be downloaded repeatedly. Even if the caching policy is more lax, HTML documents typically have limited shelf life. This is a trade-off that we’re willing to make in HTTP/1 optimization workflows, though. It does work, and it’s quite effective for first-time visitors. First impressions are often the most important.
These are the problems that server push addresses. When you push assets, you get the practical benefits that come with inlining, but you also get to keep your assets in external files that retain their own caching policy. There is a caveat to this point, though, and it’s covered toward the end of this article. For now, let’s continue.
I’ve talked enough about why you should consider using server push, as well as the problems that it fixes for both the user and the developer. Now let’s talk about how it’s used.
Using server push usually involves using the Link HTTP header, which takes on this format:
Link: </css/styles.css>; rel=preload; as=style
Note that I said usually. What you see above is actually the preload resource hint9 in action. This is a separate and distinct optimization from server push, but most (not all) HTTP/2 implementations will push an asset specified in a Link header containing a preload resource hint. If either the server or the client opts out of accepting the pushed resource, the client can still initiate an early fetch for the resource indicated.
The as=style portion of the header is not optional. It informs the browser of the pushed asset’s content type. In this case, we use a value of style to indicate that the pushed asset is a style sheet. You can specify other content types10. It’s important to note that omitting the as value can result in the browser downloading the pushed resource twice. So don’t forget it!
Now that you know how a push event is triggered, how do we set the Link header? You can do so through two routes:
httpd.conf or .htaccess);header function).Link Header in Your Server Configuration LinkHere’s an example of configuring Apache (via httpd.conf or .htaccess) to push a style sheet whenever an HTML file is requested:
<FilesMatch ".html$"> Header set Link "</css/styles.css>; rel=preload; as=style" <FilesMatch>
Here, we use the FilesMatch directive to match requests for files ending in .html. When a request comes along that matches this criteria, we add a Link header to the response that tells the server to push the resource at /css/styles.css.
Side note: Apache’s HTTP/2 module can also initiate a push of resources using the H2PushResource directive. The documentation for this directive states that this method can initiate pushes earlier than if the Link header method is used. Depending on your specific setup, you may not have access to this feature. The performance tests shown later in this article use the Link header method.
As of now, Nginx doesn’t support HTTP/2 server push, and nothing so far in the software’s changelog11 has indicated that support for it has been added. This may change as Nginx’s HTTP/2 implementation matures.
Link Header in Back-End Code LinkAnother way to set a Link header is through a server-side language. This is useful when you aren’t able to change or override the web server’s configuration. Here’s an example of how to use PHP’s header function to set the Link header:
header("Link: </css/styles.css>; rel=preload; as=style");
If your application resides in a shared hosting environment where modifying the server’s configuration isn’t an option, then this method might be all you’ve got to go on. You should be able to set this header in any server-side language. Just be sure to do so before you begin sending the response body, to avoid potential runtime errors.
All of our examples so far only illustrate how to push one asset. What if you want to push more than one? Doing that would make sense, right? After all, the web is made up of more than just style sheets. Here’s how to push multiple assets:
Link: </css/styles.css>; rel=preload; as=style, </js/scripts.js>; rel=preload; as=script, </img/logo.png>; rel=preload; as=image
When you want to push multiple resources, just separate each push directive with a comma. Because resource hints are added via the Link tag, this syntax is how you can mix in other resource hints with your push directives. Here’s an example of mixing a push directive with a preconnect resource hint:
Link: </css/styles.css>; rel=preload; as=style, <https://fonts.gstatic.com>; rel=preconnect
Multiple Link headers are also valid. Here’s how you can configure Apache to set multiple Link headers for requests to HTML documents:
<FilesMatch ".html$"> Header add Link "</css/styles.css>; rel=preload; as=style" Header add Link "</js/scripts.js>; rel=preload; as=script" <FilesMatch>
This syntax is more convenient than stringing together a bunch of comma-separated values, and it works just the same. The only downside is that it’s not quite as compact, but the convenience is worth the few extra bytes sent over the wire.
Now that you know how to push assets, let’s see how to tell whether it’s working.
So, you’ve added the Link header to tell the server to push some stuff. The question that remains is, how do you know if it’s even working?
This varies by browser. Recent versions of Chrome will reveal a pushed asset in the initiator column of the network utility in the developer tools.
Furthermore, if we hover over the asset in the network request waterfall, we’ll get detailed timing information on the asset’s push:
14Firefox is less obvious in identifying pushed assets. If an asset has been pushed, its status in the browser’s network utility in the developer tools will show up with a gray dot.
If you’re looking for a definitive way to tell whether an asset has been pushed by the server, you can use the nghttp command-line client18 to examine a response from an HTTP/2 server, like so:
nghttp -ans https://jeremywagner.me
This command will show a summary of the assets involved in the transaction. Pushed assets will have an asterisk next to them in the program output, like so:
id responseEnd requestStart process code size request path 13 +50.28ms +1.07ms 49.21ms 200 3K / 2 +50.47ms * +42.10ms 8.37ms 200 2K /css/global.css 4 +50.56ms * +42.15ms 8.41ms 200 157 /css/fonts-loaded.css 6 +50.59ms * +42.16ms 8.43ms 200 279 /js/ga.js 8 +50.62ms * +42.17ms 8.44ms 200 243 /js/load-fonts.js 10 +74.29ms * +42.18ms 32.11ms 200 5K /img/global/jeremy.png 17 +87.17ms +50.65ms 36.51ms 200 668 /js/lazyload.js 15 +87.21ms +50.65ms 36.56ms 200 2K /img/global/book-1x.png 19 +87.23ms +50.65ms 36.58ms 200 138 /js/debounce.js 21 +87.25ms +50.65ms 36.60ms 200 240 /js/nav.js 23 +87.27ms +50.65ms 36.62ms 200 302 /js/attach-nav.js
Here, I’ve used nghttp on my own website19, which (at least at the time of writing) pushes five assets. The pushed assets are marked with an asterisk on the left side of the requestStart column.
Now that we can identify when assets are pushed, let’s see how server push actually affects the performance of a real website.
Measuring the effect of any performance enhancement requires a good testing tool. Sitespeed.io20 is an excellent tool available via npm21; it automates page testing and gathers valuable performance metrics. With the appropriate tool chosen, let’s quickly go over the testing methodology.
I wanted measure the impact of server push on website performance in a meaningful way. In order for the results to be meaningful, I needed to establish points of comparison across six separate scenarios. These scenarios are split across two facets: whether HTTP/2 or HTTP/1 is used. On HTTP/2 servers, we want to measure the effect of server push on a number of metrics. On HTTP/1 servers, we want to see how asset inlining affects performance in the same metrics, because inlining is supposed to be roughly analogous to the benefits that server push provides. Specifically, these scenarios are the following:
In each scenario, I initiated testing with the following command:
sitespeed.io -d 1 -m 1 -n 25 -c cable -b chrome -v https://jeremywagner.me
If you want to know the ins and outs of what this command does, you can check out the documentation24. The short of it is that this command tests my website’s home page at https://jeremywagner.me25 with the following conditions:
Three metrics were collected and graphed from each test:
async attribute on <script> tags can help to prevent parser blocking.With the parameters of the test determined, let’s see the results!
Tests were run across the six scenarios specified earlier, with the results graphed. Let’s start by looking at how first paint time is affected in each scenario:
26Let’s first talk a bit about how the graph is set up. The portion of the graph in blue represents the average first paint time. The orange portion is the 90th percentile. The grey portion represents the maximum first paint time.
Now let’s talk about what we see. The slowest scenarios are both the HTTP/2- and HTTP/1-driven websites with no enhancements at all. We do see that using server push for CSS helps to render the page about 8% faster on average than if server push is not used at all, and even about 5% faster than inlining CSS on an HTTP/1 server.
When we push all assets that we possibly can, however, the picture changes somewhat. First paint times increase slightly. In HTTP/1 workflows where we inline everything we possibly can, we achieve performance similar to when we push assets, albeit slightly less so.
The verdict here is clear: With server push, we can achieve results that are slightly better than what we can achieve on HTTP/1 with inlining. When we push or inline many assets, however, we observe diminishing returns.
It’s worth noting that either using server push or inlining is better than no enhancement at all for first-time visitors. It’s also worth noting that these tests and experiments are being run on a website with small assets, so this test case may not reflect what’s achievable for your website.
Let’s examine the performance impacts of each scenario on DOMContentLoaded time:
28The trends here aren’t much different than what we saw in the previous graph, except for one notable departure: The instance in which we inline as many assets as practical on a HTTP/1 connection yields a very low DOMContentLoaded time. This is presumably because inlining reduces the number of assets needed to be downloaded, which allows the parser to go about its business without interruption.
Now, let’s look at how page-loading times are affected in each scenario:
30The established trends from earlier measurements generally persist here as well. I found that pushing only the CSS realized the greatest benefit to loading time. Pushing too many assets could, on some occasions, make the web server a bit sluggish, but it was still better than not pushing anything at all. Compared to inlining, server push yielded better overall loading times than inlining did.
Before we conclude this article, let’s talk about a few caveats you should be aware of when it comes to server push.
Server push isn’t a panacea for your website’s performance maladies. It has a few drawbacks that you need to be cognizant of.
In one of the scenarios above, I am pushing a lot of assets, but all of them altogether represent a small portion of the overall data. Pushing a lot of very large assets at once could actually delay your page from painting or being interactive sooner, because the browser needs to download not only the HTML, but all of the other assets that are being pushed alongside of it. Your best bet is to be selective in what you push. Style sheets are a good place to start (so long as they aren’t massive). Then evaluate what else makes sense to push.
This is not necessarily a bad thing if you have visitor analytics to back up this strategy. A good example of this may be a multi-page registration form, where you push assets for the next page in the sign-up process. Let’s be crystal clear, though: If you don’t know whether you should force the user to preemptively load assets for a page they haven’t seen yet, then don’t do it. Some users might be on restricted data plans, and you could be costing them real money.
Some servers give you a lot of server push-related configuration options. Apache’s mod_http2 has some options for configuring how assets are pushed. The H2PushPriority setting32 should be of particular interest, although in the case of my server, I left it at the default setting. Some experimentation could yield additional performance benefits. Every web server has a whole different set of switches and dials for you to experiment with, so read the manual for yours and find out what’s available!
There has been some gnashing of teeth over whether server push could hurt performance in that returning visitors may have assets needlessly pushed to them again. Some servers do their best to mitigate this. Apache’s mod_http2 uses the H2PushDiarySize setting33 to optimize this somewhat. H2O Server has a feature called Cache Aware server push34 that uses a cookie mechanism to remember pushed assets.
If you don’t use H2O Server, you can achieve the same thing on your web server or in server-side code by only pushing assets in the absence of a cookie. If you’re interested in learning how to do this, then check out a post I wrote about it on CSS-Tricks35. It’s also worth mentioning that browsers can send an RST_STREAM frame to signal to a server that a pushed asset is not needed. As time goes on, this scenario will be handled much more gracefully.
As sad it may seem, we’re nearing the end of our time together. Let’s wrap things up and talk a bit about what we’ve learned.
If you’ve already migrated your website to HTTP/2, you have little reason not to use server push. If you have a highly complex website with many assets, start small. A good rule of thumb is to consider pushing anything that you were once comfortable inlining. A good starting point is to push your site’s CSS. If you’re feeling more adventurous after that, then consider pushing other stuff. Always test changes to see how they affect performance. You’ll likely realize some benefit from this feature if you tinker with it enough.
If you’re not using a cache-aware server push mechanism like H2O Server’s, consider tracking your users with a cookie and only pushing assets to them in the absence of that cookie. This will minimize unnecessary pushes to known users, while improving performance for unknown users. This not only is good for performance, but also shows respect to your users with restricted data plans.
All that’s left for you now is to try out server push for yourself. So get out there and see what this feature can do for you and your users! If you want to know more about server push, check out the following resources:
Thanks to Yoav Weiss39 for clarifying that the as attribute is required (and not optional as the original article stated), as well as a couple of other minor technical issues. Additional thanks goes to Jake Archibald40 for pointing out that the preload resource hint is an optimization distinct from server push.
This article is about an HTTP/2 feature named server push. This and many other topics are covered in Jeremy’s book Web Performance in Action41. You can get it or any other Manning Publications42 book for 42% off with the coupon code sswagner!
(rb, vf, al, il)
The Material Design color tool helps you create, share, and apply color palettes to your UI, as well as measure the accessibility level of any color combination.
From time to time, we need to take some time off, and actually, I’m glad that this reading list is a bit shorter as the ones you’re used to. Because one thing that really stuck with me this week was Eric Karjaluoto’s article.
In his article, he states that, “Taking pride in how busy we are is one of the worst ideas we ever had.” So, how about reading just a few articles this week for a change and then take a complete weekend off to recharge your battery?
Expect-CT. Scott Helme explains when and how you should use it10.
13And with that, I’ll close for this week. If you like what I write each week, please support me with a donation19 or share this resource with other people. You can learn more about the costs of the project here20. It’s available via email, RSS and online.
— Anselm
Did you know that bandwidth overage charges are (still) a problem and most users prefer not to rely on a developer? Well, I talked to 917 (real-life) users and created a guide to help others find the e-commerce software that suits them best.
I completed this guide by searching for websites built with e-commerce software (you can verify by looking at the source code — certain code strings are unique to the software). Once I found a website, I (or one of my virtual assistants) would email the owner and ask if they’d recommend a particular software. Typically, they’d reply and I’d record their response in a spreadsheet (and personally thank them). Occasionally, I would even go on the phone to speak with them directly (although I quickly found out that this took too much time).
Here’s what I discovered.
I calculated customer satisfaction by finding the percentage of active users who recommend the software:
| E-commerce software | Recommendation % |
|---|---|
| Shopify | 98% |
| Squarespace | 94% |
| Big Cartel | 91% |
| WooCommerce | 90% |
| OpenCart | 88% |
| Jumpseller | 86% |
| GoDaddy | 83% |
| CoreCommerce | 80% |
| BigCommerce | 79% |
| Ubercart | 78% |
| Wix | 76% |
| Magento | 74% |
| Weebly | 74% |
| 3dcart | 72% |
| PrestaShop | 70% |
| Goodsie | 65% |
| Spark Pay | 65% |
| Volusion | 51% |
Shopify is the pretty clear winner, with Squarespace close behind — but both companies are actually complementary. Shopify is a complete, robust solution that works for both small and large stores, while Squarespace is a simple, approachable platform that works well for stores just starting out. (Worth noting: I’ve done similar surveys for portfolio builders5 and landing-page builders6, and Shopify is the only company I’ve seen score higher than 95% in customer satisfaction.)
But looking only at customer satisfaction is not enough. After all, e-commerce platforms have different strengths. So, I also asked users what they like and dislike about their software and found some important insights about each company.
App store and features
“The best thing is that you don’t need a developer to add features… there’s a ton of apps available.” | “Their partner ecosystem is best.” | “Shopify has any feature under the sun — if you think you need it, someone already created an app.” | “Access to Shopify Apps is great.” | “There’s heaps of third-party apps you can integrate easily that I believe are essential to growing a business.” | “So many third-party apps, templates that other platforms aren’t popular enough to have.” | “There are many apps that can help with customization issues.” | “There are a ton of great third-party apps for extended functionality.”
Ease of use
“Easy to set up without having specific skills.” | “Intuitive user interface.” | “Simple to use.” | “It is very easy to start selling online.” | “Easy UI, pretty intuitive.” | “The interface is excellent for managing e-commerce.” | “It’s really clean and easy to manage.” | “Shopify provides a very straightforward way to add products, edit options and to apply different themes.” | “More than anything, very simple.” | “It’s simple and intuitive.” | “Very user-friendly.” | “Super user-friendly for non-computer guys like myself.” | “The back end is exceptional.”
Ease of use
“It’s very easy to use.” | “The e-commerce is so easy to use.” | “It’s easy to configure, simple to add, delete and modify our inventory, and most importantly it allows us to easily keep track of our ins and outs with helpful metrics and sales graphs.” | “It’s very easy to set up.” | “The user interface is easy to use.” | “Commerce is really nice and easy to set up.” | “Love the interface, very easy to work with.” | “I find it easy to use.” | “It was pretty easy to set up and has been a snap to maintain.” | “It’s all pretty smooth and easy.” | “It’s super-easy.” | “I’ve tried Drupal, WordPress… the interface and creative ability of Squarespace is much superior.”
Templates
“Has some great templates for a good-looking website.” | “Squarespace is an easy way to get a great looking site.” | “The sites are beautiful.” | “The templates and editing features on the blog and site are super-easy.” | “The thing I like most are the beautiful and easy templates.”
Limitations
“The only thing I would say they need to improve is allowing more than one currency on the e-commerce site, which currently is not available.” | “It works pretty good for basic sales of items.” | “There are some limitations in terms of customizing, but they are minor.” | “If you are using it as is and just need the limited feature set that it comes with, it’s a great option.” | “Overall, it’s great for putting a few simple products up, but if you need anything beyond their default cart options, get a proper Squarespace developer or someone to set up a Shopify site for you.” | “It is really a great place to start, but unfortunately a place that is easily transitioned out of once the business begins to grow.”
Shipping
“My partners have had some concerns with the shipping aspect, though.” | “Yes, I would recommend it, but Squarespace needs to have calculated shipping for all the plans.” | “The shipping is still something I wish was a little easier.” | “The only thing I would say is that, for me, the shipping options are more limited than I would like.” | “There are some features I wish were better implemented in the base package (like shipping integration for international orders), but I’d recommend it.”
9Good for new stores
“I would recommend Big Cartel for smaller shops.” | “I would recommend it, especially startup users.” | “It’s a great place to start out!” | “We’d recommend it for similar businesses, especially those just getting started.” | “It is a great platform for something really simple and was very easy to set up.” | “Big Cartel is great for beginning stages of a store. We’re actually entertaining moving to a new platform right now.” | “It’s quite good for a small company or startup, for sure.” | “I’m finding that in the early stages of the business, it’s extremely handy for stock listing and very straightforward to use.”
Ease of use
“It’s very easy to use.” | “It’s very easy to use, navigate and customize the shopfront.” | “I am particularly fond of the back end and the admin tools. They make maintaining and shipping products a breeze.” | “It’s super-simple and really user-friendly.” | “I’m not savvy, so it works well for my skill level.” | “Easy to set up… and easy to control and set inventory.” | “They make it so easy to have a beautiful website.” | “For just a few items, Big Cartel totally gets the job done and is user-friendly.”
Price
“I only have to pay $9.99 a month for Big Cartel. That’s a huge perk for me.” | “Low price point and easy to use.” | “The rates are the lowest considering all the things you’re able to do.” | “I have found the cost is a lot better than my Etsy store.” | “You get a great platform for a great price.” | “Compared to Etsy, the fees are ridiculously cheap!” | “One fee a month, no item fees per listing… There is an option to open a store for free with five listings. This is an amazing feature.” | “Their prices are also very reasonable.”
Limitations
“Lacking in features.” | “It is limited in terms of themes… You always know when you’re on a Big Cartel site.” | “It does most of what I expect of it, but also has limitations.” | “The one problem I have is that the only options for receiving payments are PayPal and Stripe.” | “If you want more of an interactive site with blogs and videos and whatnot, I think there are better options out there.” | “We are currently moving over to Shopify because we have maxed out Big Cartel’s limited 300-item store capacity. That is the only downside of Big Cartel.” | “You are limited by what Big Cartel allows you to do. For example, there are certain promotions that I would like to do, but currently Big Cartel has no way of allowing it.”
11Extensions
“Many useful plugins for it.” | “So many features.” | “There are plenty of add-ons with it to customize shop as we need.” | “Fully customizable.” | “The plugin architecture is great.” | “It also has a lot of plugins.” | “It’s very good if you are looking for something that can do anything… there are extensions available, and coders who can write plugins.” | “I’m a fan of the plugins because it allows for a lot of customization.”
Ecosystem
“The ecosystem is well supported.” | “Great support with a whole online community dedicated to it.” | “I’m always able to find the answer to any question I have, either through the official WooCommerce knowledge base or in the community forums.”
Developer may be required
“Custom modifications do require somewhat advanced developer knowledge.” | “WooCommerce does require knowledge in website building… At one point, it became extremely slow, and I couldn’t figure out where the problem was.” | “What should be native often requires plugins or coding.” | “Very customizable with some code editing.” | “WooCommerce definitely requires a solid knowledge of the inner workings.” | “There definitely is a learning curve, but it is not too hard to master.” | “It had to be highly customized for us by our website developers.”
13Extensions
“There are plenty of extensions (free and for purchase).” | “Tons of extensions to make it really awesome.” | “OpenCart extensions… have been very valuable and reliable.” | “Customization does need IT capabilities, though.” | “The software is only as good as its implementers.”
Often requires a developer
“It took some PHP programming to get it completely as we wish, but now it works fine and suits my goals well.” | “If you do not have someone capable of working behind the scenes, it would be difficult to manage.” | “I’d recommend it if and only if you have at least some knowledge web programming (PHP, JavaScript, XML, MySQL, etc.).” | “Not recommended for anyone without some web programming knowledge.” | “With the right technical staff, yes I would.” | “If you would be a serious user, I can recommend OpenCart, but also I would recommend hiring a developer to make all custom improvements.” | “Yes, I would recommend it as a good platform with cheap extensions.” | “There is also a large amount of high-quality extensions.” | “Tons of plugins, both free to paid.”
Extensions can create bugs
“When you modify it, it does amazing things but is super-finicky.” | “Buying and installing extensions is a bad idea… It’s not a plug-and-play procedure.” | “As we grew bigger, there have been headaches, mostly to do with third-party extensions clashing with each other.”
15Customer support
“The Jumpseller team is also very helpful… They’ll walk you through the process of making website [changes], so you can really understand.” | “Technical support is great, always helpful and fast.” | “The best thing is its excellent service, very fast and efficient.” | “Support has worked well so far. When we’ve submitted a query, we’ve gotten quick feedback.” | “Fast and good email support.” | “The customer service is very responsive and helpful.” | “The email response time is super-fast. If I have one question or doubt regarding anything, from design to DNS configuration, they’ll reply in less than 15 minutes!”
Using it for Chilean and international stores
“Our store is based in Chile, and another feature we appreciated is that it had full integration with local payment systems.” | “Has local credit-card options (in our country).” | “Recently, they integrated the price list of one of the shipping companies most used in our country.” | “The good thing is the translation tool.” | “I can tell you that we have selected Jumpseller because we are selling in Chile, and the store was very well integrated with the most popular payment methods, couriers, etc.”
17Ease of use
“It is easy to set up.” | “Easy to maintain.” | “Fairly user-friendly.” | “They really made everything so simple to make extremely intuitive changes quickly.” | “It’s easy to work with.” | “I would recommend it for a new user because of the ease of use in building a store.” | “Easy to use and have had no issues.”
Limitations
“There are design limitations, though.” | “It is lacking in several business customization respects.” | “I wish there was a little more customization allowed.” | “There are some design limitations unless you know HTML.” | “Product is good but has many limitations.” | “I like it, but it does have limitations.” | “It has some limitations, but I have been able to work around them.” | “It does have its limitations on customizing, though.”
Credit-card processor options
“It would be better if it allowed shoppers to use a credit card to place an order, even if we don’t use their approved credit-card processor.” | “We were happy with them for years, and then out of the blue, the payment processors affiliated with GoDaddy dropped us.” | “We will be switching all of our stores from GoDaddy in the near future because it does not allow you to use the merchant service of your choice. You are forced to use Stripe.”
19Support
“Tech support has always been responsive and friendly.” | “Good customer support.” | “I have been able to live chat or call with questions without issue.” | “The support is excellent.” | “Very quick responses to any of our requests.” | “Their support is very good.” | “Their customer service is absolutely the very best.” | “You can always call them 24/7 if you need any kind of support, and it doesn’t cost any extra money.” | “Their tech support is awesome.” | “Tech support has always been responsive and friendly.” | “CoreCommerce’s service is good. It has a mom and pop feel to it.”
Price
“Price for the features and benefits given is exceptional, and no one we’ve spoken with can come close to the value.” | “It is a very cost-effective solution.” | “It is also very affordable.” | “I have yet to find another platform that offers the same value as CoreCommerce (at least for our particular business).” | “Prices are good.”
Feels outdated
“Technologies are old, and they are very slow to update it.” | “It feels like the year 2003.” | “Outdated and uninspiring admin panel.” | “They’ve been a bit behind the times with integrations (still no Bitcoin, for example).” | “They are using an antiquated system, which doesn’t bode well for tie-in structures for the future.”
Difficult to use
“I do find the GUI to be somewhat frustrating and unintuitive.” | “It is annoying when you [have] to update each thing in multiple areas.” | “It is not intuitive or user-friendly.” | “The product was flaky. Flexible but badly designed in lots of areas.” | “Control panel sucks.”
21Customer support
“I emailed the president [of BigCommerce] at 1:00 am requesting help… Within 10 minutes, [he] was on it with compassion and ready to help. They have bent over backward for me.” | “They provide excellent customer support.” | “If nothing else, they seem to have great customer service.” | “More than anything, we care about customer service, and BigCommerce provides excellent customer service.” | “Technical support has been great.” | “Great support.” | “Their tech support is 24/7 and is very responsive to our questions.” | “Customer service… is very helpful.”
Price
“Their pricing structure is punitive for successful businesses… This is surely a recurring theme if you’ve reached out to many B2C website users who have grown their site.” | “A bit pricey when your sales hit over $300,000 a year.” | “[Recently,] my monthly payments increased from $25 to $250 due to my business exceeding the annual sales of their intermediate plan.” | “Because of our sales volume, BigCommerce frequently increases our monthly fees based on increasing sales. This has become very expensive.” | “A bit pricey.” | “We feel it is overpriced these days.” | “Their pricing structure makes no sense, but I’ve been with them for seven years.” | “I would recommend BigCommerce. Pricing is a bit high, though.” | “I personally think the pricing is a little steep.”
23Not for non-developers
“Not as friendly for a non-developer or an individual who just wants to set up shop on their own and doesn’t have a technical background.” | “Ubercart works well as long as you have an experienced programmer.” | “Please note that it would require a developer who knows Drupal, because many aspects needed customization.” | “[I would recommend it ] if you’re comfortable with Drupal.”
Difficult to use
“Ubercart is OK, but it is hard to customize.” | “The learning curve is quite steep.” | “It can be a bit tricky to get your store looking just the way you want.” | “Ubercart isn’t the easiest to set up or work with.” | “The only disadvantage of Ubercart is the complex configuration of the store system.” | “It’s not as plug-and-play as Shopify.”
25Ease of use
“The e-commerce site is beyond simple to use.” | “I would recommend it on one level: It’s easy to use. I can do all the building and updating myself, and so that’s good.” | “Easy to use.” | “Easy to build and maintain.” | “It is user-friendly, easy to set up and modify.” | “It’s super-easy to use, and it seems like everyone who’s ordered from me has also done so with ease.” | “If you want a simple storefront, it’s pretty straightforward, easy and cheap.” | “It is easy to set up.” | “It’s easy to use and user-friendly.” | “It was pretty intuitive to set up.”
Limitations
“It is basic.” | “There are some limitations with shipping and accounting (sending to QuickBooks, etc.).” | “A little limited in some options.” | “I have not been able to make it work in the way I need.” | “I cannot update the inventory amount.” | “We had so many different options, which the configuration of the store and products did not allow us to do.” | “We also wanted to be able to get customers reviews and could not do it.” | “My main complaint is the lack of customization options — for example, not being able to display a price per pound.” | “If you want a variety of options and a wide range of modifications, it is not ideal.”
27Highly customizable
“We have complete control over our Magento store and have customized it extensively to meet our needs. That’s what I like most about it.” | “The amount of customizations and extensions available are endless.” | “It has an unparalleled level of customization and freedom.” | “It has a lot of great customization features.” | “It’s pretty powerful.”
Difficult to use
“Probably the steepest learning curve.” | “It’s very expensive to get changes made.” | “Magento is overkill for what I need to do on my site.” | “User interface is not as easy as it could be.” | “It can be a real pain sometimes.” | “Complicated to set up.” | “It’s got a steep learning curve.” | “Magento has a huge learning curve.” | “It breaks for no reasons, and it breaks if you add anything to the site.” | “Always something going wrong for no apparent reason.”
Often requires professional help
“You will need a good PHP programmer if you intend to add anything to it beyond the default installation.” | “If one wants to really change Magento, one needs an expert.” | “Needs a good specialist to partner with to get the best out of it.” | “I would recommend it as long as you have a true Magento-certified developer to hold your hand the entire way and to create your site and work with you.” | “Magento is good if you’re a web developer and have coding skills.”
29Ease of use
“It is very easy to use.” | “It was easy to use without web design experience.” | “It is basic and easy to use.” | “I have enjoyed the ease of Weebly and what you can accomplish with the tools.” | “It is extremely easy for me to use.” | “I’d recommend it because it is so easy to set up and track inventory.” | “This is one of the easiest [e-commerce platforms] I have used.” | “I do like the online store with Weebly because of the ease of use.” | “Weebly is really easy to use.”
Third-party hosts
“[Weebly] is offered through MacHighway, which I use for my hosting, so there were some glitches in the beginning that probably wouldn’t have been there if I’d gone straight through Weebly.” | “Just make sure you buy the Weebly subscription directly through weebly.com and not through a reseller, because I lost a whole website that way.” | “I would recommend it but only through the Weebly host.” | “The B.S. part is that since day one, iPower (a third-party Weebly host) claimed I was getting an ultra-premium package but was only paying for basic. I would go to edit a product and nothing worked. I’d call customer support and they’d tell me I need to upgrade. This has happened to me twice in three years with them. I’m hoping they get stuck with a class-action suit for fraud.” | “iPage is my host for Weebly. Because of this, I don’t have access to all of the features Weebly offers.” | “… Full access to all of the Weebly features would sort that at once, but iPage (maybe I should change) wants to lock me in for three years and pay the full amount up front!”
Limited features
“If you want a more customizable tool, then this might not work for you.” | “Weebly is missing some of the critcal things that we want from an online store.” | “I am hoping that they have, or will come up with, an automatic shipping calculation.” | “The only hiccup is when I need to change my prices. I have a lot of inventory, and I have found that the easiest way (relatively speaking) to do this is to change each one individually.” | “You can’t do everything design-wise on it.” | “It was perfect for me at first, but I have grown out of it very quickly [because of limited features].” | “The Weebly platform is not scalable. There is no element to customize your cart.” | “The shipping is a problem because it can’t be adjusted for lighter, heavier or multiple items.”
31Bandwidth overage charges
“If you read the forums, one problem that continually arises, and one that I have, is bandwidth. It seems that I’m always going over my bandwidth, even though I have relatively few products and dump files regularly.” | “3dcart charges for bandwidth, so serving lots of digital products from your server might not be a great idea depending on your budget.” | “They charge you for data, and it adds up.” | “It tends to use a lot of bandwidth. My store doesn’t have a huge amount of traffic (yet!), but I still go over my plan just about every month.”
Customer support
“Their comments are snarky, and their help is judgemental in that they always place blame on the customer, and it can take up to a week for them to solve a problem.” | “”Customer support has a laissez-faire attitude.” | “I have to really keep on them when I open a ticket, or I may not get a response for days.” | “I would say the biggest con has been customer service.” | “I would characterize them as almost disrespectful.” | “Their lack of support [was surprising].” | “The tech support also cannot help with even the most basic HTML questions.” | “Technical support online isn’t the best.” | “The help line is not very helpful. If there is a problem, such as the system stops taking orders or accepting credit cards, they assume it’s a problem on your end.” | “Their live support sucks.”
Difficult to use
“I feel the product is terribly cumbersome.” | “The admin interface makes it very difficult to find what settings I’m looking for.” | “It is awkward and not very user-friendly.” | “My website is with 3dcart, but it is overwhelming.” | “It is a little quirky in the back end.” | “I personally find it difficult to make even simple changes to.” | “Some of it is not very intuitive, so you have to keep clicking around until you remember where everything is.”
33Modules
“It has quite a lot of modules.” | “It has loads of modules” | “Lots of additional modules and functionalities to add.” | “A lot of modules.” | “They have a lot of free and already installed modules.” | “There are a lot of free modules.” | “Large offer of modules.”
Difficult to use
“You need to be quite a good geek to understand everything.” | “We’ve encountered and still are encountering lots of problems with PrestaShop.” | “PrestaShop isn’t as user-friendly as others are nowadays.” | “The admin panel is not user-friendly.” | “I don’t recommend it for a beginner or if you don’t have much technical skill.”
Buggy
“I hate it… It’s buggy and impossible to upgrade easily to newer versions.” | “It’s kind of an unstable, slow system for me, but I think in the near future it will be more stable and fast.” | “We have lots of problems with PrestaShop.” | “No, I would not recommend it. Buggy as hell.” | “No, I would not recommend it. Too heavy and too slow.” | “The back-end pages sometimes take an age to load — even for simple stuff.”
35Good for beginners
“Quick and easy. I think its simplicity best suits the light or new user.” | “Great for people with no knowledge [of how to build a store].” | “For someone with zero experience building a website, I found their product to be so easy to navigate.” | “I highly recommend it for beginners.”
Pricing
“Way too expensive.” | “There are cheaper options out there that do the same thing.” | “I liked Goodsie when I started with them five or six years ago, but their prices keep going up.” | “Prices were hiked above what they should be, so I am about to change.” | “The price went from $15 to $30 per month not too long ago.”
37Customer support is prompt
“Whenever we’ve needed support, their help systems are very responsive.” | “Spark Pay’s technical support is excellent.” | “Very responsive for help.” | “They have been responsive to any needs I’ve had.” | “I find their customer service to be quite responsive.” | “Tech support is very responsive via phone or email.” | “They have been very responsive to helping out with general website questions and problems.”
Bandwidth overage charges
“The main thing I don’t like are the extra bandwidth charges.” | “Nailed with huge bandwidth charges.” | “They are little hidden fees for going over your bandwidth account file storage and product count if you don’t keep an eye on them.”
Difficult to use
“Spark Pay is not simple!” | “They have a ton of features built in — most of them are half-baked and don’t function 100%, which has led to frustration.” | “[Needs to] reduce the bloat in their software.” | “Unless you have a designer and/or developer on staff, or at the very least a very computer-savvy non-techie, it’s virtually impossible to understand Spark Pay.” | “Their web editor is clumsy.” | “Their platform is buggy.” | “It is crazy complicated to make even some of the most mundane changes.” | “Their system bogs down so much that only the most minor of changes are doable.” | “Clunky UI, way too much complexity. Just a nightmare to deal with.”
While prompt, customer support can be disappointing
“There service desk really isn’t one. They have no formal (or competent) escalation process.” | “They are not nearly as responsive to fixing significant issues as they should be.” | “I feel like the platform has a lot of tools to offer, but few resources to teach you how to use them.” | “Technical support is rather lacking. When you do finally get someone to answer the tickets, they do a very minimal amount of work and effort to correct the problem.”
39Customer support
“Their technical support department people are top-notch… I’m extremely impressed with them.” | “The [support] team at Volusion is knowledgeable, and that is highly important.” | “Their customer support is excellent.” | “Their support is superb.” | “Support is second to none.” | “There technical support team is also very good in helping to fix any issues that we might have had.”
Bandwidth limitations
“The one thing I can’t stand is the amount of bandwidth they provide you with. [It] will easily be gone in a week if you have a lot of visitors.” | “They don’t have adequate bandwidth plans, and their billing for bandwidth overages is highly irritating.” | “Site traffic is pricey.” | “I originally used very large images for my products and received some rather stiff hosting fines for going over the stupidly low bandwidth level.” | “The way they charge for bandwidth caused us to have obscene overage charged for months.”
Expensive
“It is particularly expensive, and the costs weren’t clear [when we started].” | “Once the site is built, they nickel and dime you for every little thing imaginable.” | “I also used the Volusion SEO team and that was a joke. $1600 a month!” | “Not the least expensive around.” | “I would caution new users to be aware of hidden costs. Email addresses are extra. An SSL certificate is extra. A service to check the reliability of each credit card is extra. SEO and design services are phenomenally expensive.” | “Going by the prices they charge for SEO packages, they’re aiming at companies far larger than mine.” | “If you want anything besides barebone offerings, everything else is available… for a price.” | “I just wish it was a little cheaper.” | “Volusion keeps [the initial setup and customization] complicated, hoping that you will pay them to do it for you.”
Difficult to use
“The back end is not user-friendly.” | “The UX is confusing and bloated, but I’m used to it.” | “There is a learning curve, so it takes a while to get going. And if you want customization, be prepared to learn it yourself or pay some hefty fees.” | “It’s not straightforward and is prone to errors.” | “If you change a font size within the text, you then lose all other formatting — nothing major, but annoying and time-consuming.” | “It’s quite clunky to manage content and design.” | “There are random glitches throughout the site that have probably cost me thousands in abandoned carts.” | “One thing that is hard for me is manipulating website elements. GoDaddy was easier for me.”
41It’s worth noting that this is not a list of all e-commerce software currently available in the world. Instead, I’ve only included software for which I was able to talk to a minimum of 30 users (and I was not able to find 30 users for several companies).
But this is a fairly comprehensive list of the most popular e-commerce platforms. Furthermore, these are the thoughts of real, verified users. I hope it’s helpful in your search for the right e-commerce software!
This article is based off of the e-commerce software guide originally published here43.
(vf, al, il)