Lottie
Lottie is an iOS, Android, and React Native library that renders After Effects animations in real time, allowing apps to use animations as easily as they use static images.
Lottie is an iOS, Android, and React Native library that renders After Effects animations in real time, allowing apps to use animations as easily as they use static images.
With great power comes great responsibility. This week I found some resources that got me thinking: Service Workers that download 16MB of data on the user’s first visit? A Bluetooth API in the browser? Private browser windows that aren’t so private at all?
We have a lot of methods and strategies to fix these kinds of things. We can give the browser smarter hints, put security headers and HTTPS in place, serve web fonts locally, and build safer network protocols. The responsibility is in our hands.
<keygen>
will be removed, for example, as well as the prefix in some webkit
-prefixed APIs.position: sticky
(the only browser still lacking support for it now is MS Edge).fetch()
, Custom Elements, CSS Grid Layout, Reduced Motion Media Query, and ES6 native modules are notable ones.<link rel='preload'>
(and prefetch) automatically.And with that, I’ll close for this week. If you like what I write each week, please support me with a donation21 or share this resource with other people. You can learn more about the costs of the project here22. It’s available via email, RSS and online.
— Anselm
If it’s still snowy where you live, then you’re probably tired of the cold weather by now. Winter may be in full swing but that shouldn’t stop us from hunting for inspiration. While the gray days always seem to find a way to make us more and more anxious for springtime to finally arrive, it’s also a time we can use to reflect on our work and perhaps better decide what it is that we hope to improve or change in the next months.
Believe it or not, some of these photographs and illustrations are the starting point of a design that I create. They are the spark that sets the process of creation in motion. It doesn’t take much; it can be any part of an element that catches my eye, be it a particular color, style, texture, or anything really. You’ll find a bit of everything in today’s selection: Architecture, colors, some of the best photographs from 2016, and more. I hope you’ll like my playground! 😉
I’m admiring the textures and colors here. Wonderful to get some new ideas for backgrounds.
I really like the style of Bodil Jane. This piece of beautiful artwork looks almost like a collage of separate items that is glued on a canvas.
Some good advice that I can totally get behind. Cleverly translated to something else you do everyday.
The view angle is so well done in this illustration, as well as the shadow and light effects. A few other gems in here, such as the transparency of the bag on the desk and the wooden floor under the desk.
Beautiful book cover illustration. How deep she is in her thoughts is just so inspiring.
A wonderful advertisement for planet Earth. Look at that fire in the sky! Purdy.
One of my own pictures shot during a morning bicycle ride. The best kind! Those colors are just wow!
Creating hair is among the most difficult things to achieve. It takes a long time to get it right. That’s why I always study the ones who master it. The hair is simply gorgeous in this illustration, especially those braids.
Some fantastic photographs in this Strava collection of 2016. Hard to pick just one, but after much deliberation I chose this one. Isn’t it marvelous?
The first thing I noticed is the wonderful color palette. I’m also admiring all the different buildings created with very little elements. Imagine how it would look like if it was brought to life. Well, look here19!
Perfect scenery for some daydreaming. The texture used in this illustration is awe-inspiring.
Not your typical color palette. They really work quite well together. Lovely shapes, too!
Using negative space in illustration is one of my favorite things. I’ve personally never done it, but would love to one day. This is one of those nice examples to look up to.
I love the style in which the illustrator doesn’t draw perfect characters. It’s an illustration style that embraces the awkward. Hard to pull off right.
First thing I checked out was the pattern on the guy’s shirt. I also love the use of sharp angles for the arms, legs and other elements in this scene.
Just like the illustration above, this one also features the sharp angles, but also some interesting textures. Look at how the shadows and highlights are applied. Truly amazing!
The choice of colors and shapes are well considered here. Simple, yet effective.
Such an incredible spot! Beautifully captured with some gorgeous light.
Speaking of the use of sharp angles and shapes, Tokyo-based illustrator Jun Takahashi uses this technique to create geometric sports characters. All this with a muted, contemporary palette of colors. His series is called Square Modern.38
Some fine details in this cover such as the dotted stripes on the pants of the male character. Another is the tree bast structure.
Very appropriate since we are still in the middle of winter. This snow landscape is just beautifully executed. Works so well with this pinkish mood/sky.
Splendid shot! Stunning light, colors and lovely composition.
Beautiful smooth water and great sunlight colors.
What makes this interesting is the way this illustration is compiled: the mix of lines and fills, in combination with a limited color palette. Clever.
So delicate and beautiful! Colors, subtle use of gradients, everything is inspiring.
I like the atmosphere in this fall like scenery. Many great details such as the way the collar and sleeve patterns are created. It creates a lovely accent.
This is gorgeous! Loving the colors for this first set of Data Visualization Guidelines from IBM. Great composition and geometry.
If time travel was possible it would look like this. Jurassic Age is part of the Time Travel Destinations Posters. There are a few more, and some are animated too. Go have a look57.
Really diggin’ the stylized perspective. It creates a nice composition.
Great character in this clever logo illustration. That type is great – it really fits the tone of the logo.
So many details in this colorful illustration.
Admiring the simplicity in this illustration.
If you love minimalistic architecture and colors like me, you’ll appreciate this work by Paolo Pettigiani. The new series is called “SHAPEGUARD”.
A second one from the new series called “SHAPEGUARD” by Paolo Pettigiani.
Wonderful duotones at work, especially to create the feeling of the movement of the water. Those swimsuits are not too shabby as well.
For the sci-fi fans among us. A hi-tech village on a transparent hill, enjoying a dramatic red sunset of a class M red dwarf sun. Those gradients and the glowy sun is so perfect!
Talking about being in the right place at the right time. Sunset curving up a wave!
One more for the sci-fi fans. A special color palette and a great illustration style.
Shepard Fairey, whose iconic posters supporting Barack Obama’s 2008 election and won him Design of the Year, has a new offering. The American graphic designer has applied the same posterized style and palette of red, beige and blue of the Hope imagery to three new designs, created for a nonprofit organization called ‘the Amplifier Foundation’.
Loving this muted color palette and the organic style.
If you love your classics you’ll recognize the Lindy Hop in this wonderful illustration.
Creative Director and photographer Dylan Schwartz‘s point-of-view is high above the cities he photographs, capturing the bridges, sports complexes, and tips of high rises from the cockpit of a helicopter.
I’ve featured an image of San Francisco’s fog here before. The waviness is almost surreal.
When Charles Manson and The Beach Boys‘ Dennis Wilson meet. So beautifully stylized!
Such a great concept to have a panoramic scenery on the sweaters.
How does one look like after a rough day at work? I think this illustration pretty much nails it.
Great view on the hustle of Hong Kong. The flow of the water is greatly executed. Lovely color palette, perfectly executed.
Riccardo is a regular guest here. Love how he works with flat colors and sharp-angled shapes.
One more of Riccardo’s recent work. Brilliant as always! I also love the retro touch in all of them.
The Swedish photographer Jeanette Hägglund seems to have found a nice playground in the city of La Manzanera, near Alicante. She plays with the architecture, colors, and light and shadows. Be sure to see the rest of the series.
(yk, il)
In a recent sales meeting for a prospective healthcare client, our team at Mad*Pow found ourselves answering an all-too-familiar question. We had covered the fundamental approach of user-centered design, agreed on leading with research and strategy, and everything was going smoothly. Just as we were wrapping up, the head of their team suddenly asked, “Oh, you guys design mobile-first, right?”
Well, that’s a difficult question to answer.
While the concept of mobile-first began as a philosophy to help prioritize content and ensure positive, device-agnostic experiences, budgetary and scheduling constraints often result in mobile-first meaning mobile-only.
But according to the analytics data of our healthcare clients, the majority of their users are still on desktop. We want to provide a positive experience for those users and for users on mobile and tablet apps and for those using mobile browsers — and even for users having an in-person experience! It is not accurate to assume that mobile is the primary experience.
We’ve come to the conclusion that mobile-first is not specific enough to user needs. Truly user-centered design needs to start with the journeys our users are taking and the flows they follow to complete their objectives. In other words, journey-driven design. Journey-driven design naturally emerges from a user-centered approach that factors in the who, the when and the how to reveal the truly complex set of user needs. Good design doesn’t force users to pick up the device that we designers want them to pick up; good design gives users the best of what a company has to offer on the device that the user wants to use at that point in their journey.
Early in the world of mobile, we (designers in general) essentially designed for desktops… desktops with small screens. UX designers were used to thinking about things like how their users would approach a website and what visual, linguistic and contextual clues they would need to complete their tasks, but we didn’t think about the screen’s size changing. In 1999, we merely worked with 800 pixels. Then, we expanded to 1024, then 1200. Designers rejoiced!
As mobile design improved, battery life lengthened and Wi-Fi became ubiquitous, we learned that users were likely to approach a website from their mobile screen. But all of the design considerations for desktop didn’t translate well.
The idea of mobile-first was a triumph for the user. In 2009, Luke Wroblewski introduced6 this best practice. Karen McGrane added to the conversation7 with her Content Strategy for Mobile in 2012. They found that designing with the constraints of small screens helps us to prioritize content, which leads to a better experience for the end user. In addition, the capabilities of mobile devices left more opportunities for engaging experiences.
Still, it focused on only one great experience. One variation focused on the concept of graceful degradation, which suggested that we design a perfect experience (typically for desktop), and then account for older browsers and less common devices by ensuring functionality, even if the design suffered. Similarly, we tried progressive enhancement, which suggested starting small (mobile), and then enhancing the design as the device or browser gets bigger. Neither accounted for a great design across the realm.
And, more importantly, no one intended mobile-first to mean mobile-only.
Now it’s 2017, and we assume that every project needs to be mobile-friendly; so, when budgets decrease, mobile-first does become mobile-only. After all, 34% of people8 use the Internet predominantly from their mobile phones, and as of April 2015, Google penalizes websites that aren’t usable on mobile. But the choice isn’t as simple as mobile or desktop. Many users switch devices mid-task9, making it even more vital that we focus our content and create consistency across the experiences. In healthcare, 50% of smartphone users download health apps10 — which also means that 50% do not yet.
In a recent report on mobile marketing statistics, Smart Insights’ founder Dave Chafey analyzed the reports11 and concluded:
The reality is that while smartphone use is overwhelmingly popular for some activities such as social media, messaging and catching up with news and gossip, the majority of consumers in western markets also have desktop (and tablet) devices which they tend to use for more detailed review and purchasing.
We have to ask: Did a patient get their diagnosis via a phone call at home and then turn to their desktop, or were they at the doctor’s office searching on their mobile phone? Did that shoe shopper peek at their phone for a cheaper online deal, or did they go home and make the purchase on their tablet?
The divide between online and offline has dissolved as the Internet of Things15 expands, and our experiences are likely to cross between phone, laptop, tablet, TV, watch, refrigerator, car and even toilet16! Pixels and dimensions mean far less than responsive design, adaptive content and journey mapping. There is no obvious starting point for a journey, and no clear template to follow. In order to prioritize content as well as to design for screens beyond mobile, we need to focus on the journey as a holistic piece.
What makes journey-first design more effective than mobile-first is that we are looking at the process holistically. This means that even small budgets have the time and money to take into account design thinking for more screen sizes. In addition, journey-first provides context, a necessary element of today’s design, which was not a focus when mobile-first design began.
The first step in journey-driven design is to map the journey itself, with a focus on how someone accomplishes their goals, and the best person to ask is the end user. Most user-centered design projects already begin with a research phase. It’s an opportunity to hear from users or prospective users and to learn what their expectations are, where they have pain points, and what device or devices they will use as they navigate through the experience.
Based on the research, we create personas17, and then map out each persona’s ideal journey, including making note of every touchpoint along the way to the final objective. These are the times when the business and the persona communicate, whether via email, website, social media, store visit, phone call, mailing or other method. Those touchpoints will be what we can actually design, in order to shape the complete experience. In other words, we can’t necessarily control what a user does when they’re out for a walk, with their phone on silent, but we can control what they see when they receive an email from us, and that will affect the portions of their experience when we’re not connected.
For one of our healthcare clients, this became particularly clear when we considered how end users interact with insurance companies. Though the patient’s goal is to get their lab results, on the way to the doctor’s office, the patient might wonder what their copayment is. If they check the mobile website to log in, that’s a touchpoint for us. If they look for signs in the doctor’s office that list typical copayments, that’s another potential touchpoint. After the appointment, when the claim is filed, we have an opportunity to email the patient an update — another touchpoint.
All of these touchpoints come together in an ecosystem web21, which is one of our primary tools when we engage in journey-driven design. The ecosystem web will help us make connections between the areas of our website, the various other technologies involved in the Internet of Things, and the actions the user is taking. On the ecosystem web, we can also identify which device the persona is using at each digital touchpoint. We might know this information from user research, or we can gather it from analytics, which can get so granular as to tell us which pages on a website are most visited by which devices.
Knowing the touchpoints and designing an ecosystem map is all well and good, but there’s still the matter of designing an appropriate user interface and interactions to help the end user accomplish their goal(s). We still need to build out wireframes or mockups that can ultimately be developed into an app or website.
For some designers, creating an ecosystem map or shifting that into a journey map (whether current or idealized) is as far as journey-driven design goes, before shifting back to mobile-first. The consistency of viewing all designs in mobile-sized mockups is attractive, and stakeholders love the cohesive feel. But our job as designers is to sell them on the need for a variety of screens. We need to consider each set of interactions independently. If there is an ideal user journey that begins on mobile but after four screens switches to desktop, then we need to design four steps for mobile and then change to desktop. If another prospective user journey (maybe the power user’s journey) within the same app begins on a tablet, then we should begin with a tablet design. If still another begins on the watch, then that’s where we begin. We’re still prioritizing content, but we’re also accounting for the visual frame.
After designing each interaction, ask these key questions:
Although the designs won’t have the cohesive feel of a stack of mobile screens, it will have the context of the user’s scenario and their rationale for particular actions. As an added benefit, this type of non-linear thinking will encourage new design approaches. Plus, front-end developers love getting a few screens for each screen size early on; it helps them to structure their development work.
For our healthcare client, we sketched out an ideal journey map on sticky notes, using one sticky note for each touchpoint, and different colors for each likely device. Once we understood the touchpoints along the journey, we were able to think in terms of interactions on screens. We designed every screen for tablet, as well as variations for the six screens most likely to be done on mobile and the four most likely to be done on a larger desktop monitor. We used mobile-first and responsive design best practices, resulting in screens that engage users across all devices.
But best of all, we improve our work as storytellers by linking interactions back to the journey. Even the best, most user-focused designers can forget about the narrative when they begin to focus on individual interactions. The shift from one device to another will help recenter you, reminding you again and again of who the user is, where they are, why they’re doing what they’re doing and what their main focus is — particularly when their focus is not on your UI!
For designers working with content, there’s still another benefit to journey-driven design. Content strategists tend to plan out screen content by identifying what the goal of a screen is, and then providing copy to accomplish that goal and to get across any related messages. In traditional UX design, different content strategists have different methods for determining the goal of any given screen. But with journey-drive design, the designer and strategist will identify the goal or action happening each step of the way early on.
The designer and content strategist should work together to consider the goals of each touchpoint through the journey. Rather than waiting to ultimately write copy in wireframes, the content strategist can help ascertain the goals during the mapping of the ecosystem, which gives the designer a leg up on designing something that will work hand in hand with the content.
In addition, content strategists need to consider what content will be static and what will be adaptive26. When the designer designs different screens for different steps of the journey, they provide insight into how content will be perceived, whether the user will be distracted and what other clues to context the content strategist should consider.
In short, journey-driven design provides us with a big-picture approach that ensures fewer surprises and easier work the farther you go into the project. It keeps the whole team working together, all engaged in the creation of the ecosystem, so that decisions are shared early on, before designs are begun.
At its heart, journey-driven design is still just another flavor of user-centered design. Once the ecosystem is mapped and the journey identified, journey-driven design proceeds like any other UX project. Designs need to be created for other screen sizes and then checked for consistency across devices to account for edge cases. Usability testing, revising and iterating is as much a part of journey-driven design as any other.
Looking to get started with your own journey maps? Try out these steps:
We live in an Internet of Things. There’s no use pretending that mobile, desktop or tablets will be the way of the future. There are still more devices to come our way, more technologies to infuse our homes, workplaces and commutes. Journey-driven design allows design to expand and evolve as technologies evolve. We’re starting to practice this more and more, and we hope to share a case study or two in the coming months.
(cc, vf, al, il)
Chat bots, virtual reality and conversational design are just a few of the hot topics that are about to change the way we craft digital experiences. So, it’s a good time to rethink current practices and prepare for what’s about to come, don’t you think?
To give us all an early head start into the future of designing meaningful experiences, we are happy to live stream the backstage interviews which we’ll hold at the Awwwards Digital Design Conf in London1 today. Kindly organized by our good friends at Adobe.
We’ll sit down with some of the creative minds out there to discuss their workflows, techniques, failures and lessons learned from crafting digital experiences today. We’ll talk about ongoing and upcoming developments and the challenges they bring along for you as a designer. Light-bulb moments are guaranteed.
Sounds good? Well then, prepare yourself a nice cuppa coffee, or better yet, find a comfy spot not too far away from the coffee machine: the 5-hour long live stream will start right here on Thursday, February 2nd at 12 PM GMT.
Designing for the web today isn’t easy. We have to deal with so many unknowns – ranging from screen sizes to network conditions to capabilities of devices. To manage this unpredictability, we tend to rely on predictable patterns – predictable solutions which bring us to predictable results — faster. However, as a result, we tend to create same-looking experiences as well.
The live stream today will explore how we can break out of the box and create unpredictable, delightful experiences based on the new possibilities web technologies offer today. We’ll discuss all those fancy new technologies such as AR/VR, conversational interfaces and new hardware capabilities with established designers and developers from the industry to find out how we can apply them in our work, too.
We’ll talk about how we all can use these technologies in actual projects and how other designers and developers out there make use of them already. More importantly, you’ll get insights into lessons learned from their work – things that worked well, failures and successes, and the rationale behind all of the design decisions in-between.
You’ll find the schedule below, so you can jump in and out whenever you are interested, or just having lunch! There will be 20-25 min interview sessions, with short breaks in-between, in addition to two panels, in the middle and end of the day.
Send your questions! This is going to be quite a day, and we’d love you to be a part of it! We highly encourage you to take part in the livestream and send in your questions in the live chat window on YouTube3. We’ll moderate all questions and we’ll ask our speakers live during the Q&A. Big thanks to Adobe and Awwwwards for making it all possible!
Not enough? Well, to help the learning continue even after the live stream has ended, we did some digging in our own archives, as well to bring you eleven timeless SmashingConf talks that’ll take your UX skills to the next level. After all, a good foundation is the best way to be equipped for the new challenges that the future might bring, right? Happy learning!
After a decade and a half as a user experience professional, Jesse James Garrett has had more than his fair share of scrapes and bruises. In this presentation, he reflects on what he’s learned about what it really takes to deliver great UX work, from working with teams and managing stakeholders to breaking a creative rut and finding innovative solutions to design problems.
UX design is all the rage at the moment, but how usable is it as a process? When the top industry experts can’t even agree to its definition (or it’s existence) how are you supposed to bake it into your practice, let alone sell it to your clients? In fact, should you or your clients even care? In this session Andy Budd will try to demystify some of the rhetoric and dogma floating around about User Experience Design and explain what should and shouldn’t matter to your business, your clients, and your day-to-day work as a web designer.
Often when solving design problems we take into account the demands of stakeholders, or our desire to create beautiful work. These are both valuable for informing the choices we make, but there’s another factor that’s imperative to our design’s success: the needs of real people. Meagan Fisher will share why human need matters in design, why we resist getting close to our users, and easy ways to put users front and center in our process.
When you design a website, an app, software, or a product, you expect a human to interact with your design with their perceptual and sensory systems. If you want to design a product that is easy to use, engaging, and that meets your goals and objectives for user experience, you need to know about the psychology of perception. In this talk, Susan Weinschenk will share her top ten most important research studies on perception – concentrating on vision and hearing.
Joe Leech will take you on a journey to find the holy grail we are all looking for: the “perfect” design. To get you a step closer to finding it, he’ll share a practical strategy that uses psychology to produce the ideal design for those tricky user experience design problems we face everyday.
As experience designers, we spend our days, and often nights, working hard to solve problems for people. Often, the focus of our work is solely put on how a user will interact with any given digital experience. We’re told to work quickly, fail fast, and to not overthink it. What we tend to miss are the many touch points that can help shape the larger user experience and enable customers to have an emotional connection to a product, a service or a company. In this talk, Jon Setzen will explain how embracing a “design for service” thinking can help digital design teams shift from transaction-driven thinking to a relationship-centric approach.
Can good design turn people into better citizens? In this talk Adrian Zumbrunnen will discuss how design can drive behavior, the responsibilities of our craft, and explore a few rules we can use to nudge people in our desired direction.
Our current focus on components, design systems, pattern libraries, and frameworks has helped make design and development easier for us. It’s made it easier for us to make things consistent. But it has also provided fertile ground for design sameness and boring websites. One part of art direction is focusing on the details. But another part, the part that should define the details, the part too often quietly kicked under the bed, so people don’t see it, is the big picture. In this talk, Stephen Hay will talk about stepping back and looking at how all the small parts of your design can add up to a meaningful experience. That also involves looking at how meaningful experience can lead to all the small parts of your design.
It has never been easier to make a website, and our digital toolbox has never been greater. At the same time, we seem more concerned with automating our process and systemising design than with creative thinking and generating ideas. Is web design purely about utility? Is it all about convention? Is it a science? Or, is there room for beauty, expression and art? In this talk, Espen Brunborg will take a tongue-in-cheek look at the state of web design, explore different creative mindsets, and show how adding a pinch of comedy can make a real difference to the bottom line.
Based on his experience at Stack Overflow and Discourse, Jeff Atwood talks about building a habit forming community based on fun, tangible progress, and respect. It’s about how to gently guide your community members down the path toward mutual cooperation. You’ll gain a closer look at how Q&A and Discussion are different and how they can learn from each other and at building habits that lead to positive community behaviors.
What if this thing was magic? The web is touching everyday objects now, and designing for the internet of things means blessing everyday objects, places, even people with extraordinary abilities—requiring designers, too, to break with the ordinary. Designing for this new medium is less a challenge of technology than imagination. Sharing a rich trove of examples, Josh Clark explores the new experiences that are possible when anything can be an interface.
Well then, why not watch a couple more SmashingConf videos? Our Vimeo channel26 has got you covered.
(il, aa)
A great article that explains what secure headers are and how to implement them in Rails, Django, Express.js, Go, Nginx, and Apache.
Time flies by! February is already here and artists and designers from across the globe have once again diligently created a potpourri of unique wallpaper calendars to freshen up your desktop. This monthly wallpapers mission has been going on for eight years1 already and we are very thankful to all the creative minds who challenge their skills and contribute to it each month anew.
This post features their desktop artwork for February 2017. The wallpapers all come in versions with and without a calendar and can be downloaded for free. Now there’s only one question left to answer: Which one will make it to your desktop this month?
Please note that:
Designed by Xenia Latii6 from Germany.
“I was doodling pictures of my cat one day and decided I could turn it into a fun wallpaper – because a cold, winter night in February is the perfect time for staying in and cuddling with your cat, your significant other, or both!” — Designed by Angelia DiAntonio49 from Ohio, USA.
“Amantine-Lucile-Aurore Dupin, best known by her pseudonym George Sand, was a French novelist and memoirist.” — Designed by Tazi Design84 from Australia.
“Valentine’s day is coming and there’s always this saying that ‘opposites attract’ and while it is true, I think that even if you are the same, if love is strong, you can always make it work. No matter if the odds are against you.” — Designed by Maria Keller109 from Mexico.
“Valentine’s Day is probably celebrated almost everywhere in the world today. These celebrations and traditions on how a particular form of society celebrates love vary based on the place or the country. Mostly Valentine’s day is recognized as the day that celebrates love. But in many cultures, it can also be recognized as many other things such as spring, happiness and so on. There are so many folk traditions based on Valentine’s day and the belief it holds among various cultures also differs with respect to the history of the day.” — Designed by Dipanjan Karmakar162 from India.
“This minimalistic love logo is designed from geometric shapes, where the heart represents the letter ‘O’ and love. The anchor represents the letter ‘V’ very delicately and stylish and it symbolizes your wanderlust. The anchor is a symbol of adventure and travels.” — Designed by Antun Hirsman181 from Croatia.
“I got my inspiration from a children’s song here in Belgium. It’s a song by K3, they were and still are my favorite Belgian girl band from my childhood. ‘Love boat baby’ is a recent song from the new girls of K3, and this is my favorite one. I thought it would be a really nice wallpaper with an actual love boat on the sea to represent the month of love.” — Designed by Melissa Bogemans202 from Belgium.
“Hearts, kisses, chocolates, cards and flowers… Love is everywhere — it’s Valentine‘s Day!” — Designed by Hemangi Rane245 from Gainesville, Forida.
“February is a month of love and friendship! Valentine’s Day – a time for special presents, going out drinking with mates and most importantly, sharing delicious meals with loved ones. Food Crush!” — Designed by foodpanda Hong Kong254 from Hong Kong.
“The best and most beautiful things in the world can’t be seen or even touched — they must be felt with the heart.” — Designed by Colorgraphicz291 from India.
“Love is what makes you live and gives you the hope to live and look for a better future.” — Designed by Hatim M. M. Yousif Al-Sharif300 from the United Arab Emirates.
“Love is like a cloud… love is like a dream… love is one word and everything in between… love is a fairytale come true… I found love when I found you.” — Designed by Suman Sil345 from India.
“Flowers, chocolates, teddy, gifts are so mainstream. This year touch her heart and soul and rather than saying those obvious ‘Three Magical Words’, say ‘Be my queen’.” — Designed by Damn Perfect376 from Jaipur, India.
“Love is something eternal – the aspect may change, but not the essence. A Valentine’s Day wallpaper with traditional motifs especially for you!” — Designed by Roxi Nastase427 from Romania.
“February is National Bird Feeding Month. I used to feed the birds in our backyard as a kid so this is what inspired the imagery.” — Designed by Karen Frolo470 from the United States.
“February means Valentine’s Day – and love is in the air!” — Designed by James Mitchell501 from the United Kingdom.
“Happy Valentine’s day! We want everyone to find somebody to love.” — Designed by Anto Fernández | Destaca Imagen522 from Spain.
“February is a short month with a whirlwind of romance smack dab in the middle. Whether there is a special someone in your life or not, a gorgeous view at sunset stirs the heart and refreshes the soul. This particular sunset did that to me the moment I walked up, and I knew it needed to be captured. Taken at Lyall Bay Rocks in Wellington, New Zealand.” — Designed by Jenni Adamitis543 from Houston, Texas.
“The world needs some love, now more then ever. In this wallpaper, you can see there is a girl giving some of her love to a tree. The tree of love which spreads the love it gets to the world.” — Designed by Mira Van der Jeugt586 from Belgium.
“Danube is Europe’s second largest river, connecting 10 different countries. In these cold days, when ice paralyzes rivers and closes waterways, a small but brave icebreaker called Greben (Serbian word for ‘reef’) seems stronger than winter. It cuts through the ice on Đerdap gorge (Iron Gate) – the longest and biggest gorge in Europe – thus helping the production of electricity in the power plant. This is our way to give thanks to Greben!” — Designed by PopArt Studio601 from Serbia.
“February is the month of love par excellence, but also a different month. Perhaps because it is shorter than the rest or because it is the one that makes way for spring, but we consider it a special month. It is a perfect month to make plans because we have already finished the post-Christmas crunch and we notice that spring and summer are coming closer. That is why I like to imagine that maybe in another place someone is also making plans to travel to unknown lands.” — Designed by Verónica Valenzuela646 from Spain.
Designed by Nathalie Ouederni667 from France.
“In February, nature shows its creativity. Our artwork occurs when it is being drawn.” — Designed by Ana Masnikosa684 from Belgrade, Serbia.
“Charles Dickens is most famous for writing Oliver Twist and A Christmas Carol. The 7th of February is Charles Dickens birthday, so to honour his birthday I created this wallpaper with a quote from my favourite book, Oliver Twist!” — Designed by Safia Begum725 from the United Kingdom.
Designed by Nathalie Croze744 from France.
Designed by Gregor Haslinger755 from Germany.
“This design is dedicated to the rooster, the one who starts with his cock-a-doodle-do at the crack of dawn to wake everyone up from their deep slumber. He’s not just nature’s alarm clock but the protector of the coop as well, watchful as he walks through the farm with his pointed saddle feathers and red comb.” — Designed by Acodez IT Solutions772 from India.
Designed by Elise Vanoorbeek – Doud815 from Belgium.
“See the beautiful colors, precision, and the nature of Japan in one picture.” — Designed by Fatih Yilmaz846 from the Netherlands.
“2017 is the Chinese Year of the Rooster, the golden eggs on behalf of auspicious meaning.” — Designed by Sunny Hong869 from Taiwan.
Please note that we respect and carefully consider the ideas and motivation behind each and every artist’s work. This is why we give all artists the full freedom to explore their creativity and express emotions and experience throughout their works. This is also why the themes of the wallpapers weren’t anyhow influenced by us, but rather designed from scratch by the artists themselves.
A big thank you to all designers for their participation. Join in next month888!
What’s your favorite theme or wallpaper for this month? Please let us know in the comment section below.
(cm)
A free drone icon set in solid and line style with 50 easy-to-adjust icons in AI, Sketch, SVG, PNG and Iconjar format.
When working in a team, we focus so much on the work, that we often forget that we all have something in common. Something that is so obvious that we underestimate it: we all are human beings. And well, if we want to grow as a team and get better at what we do, we should embrace this fact more. In fact, I just came back from a week-long team retreat where we had team activities, team games, and sessions and discussions about how we can achieve just that.
We figured out how much we value diversity, we realized how different the English language and its words are perceived by people from different countries, and we’ve seen short talks on various topics like work-life-balance but also on technical stuff like Docker or intercepting any computer’s traffic with a Raspberry Zero. So if you have the chance to work in a team, use the opportunity and exchange views and share information with your co-workers. Work is part of your life, so why not make it a lovely part?
::placeholder
, broader ES2015 support, WebGL 2, IndexedDB v2, and tabindex
for SVG. Scripts served with an image/*
, video/*
, audio/*
or text/csv
MIME type are now blocked.display
property value flow-root
17 that was added to Chrome and Firefox (both Nightly/Canary only) and why it will finally replace the old clearfix hacks that we’re currently using to fix parent box sizes when floating inner elements.And with that, I’ll close for this week. If you like what I write each week, please support me with a donation21 or share this resource with other people. You can learn more about the costs of the project here22. It’s available via email, RSS and online.
— Anselm
So, you want to give your projects some extra love? With Valentine’s Day coming up soon, we’ve got a fresh new set that consists of a delicious selection — just like a good box of chocolates. Take a peek, and you’ll find icons especially tailored to e-commerce1 projects (such as shopping carts and price tags), but also some more versatile motifs like a love letter, calendar, and even a WiFi sign that sends off some lovely vibes.
This catchy icon set is available in two different styles — each available in AI, SVG, EPS, Sketch, CSH and PNG formats. Thanks to the creative minds behind Roundicons104 who created these 30 sugar-sweet icons dedicated just for you to use for all things valentine. Perfect to spread some love, and hopefully, conquer the hearts of your users.
Please note that the set is released under a Creative Commons Attribution 3.0 Unported8. This means that you may modify the size, color and shape of the icons (more details in the readme.txt file). No attribution is required, however, reselling of bundles or individual pictograms is not cool. If you would like to spread the word in blog posts or anywhere else, please also remember to credit the designers and provide a link to this article.
“We’re proud to share this icon set: it’s part of a 4-year-old project that is being used by thousands of designers around the world, and will keep growing. We hope you like each one, and don’t forget to spread the love!”
A big thank you to Roundicons — we sincerely appreciate your time and efforts. Keep up the brilliant work!
(il, cm)
When the Russian ruble’s exchange rate slumped two years ago, it drove us to think of cutting hardware and hosting costs for the Mail.Ru email service. To look at ways to save money, let’s first take a look at what email consists of.
Indexes and bodies account for only 15% of the storage size, whereas 85% is taken up by files. So, optimization of files (that is, attachments) is worth exploring in more detail. At the time, we didn’t have file deduplication in place, but we estimated that it could shrink the total storage size by 36%, because many users receive the same messages, such as price lists from online stores and newsletters from social networks that contain images2 and so on. In this article, I’ll describe how we implemented a deduplication system under the guidance of PSIAlt3.
We’re dealing with a stream of files. When we receive a message, we must deliver it to the user as soon as possible. We need to be able to quickly recognize duplicates. An easy solution would be to name files based on their contents. We’re using SHA-1 for this purpose. The initial name of the file is stored in the email itself, so we don’t need to worry about it.
Once a new email arrives, we retrieve the files, compute their hashes and add the result to the email. It’s a necessary step to be able to easily locate the files belonging to a particular email in our future storage when this email is being sent.
Now, let’s upload a file to our storage and find out whether another file with the same hash already exists there. This means we’ll need to store all hashes in memory. Let’s call this storage for hashes “FileDB.”
A single file can be attached to different emails, so we’ll need a counter that keeps track of all emails containing this file.
The counter increments with every new file uploaded. About 40% of all files are deleted, so if a user deletes an email containing files uploaded to the cloud, the counter must be decremented. If the counter reaches zero, the file can be deleted.
Here we face the first issue: Information about an email (indexes) is stored in one system, whereas information about a file is stored in another. This could lead to an error. For example, consider the following workflow:
In this case, the email stays in the system, but the counter gets decremented by 1. When the system receives a second request to delete this email, the counter gets decremented again — and we could encounter a situation in which the file is still attached to an email but the counter is already at 0.
Not losing data is critical. We can’t have a situation in which a user opens an email and discovers no attachment there. That being said, storing some redundant files on disks is no big deal. All we need is a mechanism to unambiguously determine whether the counter is correctly set to 0. That’s why we have one more field — magic
.
The algorithm is simple. Along with the hash of a file, we store a randomly generated number in an email. All requests to upload or delete a file take this random number into account: In case of an upload request, this number is added to the current value of the magic number; if it’s a delete request, this random number is subtracted from the current value of the magic number.
Thus, if all emails have incremented and decremented the counter the correct number of times, then the magic number will also equal 0. Otherwise, the file must not be deleted.
Let’s consider an example. We have a file named sha1
. It’s uploaded once, and the email generates a random (magic) number for it, which is equal to 345.
Then a new email arrives with the same file. It generates its own magic number (123) and uploads the file. The new magic number is added to the current value of the magic number (345), and the counter gets incremented by 1. As a result, what we have in FileDB is the magic number with the value of 468 and the counter set to 2.
After the user deletes the second email, the magic number generated for this email is subtracted from the current value of the magic number (468), and the counter gets decremented by 1.
Let’s first look at the normal course of events. The user deletes the first email, and the magic number and the counter both become 0. This means the data is consistent and the file can be deleted.
Now, suppose something goes wrong: The second email sends two delete requests. The counter being equal to 0 means there are no more links to the file, but the magic number, which equals 222, signals a problem: The file can’t be deleted until the data is consistent.
Let’s develop the situation a little more. Suppose the first email also gets deleted. In this case, the magic number (-123) still signals an inconsistency.
As a safety measure, once the counter reaches 0 but the magic number doesn’t (in our case, the magic number is 222 and the counter is 0), the file is assigned a “Do not delete” flag. This way, even if — after a series of deletions and uploads — both the magic number and the counter somehow become 0, we’ll still know that this file is problematic and must not be deleted. The system isn’t allowed to generate a magic peer 0. If you send 0 as the magic number, you’ll receive an error.
Back to FileDB. Every entity has a set of flags. Whether you plan to use them or not, you’re going to need them (if, say, a file needs to be marked as undeletable).
We have all of the file attributes, except for where the file is physically located. This place is identified by a server (IP) and a disk. There should be two such servers and two such disks. We store two copies of each file.
But one disk stores a lot of files (in our case, about 1 million), which means these records will be identified by the same disk pair in FileDB, so storing this information in FileDB would be wasteful. Let’s put it in a separate table, PairDB, linked to FileDB via a disk pair ID.
I guess it goes without saying that, apart from a disk pair ID, we’ll also need a flags
field. I’ll jump ahead a bit and mention that this field signals whether the disks are locked (say, one disk has crashed and the other is being copied, so no new data can be written to either of them).
Also, we need to know how much free space each disk has — hence, the corresponding fields.
To make sure everything works fast, both FileDB and PairDB must be RAM-resident. We were using Tarantool 1.5, but now the latest version should be used. FileDB has five fields (20, 4, 4, 4 and 4 bytes long), which add up to 36 bytes. Also, each record has a 16-byte header, plus a 1-byte length pointer per field, which brings the total record size to 57 bytes.
Tarantool allows you to specify the minimum allocation size, so memory-associated overhead can be kept close to zero. We’ll be allocating the exact amount of memory needed for one record. We have 12 billion files.
(57 * 12 * 10^9) / (1024^3) = 637 GB
But that’s not all, we’ll also need an index on the sha1
field, which adds 12 more bytes to the total record size.
(12 * 12 * 10^9) / (1024^3) = 179 GB
All in all, 800 GB of RAM are needed. And let’s not forget about replication, which doubles the amount of RAM required.
If we buy machines with 256 GB of RAM on board, we’d need eight of them.
We can assess the size of PairDB. The average file size is 1 MB and disk capacity is 1 TB, which allows storage of about 1 million files on a single disk; so, we’d need some 28,000 disks. One PairDB record describes two disks. Therefore, PairDB contains 14,000 records — negligible compared to FileDB.
Now that we’ve got the database structure out of the way, let’s turn to the API for interacting with the system. At first glance, we need the upload
and delete
methods. But don’t forget about deduplication: Chances are good that the file we’re trying to upload is already in storage, and uploading it a second time wouldn’t make sense. So, the following methods are necessary:
inc(sha1, magic)
increments the counter. If a file doesn’t exist, it returns an error. Recall that we also need a magic number that helps to prevent incorrect file deletion.upload(sha1, magic)
should be called if inc
has returned an error, which means this file doesn’t exist and must be uploaded.dec(sha1, magic)
should be called if a user deletes an email. The counter is decremented first.GET /sha1
downloads a file via HTTP.Let’s take a closer look at what happens during uploading. For the daemon that implements this interface, we chose the IProto protocol. Daemons must scale well to any number of machines, so they don’t store states. Suppose we receive a request via a socket:
The command name tells us the header’s length, so we read the header first. Now, we need to know the length of the origin-len
file. It’s necessary to choose a couple of servers to upload it. We just retrieve all of the records (a few thousand) from PairDB and use the standard algorithm to find the needed pair: Take a segment with the length equal to the sum of free spaces on all pairs, randomly pick a point on this segment, and choose whatever pair this point belongs to.
However, choosing a pair this way is risky. Suppose all of our disks are 90% full — and then we add a new empty disk. It’s highly likely that all new files will be uploaded to this disk. To avoid this problem, we should sum not the free space of a disk pair, but the nth root of this free space.
So, we’ve chosen a pair, but our daemon is a streaming one, and if we start uploading a file to storage, there’s no turning back. That being said, before uploading a real file, we’ll upload a small test file first. If the test upload is successful, we’ll read filecontent
from the socket and upload it to storage; otherwise, another pair is chosen. SHA-1 hash can be read on the fly, so it is also checked during uploading.
Let’s now examine a file upload from a loader to a chosen disk pair. On machines that contain the disks, we set up nginx and use the WebDAV protocol. An email arrives. FileDB doesn’t have this file yet, so it needs to be uploaded to a disk pair via a loader.
But nothing prevents another user from receiving the same email — suppose two recipients are specified. Remember that FileDB doesn’t have this file yet, so one more loader will be uploading this very file and might pick the same disk pair for uploading.
Nginx would likely resolve this situation correctly, but we need to control the whole process, so we’ll save the file with a complex name.
The part of the name in red is where each loader puts a random number. Thus, the two PUT
methods won’t overlap and upload two different files. Once nginx responds with 201
(OK), the first loader performs an atomic MOVE
operation, which specifies the final name of the file.
When the second loader is done uploading the file and also performs MOVE
, the file will get overwritten, but it’s no big deal because the file is one and the same. Once the file is on the disks, a new record needs to be added to FileDB. Our version of Tarantool is divided into two spaces. We’ve only been using space0
so far.
However, instead of simply adding a new record to FileDB, we’re using a stored procedure that either increments the file counter or adds a new record to FileDB. Why? In the time that has passed since the loader made sure the file doesn’t exist in FileDB, uploaded it and proceeded to add a new record to FileDB, someone else could have uploaded this file and added the corresponding record. We’ve considered this very case: Two recipients are specified for one email, so two loaders start uploading the file; once the second loader finishes the upload, it also proceeds to add a new record to FileDB.
In this case, the second loader just increments the file counter.
Let’s now look at the dec
method. Our system has two high-priority tasks: to reliably write a file to disk and to quickly give it back to a client from this disk. Physically deleting a file generates a certain workload and slows down these two tasks. That’s why we perform deletions offline. The dec
method itself decrements the counter. If the latter becomes 0, just like the magic number, then it means nobody needs the file anymore, so we move the corresponding record from space0
to space1
in Tarantool.
decrement (sha1, magic){ counter-- current_magic –= magic if (counter == 0 && current_magic == 0){ move(sha1, space1) } }
Each storage has a Valkyrie daemon that monitors data integrity and consistency — and it works with space1
. There’s one daemon instance per disk. The daemon iterates over all of the files on a disk and checks whether space1
contains a record corresponding to a particular file, which would mean that this file should be deleted.
But after the dec
method is called and a file is moved to space1
, Valkyrie might take a while to find this file. This means that, in the time between these two events, the file may be reuploaded and, thus, moved to space0
again.
That’s why Valkyrie also checks whether a file exists in space0
. If this is the case and the pair_id
of the corresponding record points to a pair of disks that this Valkyrie instance is running on, then the record is deleted from space1
.
If no record is found in space0
, then the file is a potential candidate for deletion. However, between a request to space0
and the physical deletion of a file, there’s still a window of time in which a new record corresponding to this file might appear in space0
. To deal with this, we put the file in quarantine.
Instead of deleting the file, we append deleted
and a timestamp to its name. This means we’ll physically delete the file at timestamp
plus some time specified in the config file. If a crash occurs and the file is deleted by mistake, the user will come to reclaim it. We’ll be able to restore it and resolve the issue without losing any data.
Now, recall that there are two disks, with a Valkyrie instance running on each. The two instances are not synced. Hence the question: When should the record be deleted from space1
?
We’ll do two things. First, for the file in question, let’s make one of the Valkyrie instances the master. This is easily done by using the first bit of the file name: If it equals zero, then disk0
is the master; otherwise, disk1
is the master.
Let’s introduce a processing delay. Recall that when a record sits in space0
, it contains the magic
field for checking consistency. When the record is moved to space1
, this field is not used, so we’ll put there a timestamp corresponding to the time that this record appeared in space1
. This way, the master Valkyrie instance will start processing records in space1
at once, whereas the slave will add some delay to the timestamp and will process and delete records from space1
a little later.
This approach has one more advantage. If a file was mistakenly put in quarantine on the master, it’ll be evident from the log once we query the master. Meanwhile, the client that requested the file will fall back to the slave — and the user will receive the needed file.
So, we have considered the situation in which the Valkyrie daemon locates a file named sha1
, and this file (being a potential candidate for deletion) has a corresponding record in space1
. What other variants are possible?
Suppose a file is on a disk, but FileDB doesn’t have any corresponding record. If, in the case considered above, the master Valkyrie instance, for some reason, hasn’t been working for some time, it would mean the slave’s had enough time to put the file in quarantine and to delete the corresponding record from space1
. In this case, we would also put the file in quarantine by using sha1.deleted.timestamp
.
Another situation: A record exists but points to a different disk pair. This could happen during uploading if two recipients are specified for one email. Recall this scheme:
What happens if the second loader uploads the file to a different pair than the first one? It will increment the counter in space0
, but the disk pair where the file was uploaded would contain some junk files. What we need to do is make sure these files can be read and that they match sha1
. If everything’s fine, such files can be deleted at once.
Also, Valkyrie might encounter a file that has been put in quarantine. If the quarantine is over, this file will get deleted.
Now, imagine that Valkyrie encounters a good file. It needs to be read from disk, checked for integrity and compared to sha1
. Then, Valkyrie needs to query the second disk to see if it has this same file. A simple HEAD
method is enough here: The daemon running on the second disk will itself check the file’s integrity. If the file is corrupt on the first disk, it’s immediately copied over from the second disk. If the second disk doesn’t have the file, its copy is uploaded from the first disk.
We’re left with the last situation, which has to do with disk problems. If any problem with a disk is identified in the course of system monitoring, the problematic disk is put in service (read-only) mode, while on the second disk, the UNMOVE
operation is run. This effectively distributes all of the files on the second disk among other disk pairs.
Let’s get back to where we started. Our email storage used to look like this:
We’ve managed to reduce the total size by 18 PB after implementing the new system:
Emails now occupy 32 PB (25% for indexes, 75% for files). Thanks to the 18-PB cut, we didn’t have to buy new hardware for quite some time.
As of now, there are no known examples of SHA-1 collisions. There exist collision examples for its internal compression function (SHA-1 freestart collision), though. Given 12 billion files, the probability of a hash collision is less than 10^-38.
But let’s assume it’s possible. In this case, when a file is requested by its hash value, we check whether it has the correct size and CRC32 checksum that were saved in the indexes of the corresponding email during upload. That is to say, the requested file will be provided if and only if it was uploaded along with this email.
(vf, yk, il, al)
Capture every interaction on your site for pixel-perfect playback, so you can read between the line graphs. For free, forever.?
Color is arguably the second most important aspect of your app, after functionality. The human to computer interaction is heavily based on interacting with graphical UI elements, and color plays a critical role in this interaction. It helps users see and interpret your app’s content, interact with the correct elements, and understand actions. Every app has a color scheme, and it uses the primary colors for its main areas.
When designing a new app, it’s often difficult to decide on a color scheme that works well, as there are an infinite number of possible color combinations out there. In this article, we’ll go over the most important points related to color in apps. We’ll cover traditional color scheme patterns (monochrome, analogous, complementary), custom color combinations that aren’t based strictly on any one pattern, and we’ll also learn how to choose colors and contrasts for your app that support usability. If you’d like to hone your own color usage skills, you can download and test Adobe XD1for free, and get started right away.
When creating a color scheme there are many factors to consider, including brand colors and color associations for your region.
Keeping your color combinations simple will help you improve the user experience6. A simple color scheme isn’t overwhelming to the eye and makes your content easier to understand. Conversely, having too many colors in too many places is an easy way to mess up a design.
A University of Toronto study7 on how people used Adobe Color CC8 revealed that most people preferred simple color combinations that relied on only two to three colors.
So, how do you choose those two or three colors? The color wheel can help.
There are a number of predefined color scheme standards that make creating new schemes easier, especially for beginners.
Monochromatic schemes are the simplest color schemes to create, as each color is taken from the same base color. Monochromatic colors go well together, producing a soothing effect.
The monochromatic scheme is very easy on the eyes, especially with blue or green hues.
As you can see, the scheme looks clean and elegant.
Analogous color schemes are created from related colors that don’t stand out from one another; one color is used as a dominant color while others are used to enrich the scheme.
While this scheme is relatively easy to pull off, the trick is in deciding which vibrancy of color to use, as it will be exaggerated. For example, Clear20, a gesture-driven21 to-do app, uses the analogous colors to visually prioritize current set of tasks.
While Calm26, a meditation app, uses the analogous colors blue and green to help users feel relaxed and peaceful.
Colors aren’t always at odds with each other; complementary colors are opposite colors.
They contrast strongly, and they can be used to attract the viewer’s attention. When using a complementary scheme, it is important to choose a dominant color and use its complementary color for accents. For example, when the human eye sees an object full of different hues of greens, a bit of red is going to stand out very well.
However, you have to use complementary colors carefully to keep your content from being visually jarring.
Creating your own color schemes is not as complicated as many people think. Adding a bright accent color into an otherwise neutral palette is one of the easiest color schemes to create, and it’s also one of the most visually striking.
White canvases and cool gray copy, splashed with accents of blue, make up the Dropbox color scheme.
Adobe Color CC41, previously known as Kuler, makes color selection extremely easy. Every color on the palette can be individually modified or chosen as the base color with a few simple clicks.
Palettes can be saved and added directly to a library. In addition, there are a number of great color schemes created by the community available on the site.
Check it out, so you don’t need to start from scratch.
Typically, a colored object or area on a UI is not displayed in isolation, but is adjacent to or superimposed on another colored object or area. This creates contrast effects. Contrast is how one color stands apart from another. Properly used, it reduces eyestrain and focuses user attention by clearly dividing elements on a screen.
Color contrast is one area where color theory is crucial to the usability of a design. Designers often like to use low contrast techniques because low contrast makes things look beautiful and harmonious. However, beautiful isn’t always the best for readability. When you’re using colors in text, be aware that placing two colors with low-value contrast next to each other can make your copy very difficult to read. This is especially true on mobile screens, where users are often on devices outdoors or in bright places that cause screen glare.
Make sure you have a fair amount of contrast between elements. It’s really not that hard — all you need to do is to check the contrast ratio. Contrast ratios represent how different a color is from another color (commonly written as 1:1 or 21:1). The higher the difference between the two numbers in the ratio, the greater the difference in relative luminance between the colors. The W3C50 recommends51 the following contrast ratios for body text and image text:
This guideline also helps users with low vision, color blindness, or worsening vision see and read the text on your screen.
Icons or other critical elements should also use the above-recommended contrast ratios.
There are several free tools available to give you meaningful feedback about the levels of contrast in your chosen palette. One of them is WebAIM’s Color Contrast Checker56 which let you test colors you have already chosen.
Along with establishing readable text, contrast can also draw the user’s attention towards specific elements on a screen. Generally, high-contrast is the best choice for important content or key elements. So, if you want users to see or click something, make it stand out! For example, users are much more likely to click a call-to-action button that strongly contrasts with its background.
Have you thought about your app appears to users who have visual impairments?
When people talk about color blindness58, they usually refer to the inability of perceiving certain colors. Approximately 8% of men and 0.5% of women are affected by some form of color blindness. Red and green colors are a common problematic combination.
Since colorblindness takes different forms (including red-green, blue-yellow, and monochromatic), it’s important to use multiple visual cues to communicate important states in your app. Never rely on a color solely to indicates system status. Instead, use elements such as strokes, indicators, patterns, texture, or text to describe actions and content.
Also, Photoshop61 has really useful tools to help, and can simulate color blindness. This feature allows the designer to see what the app’s screen will look like to people with different types of color blindness.
We’ve barely covered the fundamentals of how color theory can enhance your app UI design. Honing your color usage skills is an ongoing endeavor. If you want to learn how to create beautiful, functional color schemes, all it takes is practice, determination, and lots of user testing.
This article is part of the UX design series sponsored by Adobe. The newly introduced Experience Design app65 is made for a fast and fluid UX design process, creating interactive navigation prototypes, as well as testing and sharing them — all in one place.
You can check out more inspiring projects created with Adobe XD on Behance66, and also visit the Adobe XD blog to stay updated and informed. Adobe XD is being updated with new features frequently, and since it’s in public Beta, you can download and test it for free67.
(ms, aa, il)
Email is one of the best ways to engage with your users, especially during the holiday season. However, if you want to stand out, no matter how beautiful your emails are, you need to make sure they render correctly in your reader’s inbox, regardless of what email client they’re using. Creating responsive email is not an easy task, and there are various reasons for that.
First, there is no standard in the way email clients render HTML. This is true for email clients from different companies, such as Outlook and Apple Mail, but not only. Even different versions of Outlook, such as Outlook 2003, Outlook 2013 and Outlook.com, render HTML differently.
Then, while email clients render HTML, many of them have very limited support of it. Some email clients will just strip the head of your HTML file, including media queries, which is why inline styles are heavily recommended. On a good note, this is moving in the right direction with the Gmail update1.
Now, there are a few techniques out there to help email developers. You might be familiar with some of them, such as the hybrid approach2, the mobile-first approach3 or even the Fab Four technique4 by HTeuMeuLeu5. But because of the reasons stated earlier, and especially the lack of a standard, none of these techniques will enable you to tame all email clients at once.
MJML is an open-source framework that abstracts away the complexity of responsive email. The idea behind it is pretty simple. Just as jQuery normalizes the DOM and abstracts low-level interactions and animations, MJML abstracts the low-level hacks for responsive emails with an easy syntax. MJML is responsive by design. This means you can forget about nested tables and conditional comments and, more generally, about making your email responsive. MJML does it for you.
Leveraging a semantic syntax and high-level components such as the carousel6 (yes, you can display an interactive image gallery within an email!), MJML is really easy to learn for anyone. Responsive emails are no longer only accessible to a handful of experts anymore.
Being easy to use doesn’t mean that MJML is not powerful. It just enables experts to streamline their development workflow, while still giving them the flexibility they need with lower-level components such as tables8.
For instance, our example email9 was coded in 788 lines of HTML10 and reproduced in fewer than 240 lines of MJML11.
MJML is built in React12, a JavaScript library developed and maintained by Facebook, and it leverages the power of React’s components. The component names are semantic, starting with mj-
, so that they are straightforward as well as easy to recognize and understand: mj-text
, mj-image
, mj-button
, etc.
MJML will transpile to responsive HTML, following a mix of the mobile-first and hybrid coding approaches. Going mobile-first enables you to make sure that the most readable version is displayed by default in email clients that do not change the layout according to the device used.
For example, in Outlook.com, the mobile version will be displayed on both desktop and mobile (which is far more readable than a desktop version being displayed on mobile). The hybrid approach then enables the layout to change according to the device’s size wherever possible, using a mix of fallbacks, conditional comments, nested tables and media queries to target as many clients as possible. The layout degrades nicely, with multi-column layouts on desktop turning into single-column layouts on mobile.
Before we start the tutorial, let’s get ready to code in MJML. We have different ways to use MJML, such as running it locally15 or using the online editor2316. By choosing the online editor, you’ll be able to start immediately, but running it locally enables you to use MJML with your favorite code editor (with plugins for Atom17, Sublime Text18 and Vim19), task runners such as gulp-mjml20 and grunt-mjml21 and a lot more22.
For this tutorial, the online editor2316 is recommended because it doesn’t require any setup.
If you still want to use it locally, open your terminal and run npm install mjml -g
(requires to have Node.js and npm24 installed).
Once MJML is installed, create a file named example.mjml
(or any name you like) with some MJML and run mjml -r example.mjml
in your terminal (make sure to be in the same folder as the file you’re rendering). It will render your MJML file in a file named example.html
, with the responsive HTML inside. You can find all available options for the command line in the documentation25.
Now that you’re all set up, we can start creating a responsive newsletter together. To find inspiration, one of the best places around is ReallyGoodEmails26. I found a great example of a newsletter27 by Thrive Market28. Let’s recreate it with MJML!
(Brand names, logos and trademarks used herein remain the property of their respective owners, and their use does not imply any endorsement by or affiliation with Thrive Market.)
First, here is what the basic layout30 of an MJML document looks like:
<mjml> <mj-head> <!-- Head components go here: https://mjml.io/documentation/#standard-head-components --> </mj-head> <!-- All of the content of our email will go in mj-body --> <mj-body> <!-- mj-container defines the default styling of our email, including the default width, set to 600px but overwritable --> <mj-container> <!-- Body components go here (https://mjml.io/documentation/#standard-body-components) --> </mj-container> </mj-body> </mjml>
While inlining is the norm in HTML to ensure responsiveness, MJML also allows you to create custom MJML classes and MJML styles, which will then automatically be inlined from the head of your MJML file31. In MJML, styles come as component attributes.
<mj-text font-size="20px" color="#F45E43" font-family="helvetica"> Hello World </mj-text>
To start, we’ll set a default sans-serif
font family for text components, because sans-serif
is used for most text here. Then, we’ll create classes for elements used repeatedly, so that we don’t have to manually set styles over and over again. We could have created more custom classes and styles, but the most useful styles will be for:
Here, we’ll be leveraging head components32 in three ways:
padding
of 0
for every component, using <mj-all />
. This default padding
can be overridden by manually specifying a padding
directly on the components we are using.<mj-text>
, using <mj-text />
in the head. Each time we use <mj-text>
, the default font family will be sans-serif
. We can still manually override this new default font by specifying a new one directly on the component.<mj-class />
and, more specifically, using the following mj-classes:
pill
background-color
set to transparent
and the inner-padding
, used to size the button as we want.desc
ico
<mj-head> <mj-title>Discover the latest trends</mj-title> <mj-attributes> <mj-all padding="0" /> <mj-text font-family="sans-serif" color="#8e8b85" /> <mj-class name="pill" font-weight="700" color="#d5ad4b" border-radius="50" border="2px solid #d5ad4b" font-size="16px" line-height="16px" padding="8 20 20 20" inner-padding="10 75 10 75" background-color="transparent" /> <mj-class name="desc" font-family="Georgia" font-size="20px" color="#45495d" padding="25 5 10 10" /> <mj-class name="ico" font-family="Helvetica" font-size="14px" align="center" padding="0 0"/> </mj-attributes> </mj-head>
(As you may have noticed, we have omitted the units on some attributes, such as padding
. If you do so, MJML will output the attributes with px
by default.)
Now that the styles are defined in the head
(you can see here33 what your code should look like now), let’s start adding content to our email! MJML layouts are based on <mj-section>
34 to create rows and <mj-column>
3835 to create columns inside rows, which is very practical for email design. Note that, except for high-level components such as <mj-hero>
36 and <mj-navbar>
37, content should always be wrapped in a <mj-column>
3835, which itself should go in a <mj-section>
39, even when there is only one column in a section.
Let’s see this with two examples, the preheader and the header of the email.
There’s nothing very fancy here. We’re just creating a text preheader using the <mj-text>
40 component, wrapped in a column, and giving it some MJML styles.
Hack: <mj-text>
enables you to use HTML directly inside MJML, granting you full control over the content and allowing you to use the <span>
and <a>
tags you’re used to, for example.
<mj-section padding="0" background-color="#eeebe7"> <mj-column> <mj-text padding="0 2" align="center" font-size="10px"> The latest tips, trends, recipes, and more </mj-text> </mj-column> </mj-section>
The header is a bit fancier but very easy to achieve with MJML. All we have to do is create a section that we’ll split into three equal columns (because columns are equal, we don’t even have to manually set the width). We’ll use images as in the original email, leveraging the explicit component <mj-image>
42, which comes with the common attributes you would expect, including src
, padding
and border
.
By default, columns will stack in the mobile version of our email. Here, because we want those three columns to stay side by side even on mobile, we’ll wrap them in an <mj-group>
43 component, which will prevent the columns from stacking.
Hack: As noted in the documentation, due to a bug in iOS, you might have to use a minifier, or else the columns could stack on iPhone even if they’re wrapped in <mj-group>
. The MJML command-line interface44 comes with a built-in minifier45 that you can use to run the -m
option when rendering MJML.
<mj-section padding="0" background-color="#ffffff"> <mj-group> <mj-column> <mj-image align="right" padding="40 0 0 0" width="100" href="https://thrivemarket.com/blog?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/blog_zpscedcgvla.jpg" alt="blog" title="blog" /> </mj-column> <mj-column> <mj-image padding="15 0 0 0" href="https://thrivemarket.com/?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/thrive-logo-250_zps4lonavha.jpg" title="logo" alt="logo" /> </mj-column> <mj-column> <mj-image align="left" padding="40 0 0 0" width="100" href="https://thrivemarket.com/customer/account/login/referer/aHR0cHM6Ly90aHJpdmVtYXJrZXQuY29tL2ludml0ZT9jY29kZT1LRzZPQ0QzSCZ1YWV4cHRpbWU9MTc3ODYzNzg2MiZ1YXRva2VuPTE0NjJiOTc3YzFmYTcwYjA5YjU4NWYzYTg5NDNkNzlhNzA3OWFiMzE0MzkzZmU4OTg1MmZkODNmZDA1YjYzMWUmdWlkPTUwMTk4NTAmdXRtX2NhbXBhaWduPWRheTgmdXRtX2NvbnRlbnQ9bGVhZF93ZWxjb21lJnV0bV9tZWRpdW09bGlmZWN5Y2xlJnV0bV9zb3VyY2U9c2FpbHRocnU,/" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/refer_zpsnoo1uzvu.jpg" title="refer and win" alt="refer and win" /> </mj-column> </mj-group> </mj-section>
We’ll create another section in which we’ll simply add an image, some text and a button. The only new component here is <mj-button>
46, which should be clear enough and which comes with the standard attributes. Please note that you must specify an href
, or else the text of the button might not display in some email clients.
<mj-section padding="20"> <mj-column> <mj-image padding="5 0 23 0" width="200px" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/566f6a67e871e_zpskalhjefi.gif" /> <mj-text align="center" font-size="16px"> Your daily destination for healthy living </mj-text> <mj-button padding="20 0 10 0" mj-class="pill" href="https://thrivemarket.com/blog?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru"> Read more </mj-button> </mj-column> </mj-section>
Yes, there’s a component for that, too! It’s a good practice to split different rows into different sections, instead of dumping everything into the same section, because that gives you more control over styling (especially through padding
), and it also makes it easier to isolate any problems that occur.
<mj-section padding="0 0 20 0"> <mj-column> <mj-divider border-width="5px" border-color="#EEEBE7" /> </mj-column> </mj-section>
We’ll use the same divider between the body and footer later.
Now that we have a beautiful header47, let’s start with what seems to be the complex part of this email.
Here, we’ll create a section for each two-column row. Each column will consist of an image, a piece of text and a button. The good news is that, thanks to MJML’s hybrid approach, we don’t have to do anything to make the images stack on mobile. It’s responsive by default!
Regarding the styling, we don’t have much to do because we’re leveraging the mj-classes pill
and desc
, which we created earlier. All we need to do is add some padding
to match the style of the original email.
Hack: Due to an issue with Outlook 2000, 2002 and 2003 ignoring the set width, it’s a good practice in MJML to set images to the sizes you want them to display at in those clients.
<mj-section padding="20 10 0 10"> <mj-column> <mj-image width="280px" href="https://thrivemarket.com/blog/foods-for-happiness?uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&uid=5019850&utm_campaign=day8&utm_content=lead_welcome&utm_medium=lifecycle&utm_source=sailthru" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/5-foods_zps9escvg7g.jpg" /> <mj-text mj-class="desc"> <a href="https://thrivemarket.com/blog/foods-for-happiness?uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&uid=5019850&utm_campaign=day8&utm_content=lead_welcome&utm_medium=lifecycle&utm_source=sailthru"> 5 Foods to Boost Your Happiness </a> </mj-text> <mj-button mj-class="pill" href="https://thrivemarket.com/blog/foods-for-happiness?uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&uid=5019850&utm_campaign=day8&utm_content=lead_welcome&utm_medium=lifecycle&utm_source=sailthru"> Read more </mj-button> </mj-column> <mj-column> <mj-image width="280px" href="https://thrivemarket.com/blog/whiten-teeth-instantly-activated-charcoal?uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&uid=5019850&utm_campaign=day8&utm_content=lead_welcome&utm_medium=lifecycle&utm_source=sailthru" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/teeth_zpsr1sycjxi.jpg" /> <mj-text mj-class="desc"> <a href="https://thrivemarket.com/blog/whiten-teeth-instantly-activated-charcoal?uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&uid=5019850&utm_campaign=day8&utm_content=lead_welcome&utm_medium=lifecycle&utm_source=sailthru"> Whiten Teeth Instantly with Activated Charcoal </a> </mj-text> <mj-button mj-class="pill" href="https://thrivemarket.com/blog/whiten-teeth-instantly-activated-charcoal?uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&uid=5019850&utm_campaign=day8&utm_content=lead_welcome&utm_medium=lifecycle&utm_source=sailthru"> Read more </mj-button> </mj-column> </mj-section>
Here, we can see that the default HTML style for links is applied. In addition to <mj-attributes>
, you can also inline CSS right into MJML using <mj-style>
49. Let’s do just that by updating the head
to overwrite the default style of links once and for all. We’ll also create a CSS class to apply to the <a>
tags to give them the right color (we’re creating a class because the links in the email shouldn’t all have the same color).
<mj-head> <mj-title>Discover the latest trends</mj-title> <mj-attributes> <mj-all padding="0" /> <mj-text font-family="sans-serif" color="#8e8b85" /> <mj-class name="pill" font-weight="700" color="#d5ad4b" border-radius="50" border="2px solid #d5ad4b" font-size="16px" line-height="16px" padding="8 20 20 20" inner-padding="10 75 10 75" background-color="transparent" /> <mj-class name="desc" font-family="Georgia" font-size="20px" color="#45495d" padding="25 5 10 10" /> <mj-class name="ico" font-family="Helvetica" font-size="14px" align="center" padding="0 0"/> </mj-attributes> <mj-style> a { text-decoration:none; } .desc { color: #45495d; } </mj-style> </mj-head>
Follow the same steps with the other two-column sections.
To create this section, we’ll use the components that we’re getting familiar with: <mj-section>
, <mj-column>
and <mj-button>
.
First, we’ll add a background-color
to our section and some padding
so that it looks as expected. Then, we just have to create two columns, one wider than the other, leveraging the width
attribute with percentages. As usual, columns will stack on mobile. Because the button is different from the others here, we won’t use the pill
class, but rather will manually create a new style.
Hack: Because of poor support for shorthand HEX colors in Internet Explorer, we recommend using six-digit HEX colors. This is a hack that we’ll reintegrate in MJML at some point to make sure that three-digit HEX codes are turned into six-digit codes.
<mj-section background-color="#6d8be1" padding="15 40 10 40"> <mj-column width="60%"> <mj-text font-weight="bold" color="#ffffff" font-size="16px" padding="0 0 10 0"> Get all your healthy groceries at Thrive Market! </mj-text> </mj-column> <mj-column width="40%"> <mj-button background-color="#ffffff" color="#45495d" font-weight="800" font-family="sans-serif" font-size="16px" border-radius="2px" inner-padding="15 60"> Shop Now </mj-button> </mj-column> </mj-section>
To design the title, we’ll create a three-column layout. The columns will consist of the following, respectively:
"Want More Tips, Tricks, and Delicious Recipes?"
,Then, we will wrap the three columns in an <mj-group>
so that it doesn’t stack on mobile.
We’ll start creating the lines, leveraging the <mj-divider>
component, setting its border-width
to 2px
and adding some padding
so that it looks the way we want. By default, the divider will fill the column it’s contained in, so that it looks consistent across devices. Therefore, we don’t need to explicitly set the width of the divider.
Then, we’re just using <mj-text>
to add our title, leveraging the desc
mj-class (because we want to inherit some of the styles set in this class) and overriding the font-family
and align
properties that are different from this class. We could have also manually set the style without using the desc
mj-class.
Finally, we just have to use our button with the pill
mj-class as we did before. We’re overriding the inner-padding
because, according to the original design, this button is not as wide as the others.
<mj-section padding="40px 0 0 0"> <mj-group> <mj-column width="12%"> <mj-divider border-width="2px" padding="10 0 0 10" /> </mj-column> <mj-column width="75%"> <mj-text padding="0 0 20 0" mj-class="desc" font-family="Helvetica" align="center"> <a href="https://thrivemarket.com/blog?utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru"> Want More Tips, Tricks, and Delicious Recipes? </a> </mj-text> </mj-column> <mj-column width="13%"> <mj-divider border-width="2px" padding="10 10 0 0" /> </mj-column> </mj-group> </mj-section> <mj-section padding="5px"> <mj-column> <mj-image src="https://img.thrivemarket.com/emails/images/img-18.jpg" href="https://thrivemarket.com/blog?utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru" /> </mj-column> </mj-section> <mj-section padding="15px"> <mj-column> <mj-button mj-class="pill" inner-padding="12 25" href="https://thrivemarket.com/blog?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru"> Read our Blog </mj-button> </mj-column> </mj-section>
You can see52 what our MJML template should look like so far.
We’re wrapping our title in a section and a column, as we’ve seen before, and we’re using the desc
style that we created earlier. We need to alter only a few styles by manually setting a different align
, font-size
and font-style
from our mj-class.
<mj-section background-color="#EEEBE7" padding="25"> <mj-column> <mj-text padding-top="20px" mj-class="desc" align="center" font-size="28px" font-style="italic"> Stay Connected </mj-text> </mj-column> </mj-section>
This section is made up of six icons, with accompanying text below. The icons display side by side on desktop and wrap to two lines of three icons on mobile. By now, you probably know that creating such layouts is easy if we leverage the <mj-column>
component. To make the icons stack onto two lines, all we have to do is wrap the columns in two groups of three columns in an <mj-group>
.
To design the text below the icons, we just have to use the ico
mj-class that we created before. We’ll also create a CSS class named ico
to apply to the a
tags, like we did in the two-column layout section.
<mj-section padding="40 0"> <mj-group> <mj-column> <mj-image padding="10" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/img-01_zpssvjtmopj.png" /> <mj-text mj-class="ico"><a href="https://thrivemarket.com/paleo?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru">Paleo</a></mj-text> </mj-column> <mj-column> <mj-image padding="10" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/img-02_zpshj3vgh1w.png" href="https://thrivemarket.com/gluten-free?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru"/> <mj-text mj-class="ico"><a href="https://thrivemarket.com/gluten-free?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru">Gluten Free</a></mj-text> </mj-column> <mj-column> <mj-image padding="10" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/img-03_zpshvwomzpo.png" href="https://thrivemarket.com/vegan?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru"/> <mj-text mj-class="ico"><a href="https://thrivemarket.com/vegan?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru">Vegan</a></mj-text> </mj-column> </mj-group> <mj-group> <mj-column> <mj-image padding="10" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/img-04_zpsyeczb1sp.png" href="https://thrivemarket.com/ingredients/gmo-free?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru" /> <mj-text mj-class="ico"><a href="https://thrivemarket.com/ingredients/gmo-free?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru">Non-GMO</a></mj-text> </mj-column> <mj-column> <mj-image padding="10" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/img-05_zpsryppwpok.png" href="https://thrivemarket.com/certifications/certified-organic?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru" /> <mj-text mj-class="ico"><a href="https://thrivemarket.com/certifications/certified-organic?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru">Organic</a></mj-text> </mj-column> <mj-column> <mj-image padding="10" src="http://i1044.photobucket.com/albums/b447/ngarnier/Thrive%20Market/img-06_zpsoq4ulmbq.png" href="https://thrivemarket.com/raw?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru" /> <mj-text mj-class="ico"><a href="https://thrivemarket.com/raw?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru">Raw</a></mj-text> </mj-column> </mj-group> </mj-section>
Once again, this section is easy to achieve by leveraging the <mj-column>
component, using one column per social network icon and wrapping all of the icons in an <mj-group>
so that they don’t stack on mobile. The only trick here is to fine-tune the width
of the images and the padding so that the result is consistent with the original design.
Because this part is pretty simple, it’s OK to wrap the divider and the text in the same column and section, even though we could have separated the divider from the text. As we did before when styling the links, we’ll create a footer
class so that our links have the proper color.
<mj-section background-color="#EEEBE7" padding="25 40"> <mj-column> <mj-divider /> <mj-text padding="30 0 0 0" align="center" font-size="14"><a href="https://thrivemarket.com/blog?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru">Read Our Blog</a> <a href="https://thrivemarket.com/">View Email Online</a></mj-text> <mj-text align="center" color="#45495d" font-size="10px" line-height="14px"> <p>Please don't hit 'reply' to this email—we won't be able to email you back from this address and help you thrive! If you need anything, visit our <a href="https://thrivemarket.com/faq?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru">FAQ</a> or contact <a href="https://thrivemarket.com/faq/contact?uid=5019850&uaexptime=1778637862&uatoken=1462b977c1fa70b09b585f3a8943d79a7079ab314393fe89852fd83fd05b631e&ccode=KG6OCD3H&utm_content=lead_welcome&utm_medium=lifecycle&utm_campaign=day8&utm_source=sailthru">Member Services</a> anytime, and we'll be happy to help!</p> <p>We don't want to see you go, but if you no longer wish to receive promotional emails from us, you can <a href="https://thrivemarket.com/">unsubscribe here</a> or <a href="https://thrivemarket.com/">change your email preferences</a> anytime.</p> <p>4509 Glencoe Ave, Marina Del Rey, CA 90292 <br />@2016 Thrive Market All Rights Reserved</p> </mj-text> </mj-column> </mj-section>
You should now have fewer than 240 lines of MJML (you can check the full code54), whereas the original file was a bit less than 800 lines of HTML. Congrats! You’ve just created your first responsive email using MJML! How easy was that?
Testing how an email renders in different email clients is always a good practice, so let’s do just that using Litmus55 (Email on Acid56 is another great platform for email testing).
Go to Litmus57 (create an account if you don’t have one already), create a new project, and paste the HTML that we generated earlier in the code area. You’ll be able to see the results in various email clients by clicking “Instant Previews” in the right pane. You can see58 the results for major email clients, from Outlook 2003 to Inbox by Gmail.
Now that you know how to use MJML to easily create a responsive email, why not try to create your own component? My tutorial59 will guide you through this step by step.
What did you think about your first experience with MJML? Feel free to reach out on Twitter60 and join the MJML community now by signing up to the Slack61 channel. You can also subscribe to the newsletter62 to keep up to date on the latest news.
(rb, vf, yk, al, il)
Will the resources spent implementing app indexing for Google search be a boon or a bust for your app’s traffic? In this article, I’ll take you through a case study for app indexing at our company, the results of which may surprise you.
App indexing is one of the hottest topics in SEO right now, and in some sense for good reason. Google has only been indexing apps for everyone1 for a little more than two years, and with only 30% of apps being indexed2 there is huge potential for websites to draw additional search traffic to their apps.
What’s more, Google has given not one but two ranking boosts3 to websites that use app indexing and the app indexing API4; so, implementing app indexing for your website is likely to increase your search traffic.
However, when we did it, it was a bust. We got a lot more app traffic from Google search when we implemented app indexing, but it was so little traffic compared to web search at that point that it was almost not worth the effort. Read on to learn more about what we did and what effect it had on our overall traffic.
If you’re not familiar with app indexing5, it is basically the process by which your app appears in Google search results alongside relevant web results. By supporting HTTP URLs in your app and adding the App Indexing SDK, you allow Google to crawl and index your app as it would a web page, and you enable users to install or launch your app from search results when they search with relevant keywords.
If the app is already installed, you’ll see a button to launch it in the relevant search results:
If the searcher doesn’t have the app installed yet, they will see an “Install” button in search results:
Theoretically, this is great for users because they can find relevant authoritative content in Google regardless of whether they prefer websites or apps, and it’s great for app developers and marketers because it allows apps to be exposed to a whole new audience outside of the app store, potentially increasing app usage and downloads. But “theoretically” doesn’t necessarily mean that app indexing brings a lot of real traffic from search.
While I have seen a lot of great info on how10 and why11 to implement app indexing, I haven’t yet seen a case study on the benefits of app indexing in terms of traffic (though Google does highlight several other benefits12). So, I looked at our app indexing traffic at Vivid Seats so that marketers, developers and webmasters can get a better sense of how much traffic, realistically, they can expect from getting their app indexed.
I want to start the case study with a small caveat: Vivid Seats13 is the largest independent ticket marketplace, and the number-three resale marketplace behind StubHub and Ticketmaster, according to Bloomberg14. As such, we get a lot of web traffic. A website that doesn’t get as much web traffic as we do or an app that has no equivalent website will probably have different results than ours — especially if it’s in a different industry. That said, a lot of large websites might see similar results and might want to adjust their strategy accordingly.
This is also an Android-only case study, because we haven’t yet been given access to the iOS app-indexing beta in Google’s Search Console15, and we don’t have the same kind of visibility into our iOS app’s indexing and ranking.
Vivid Seats has had an Android app16 indexed since September of 2015. However, Google had a difficult time finding the equivalent app URI for our web pages at first, and in February of this year we had about 18 app URIs indexed and more than 35,000 pages with the “Intent URI not supported” error17. We had had different URIs for our app than for our website at the time, which was making it difficult for Google to find equivalent pages.
Initially, we tried to add alternate tags to our web pages pointing to the equivalent app URIs, as instructed by Google’s help section on the subject18.
For example, Adele’s Vivid Seats page19 — http://www.vividseats.com/concerts/adele-tickets.html
— had the following rel="alternate"
tag, which was dynamically served to mobile user agents and which pointed to the equivalent app URI:
<link rel="alternate" href="android-app://com.vividseats.android/vividseats/performer/15313">
We were initially hopeful that this solution would be all that was necessary, because Google recommended it in its help section as a solution to this problem. Unfortunately, weeks after implementation, it was clear that Google was still having a difficult time figuring it out, and it didn’t index many more app URIs.
And the crawl errors persisted because Google was assuming that a lot of web pages on our website had equivalent app views, which at the time wasn’t the case.
We supported universal links22 in our iOS app and HTTP URLs in Android23 in April, which reduced the number of errors from 40,000 at its apex to about 85 today. As a result, we have seen the number of app URIs indexed go from 18 in February to about 35,000 today.
To do this, we simply followed the procedure outlined in the Firebase documentation26:
getIntent
in the onCreate
code and deployed the app to the Play store.Predictably, clicks and impressions grew exponentially from December, when 18 app URIs were indexed, to September, when 40,000 app URIs were indexed. Overall, clicks grew 919%, with clicks from non-brand queries (i.e. informational queries that don’t mention Vivid Seats, like “packers tickets”) growing 5,500%. The clickthrough rate (CTR) went down, predictably, as a result of the increase in impressions, specifically non-brand ones.
While app traffic (defined in Search Console as “clicks”) grew exponentially as a result of the increase in app URIs getting indexed, relative to the website traffic, app traffic has been underwhelming to date.
Even with an additional 40,000 app URIs indexed, 99.82% of our traffic from search currently goes to web pages, with just 0.23% of total search traffic going to our app URIs.
A few reasons why this might be so low:
This last point could just be a matter of this being fairly new technology and searchers not fully understanding the new layout, and we could imagine this trend reversing sometime in the future. For now, however, it’s important that this point is understood by all who are looking to index their app pages in search results. It’s tempting to think that the presence of a large colorful button in search results will increase the CTR and traffic, but our research hasn’t found any evidence to support this.
In fact, we found just the opposite: The CTR to app content is less than half of what it is to web content, with our Android app pages getting a CTR of 1.55%, at an average position of 5.1, compared to our web content CTR of 4.67%, two positions below our app content, at 7.6.
Though app content in general has a lower CTR than web content, we found that navigational (or branded) search terms actually have a lower CTR for us than informational terms, which is the opposite of how it works for web content.
56% of the app traffic we received came from branded queries, compared to just 40% of our web traffic.
Looking deeper into query type, we broke down queries into two distinct types: first, navigational queries that mention our brand (for example, “vivid seats”) and informational queries that don’t (for example, “packer tickets”), and secondly, queries that indicate the searcher is looking for a website (for example, “www.vividseats.com”) or an app (for example, “download vivid seats app”) specifically.
If the searcher hadn’t indicated in their query that they were looking for an app or a website, we recorded that the query was branded or not, and that the website or app preference was “Not Specified.”
4% of total app traffic specified that they’re looking for an app, compared to less than 1% for web traffic. This is important traffic to be aware of, because it doesn’t have an equivalent on the desktop, and it could be a new query type to optimize for, but it’s not representative of app traffic. The great majority of queries for both web (99%) and app (96%) do not use qualifiers to describe whether the searchers are looking for an app or website specifically.
The traffic to app content from search has so far been underwhelming, but the real reason most developers would index their app content would be to drive downloads.
Fortunately, with Search Console, we can see installations from search as well. It’s kind of hidden in the Search Console reports; to see it, go to “Search analytics” → “Search appearance” → “Filter search appearance” → “Install app button.”
When we did that, we found that just 0.03% of our total search traffic led to an app installation from search in the last 90 days.
But when you take the total number of installations from the last 90 days and compare it to the installations we’ve gotten from search, it’s clear that about 2% of our total Android downloads came from users installing the downloads in search results. This isn’t terribly impressive, but when you consider that this represents many app installations from search that we may not have had otherwise, it justifies for us the little bit of work it took to implement it.
If your resources are limited and you’re wondering if app indexing would deliver enough traffic and installations to justify the effort, our experience would suggest you should focus on web content instead. Even after growing our traffic from app indexing by 919%, web content still brings in more than 99.8% of the total traffic from search to our website. If you’re considering other high-value projects, you might want to do those instead.
Still, if you only have an app to index, or if you have resources on your team, then app indexing does bring traffic that you wouldn’t get otherwise, and it might be worth the long-term benefits to your website and to Google users.
Have you implemented app indexing with similar (or dissimilar) results? I’d love to hear about it in the comments.
Many thanks to the UX design and development group at Vivid Seats, including Fawaad Ahmad, who made a lot of this happen.
(da, vf, il, yk, al)
Front page image credits: Introducing Firebase App Indexing39
What fuels your work? What fuels your mind? What do you do on a non-productive day or when you’re sad? Nowadays, I try to embrace these times. I try to relax and not be angry at myself for not being productive.
And the fun fact about it? Well, most of the times when I could convince my mind that not being productive is nothing to feel bad about, things take a sudden turn: I get my ideas back, my productivity rises and, in effect, I even achieve more work than on an average day. It’s important to try to be human.
ack
over grep
in the command line3 to search for strings or regular expressions in files.Content Security Policy
at github.com6. Now they share more learnings in “GitHub’s post-CSP journey7”. The focus lies on img-src
, form nonces, same-site cookies, and more.role
attribute13. New, for example, are role=term
, role=feed
, and role=switch
.And with that, I’ll close for this week. If you like what I write each week, please support me with a donation25 or share this resource with other people. You can learn more about the costs of the project here26. It’s available via email, RSS and online.
— Anselm
First of all, let’s define some vocabulary. “Internationalization” is a long word, and there are at least two widely used abbreviations: “intl,” “i18n”. “Localization” can be shortened to “l10n”.
Internationalization can be generally broken down into the following challenges:
Note: In this article, I am going to focus only on front-end part. We’ll develop a simple universal React application with full internationalization support.
Let’s use my boilerplate repository1 as a starting point. Here we have the Express web server for server-side rendering, webpack for building client-side JavaScript, Babel for translating modern JavaScript to ES5, and React for the UI implementation. We’ll use better-npm-run to write OS-agnostic scripts, nodemon to run a web server in the development environment and webpack-dev-server to serve assets.
Our entry point to the server application is server.js
. Here, we are loading Babel and babel-polyfill to write the rest of the server code in modern JavaScript. Server-side business logic is implemented in src/server.jsx
. Here, we are setting up an Express web server, which is listening to port 3001
. For rendering, we are using a very simple component from components/App.jsx
, which is also a universal application part entry point.
Our entry point to the client-side JavaScript is src/client.jsx
. Here, we mount the root component component/App.jsx
to the placeholder react-view
in the HTML markup provided by the Express web server.
So, clone the repository, run npm install
and execute nodemon and webpack-dev-server in two console tabs simultaneously.
In the first console tab:
git clone https://github.com/yury-dymov/smashing-react-i18n.git cd smashing-react-i18n npm install npm run nodemon
And in the second console tab:
cd smashing-react-i18n npm run webpack-devserver
A website should become available at localhost:3001
2. Open your favorite browser and try it out.
We are ready to roll!
There are two possible solutions to this requirement. For some reason, most popular websites, including Skype’s and the NBA’s, use Geo IP to find the user’s location and, based on that, to guess the user’s language. This approach is not only expensive in terms of implementation, but also not really accurate. Nowadays, people travel a lot, which means that a location doesn’t necessarily represent the user’s desired locale. Instead, we’ll use the second solution and process the HTTP header Accept-Language
on the server side and extract the user’s language preferences based on their system’s language settings. This header is sent by every modern browser within a page request.
The Accept-Language
request header provides the set of natural languages that are preferred as a response to the request. Each language range may be given an associated “quality” value, which represents an estimate of the user’s preference for the languages specified by that range. The quality value defaults to q=1
. For example, Accept-Language: da, en-gb;q=0.8, en;q=0.7
would mean, “I prefer Danish, but will accept British English and other types of English.” A language range matches a language tag if it exactly equals the tag or if it exactly equals a prefix of the tag such that the first tag character following the prefix is -
.
(It is worth mentioning that this method is still imperfect. For example, a user might visit your website from an Internet cafe or a public computer. To resolve this, always implement a widget with which the user can change the language intuitively and that they can easily locate within a few seconds.)
Here is a code example for a Node.js Express web server. We are using the accept-language
package, which extracts locales from HTTP headers and finds the most relevant among the ones supported by your website. If none are found, then you’d fall back to the website’s default locale. For returning users, we will check the cookie’s value instead.
Let’s start by installing the packages:
npm install --save accept-language npm install --save cookie-parser js-cookie
And in src/server.jsx
, we’d have this:
import cookieParser from 'cookie-parser'; import acceptLanguage from 'accept-language'; acceptLanguage.languages(['en', 'ru']); const app = express(); app.use(cookieParser()); function detectLocale(req) { const cookieLocale = req.cookies.locale; return acceptLanguage.get(cookieLocale || req.headers['accept-language']) || 'en'; } … app.use((req, res) => { const locale = detectLocale(req); const componentHTML = ReactDom.renderToString(<App />); res.cookie('locale', locale, { maxAge: (new Date() * 0.001) + (365 * 24 * 3600) }); return res.end(renderHTML(componentHTML)); });
Here, we are importing the accept-language
package and setting up English and Russian locales as supported. We are also implementing the detectLocale
function, which fetches a locale value from a cookie; if none is found, then the HTTP Accept-Language
header is processed. Finally, we are falling back to the default locale (en
in our example). After the request is processed, we add the HTTP header Set-Cookie
for the locale detected in the response. This value will be used for all subsequent requests.
I am going to use the React Intl3 package for this task. It is the most popular and battle-tested i18n implementation of React apps. However, all libraries use the same approach: They provide “higher-order components” (from the functional programming design pattern4, widely used in React), which injects internationalization functions for handling messages, dates, numbers and currencies via React’s context features.
First, we have to set up the internationalization provider. To do so, we will slightly change the src/server.jsx
and src/client.jsx
files.
npm install --save react-intl
Here is src/server.jsx
:
import { IntlProvider } from 'react-intl'; … --- const componentHTML = ReactDom.renderToString(<App />); const componentHTML = ReactDom.renderToString( <IntlProvider locale={locale}> <App /> </IntlProvider> ); …
And here is src/client.jsx
:
import { IntlProvider } from 'react-intl'; import Cookie from 'js-cookie'; const locale = Cookie.get('locale') || 'en'; … --- ReactDOM.render(<App />, document.getElementById('react-view')); ReactDOM.render( <IntlProvider locale={locale}> <App /> </IntlProvider>, document.getElementById('react-view') );
So, now all IntlProvider
child components will have access to internationalization functions. Let’s add some translated text to our application and a button to change the locale (for testing purposes). We have two options: either the FormattedMessage
component or the formatMessage
function. The difference is that the component will be wrapped in a span
tag, which is fine for text but not suitable for HTML attribute values such as alt
and title
. Let’s try them both!
Here is our src/components/App.jsx
file:
import { FormattedMessage } from 'react-intl'; … --- <h1>Hello World!</h1> <h1><FormattedMessage defaultMessage="Hello World!" description="Hello world header greeting" /></h1>
Please note that the id
attribute should be unique for the whole application, so it makes sense to develop some rules for naming your messages. I prefer to follow the format componentName.someUniqueIdWithInComponent
. The defaultMessage
value will be used for your application’s default locale, and the description
attribute gives some context to the translator.
Restart nodemon and refresh the page in your browser. You should still see the “Hello World” message. But if you open the page in the developer tools, you will see that text is now inside the span
tags. In this case, it isn’t an issue, but sometimes we would prefer to get just the text, without any additional tags. To do so, we need direct access to the internationalization object provided by React Intl.
Let’s go back to src/components/App.jsx
:
--- import { FormattedMessage } from 'react-intl'; import { FormattedMessage, intlShape, injectIntl, defineMessages } from 'react-intl'; const propTypes = { intl: intlShape.isRequired, }; const messages = defineMessages({ helloWorld2: { id: 'app.hello_world2', defaultMessage: 'Hello World 2!', }, }); --- export default class extends Component { class App extends Component { render() { return ( <div className="App"> <h1> <FormattedMessage defaultMessage="Hello World!" description="Hello world header greeting" /> </h1> <h1>{this.props.intl.formatMessage(messages.helloWorld2)}</h1> </div> ); } } App.propTypes = propTypes; export default injectIntl(App);
We’ve had to write a lot more code. First, we had to use injectIntl
, which wraps our app component and injects the intl
object. To get the translated message, we had to call the formatMessage
method and pass a message
object as a parameter. This message
object must have unique id
and defaultValue
attributes. We use defineMessages
from React Intl to define such objects.
The best thing about React Intl is its ecosystem. Let’s add babel-plugin-react-intl to our project, which will extract FormattedMessages
from our components and build a translation dictionary. We will pass this dictionary to the translators, who won’t need any programming skills to do their job.
npm install --save-dev babel-plugin-react-intl
Here is .babelrc
:
{ "presets": [ "es2015", "react", "stage-0" ], "env": { "development": { "plugins":[ ["react-intl", { "messagesDir": "./build/messages/" }] ] } } }
Restart nodemon and you should see that a build/messages
folder has been created in the project’s root, with some folders and files inside that mirror your JavaScript project’s directory structure. We need to merge all of these files into one JSON. Feel free to use my script5. Save it as scripts/translate.js
.
Now, we need to add a new script to package.json
:
"scripts": { … "build:langs": "babel scripts/translate.js | node", … }
Let’s try it out!
npm run build:langs
You should see an en.json
file in the build/lang
folder with the following content:
{ "app.hello_world": "Hello World!", "app.hello_world2": "Hello World 2!" }
It works! Now comes interesting part. On the server side, we can load all translations into memory and serve each request accordingly. However, for the client side, this approach is not applicable. Instead, we will send the JSON file with translations once, and a client will automatically apply the provided text for all of our components, so the client gets only what it needs.
Let’s copy the output to the public/assets
folder and also provide some translation.
ln -s ../../build/lang/en.json public/assets/en.json
Note: If you are a Windows user, symlinks are not available to you, which means you have to manually copy the command below every time you rebuild your translations:
cp ../../build/lang/en.json public/assets/en.json
In public/assets/ru.json
, we need the following:
{ "app.hello_world": "Привет мир!", "app.hello_world2": "Привет мир 2!" }
Now we need to adjust the server and client code.
For the server side, our src/server.jsx
file should look like this:
--- import { IntlProvider } from 'react-intl'; import { addLocaleData, IntlProvider } from 'react-intl'; import fs from 'fs'; import path from 'path'; import en from 'react-intl/locale-data/en'; import ru from 'react-intl/locale-data/ru'; addLocaleData([…ru, …en]); const messages = {}; const localeData = {}; ['en', 'ru'].forEach((locale) => { localeData[locale] = fs.readFileSync(path.join(__dirname, `../node_modules/react-intl/locale-data/${locale}.js`)).toString(); messages[locale] = require(`../public/assets/${locale}.json`); }); --- function renderHTML(componentHTML) { function renderHTML(componentHTML, locale) { … <script type="application/javascript" src="${assetUrl}/public/assets/bundle.js"></script> <script type="application/javascript">${localeData[locale]}</script> … --- <IntlProvider locale={locale}> <IntlProvider locale={locale} messages={messages[locale]}> … --- return res.end(renderHTML(componentHTML)); return res.end(renderHTML(componentHTML, locale)); ```
Here we are doing the following:
DateTime
and Number
formatting during startup (to ensure good performance);renderHTML
method so that we can insert locale-specific JavaScript into the generated HTML markup;IntlProvider
(all of those messages are now available to child components).For the client side, first we need to install a library to perform AJAX requests. I prefer to use isomorphic-fetch because we will very likely also need to request data from third-party APIs, and isomorphic-fetch can do that very well in both client and server environments.
npm install --save isomorphic-fetch
Here is src/client.jsx
:
--- import { IntlProvider } from 'react-intl'; import { addLocaleData, IntlProvider } from 'react-intl'; import fetch from 'isomorphic-fetch'; const locale = Cookie.get('locale') || 'en'; fetch(`/public/assets/${locale}.json`) .then((res) => { if (res.status >= 400) { throw new Error('Bad response from server'); } return res.json(); }) .then((localeData) => { addLocaleData(window.ReactIntlLocaleData[locale]); ReactDOM.render( --- <IntlProvider locale={locale}> <IntlProvider locale={locale} messages={localeData}> … ); }).catch((error) => { console.error(error); });
We also need to tweak src/server.jsx
, so that Express serves the translation JSON files for us. Note that in production, you would use something like nginx
instead.
app.use(cookieParser()); app.use('/public/assets', express.static('public/assets'));
After the JavaScript is initialized, client.jsx
will grab the locale from the cookie and request the JSON file with the translations. Afterwards, our single-page application will work as before.
Time to check that everything works fine in the browser. Open the “Network” tab in the developer tools, and check that JSON has been successfully fetched by our client.
To finish this part, let’s add a simple widget to change the locale, in src/components/LocaleButton.jsx
:
import React, { Component, PropTypes } from 'react'; import Cookie from 'js-cookie'; const propTypes = { locale: PropTypes.string.isRequired, }; class LocaleButton extends Component { constructor() { super(); this.handleClick = this.handleClick.bind(this); } handleClick() { Cookie.set('locale', this.props.locale === 'en' ? 'ru' : 'en'); window.location.reload(); } render() { return <button onClick={this.handleClick}>{this.props.locale === 'en' ? 'Russian' : 'English'}; } } LocaleButton.propTypes = propTypes; export default LocaleButton;
Add the following to src/components/App.jsx
:
import LocaleButton from './LocaleButton'; … <h1>{this.props.intl.formatMessage(messages.helloWorld2)}</h1> <LocaleButton locale={this.props.intl.locale} />
Note that once the user changes their locale, we’ll reload the page to ensure that the new JSON file with the translations is fetched.
High time to test! OK, so we’ve learned how to detect the user’s locale and how to show translated messages. Before moving to the last part, let’s discuss two other important topics.
In English, most words take one of two possible forms: “one apple,” “many apples.” In other languages, things are a lot more complicated. For example, Russian has four different forms. Hopefully, React Intl will help us to handle pluralization accordingly. It also supports templates, so you can provide variables that will be inserted into the template during rendering. Here’s how it works.
In src/components/App.jsx
, we have the following:
const messages = defineMessages({ counting: { id: 'app.counting', defaultMessage: 'I need to buy {count, number} {count, plural, one {apple} other {apples}}' }, … <LocaleButton locale={this.props.intl.locale} /> <div>{this.props.intl.formatMessage(messages.counting, { count: 1 })}</div> <div>{this.props.intl.formatMessage(messages.counting, { count: 2 })}</div> <div>{this.props.intl.formatMessage(messages.counting, { count: 5 })}</div>
Here, we are defining a template with the variable count
. We will print either “1 apple” if count
is equal to 1, 21
, etc. or “2 apples” otherwise. We have to pass all variables within formatMessage
‘s values
option.
Let’s rebuild our translation file and add the Russian translations to check that we can provide more than two variants for languages other than English.
npm run build:langs
Here is our public/assets/ru.json
file:
{ … "app.counting": "Мне нужно купить {count, number} {count, plural, one {яблоко} few {яблока} many {яблок}}" }
All use cases are covered now. Let’s move forward!
Your data will be represented differently depending on the locale. For example, Russian would show 500,00 $
and 10.12.2016
, whereas US English would show $500.00
and 12/10/2016
.
React Intl provides React components for such kinds of data and also for the relative rendering of time, which will automatically be updated each 10 seconds if you do not override the default value.
Add this to src/components/App.jsx
:
--- import { FormattedMessage, intlShape, injectIntl, defineMessages } from 'react-intl'; import { FormattedDate, FormattedRelative, FormattedNumber, FormattedMessage, intlShape, injectIntl, defineMessages, } from 'react-intl'; … <div>{this.props.intl.formatMessage(messages.counting, { count: 5 })}</div> <div><FormattedDate value={Date.now()} /></div> <div><FormattedNumber value="1000" currency="USD" currencyDisplay="symbol" /></div> <div><FormattedRelative value={Date.now()} /></div>
Refresh the browser and check the page. You’ll need to wait for 10 seconds to see that the FormattedRelative
component has been updated.
You’ll find a lot more examples in the official wiki8.
Cool, right? Well, now we might face another problem, which affects universal rendering.
On average, two seconds will elapse between when the server provides markup to the client and the client initializes client-side JavaScript. This means that all DateTimes
rendered on the page might have different values on the server and client sides, which, by definition, breaks universal rendering. To resolve this, React Intl provides a special attribute, initialNow
. This provides a server timestamp that will initially be used by client-side JavaScript as a timestamp; this way, the server and client checksums will be equal. After all components have been mounted, they will use the browser’s current timestamp, and everything will work properly. So, this trick is used only to initialize client-side JavaScript, in order to preserve universal rendering.
Here is src/server.jsx
:
--- function renderHTML(componentHTML, locale) { function renderHTML(componentHTML, locale, initialNow) { return ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hello React</title> </head> <body> <div>${componentHTML}</div> <script type="application/javascript" src="${assetUrl}/public/assets/bundle.js"></script> <script type="application/javascript">${localeData[locale]}</script> <script type="application/javascript">window.INITIAL_NOW=${JSON.stringify(initialNow)}</script> </body> </html> `; } const initialNow = Date.now(); const componentHTML = ReactDom.renderToString( --- <IntlProvider locale={locale} messages={messages[locale]}> <IntlProvider initialNow={initialNow} locale={locale} messages={messages[locale]}> <App /> </IntlProvider> ); res.cookie('locale', locale, { maxAge: (new Date() * 0.001) + (365 * 24 * 3600) }); --- return res.end(renderHTML(componentHTML, locale)); return res.end(renderHTML(componentHTML, locale, initialNow));
And here is src/client.jsx
:
--- <IntlProvider locale={locale} messages={localeData}> <IntlProvider initialNow={parseInt(window.INITIAL_NOW, 10)} locale={locale} messages={localeData}>
Restart nodemon, and the issue will almost be gone! It might persist because we are using Date.now()
, instead of some timestamp provided by the database. To make the example more realistic, in app.jsx
replace Date.now()
with a recent timestamp, like 1480187019228
.
(You might face another issue when the server is not able to render the DateTime
in the proper format, which will also break universal rendering. This is because version 4 of Node.js is not built with Intl support by default. To resolve this, follow one of the solutions described in the official wiki11.)
It sounds too good to be true so far, doesn’t it? We as front-end developers always have to be very cautious about anything, given the variety of browsers and platforms. React Intl uses the native Intl browser API for handling the DateTime
and Number
formats. Despite the fact that it was introduced in 2012, it is still not supported by all modern browsers. Even Safari supports it partially only since iOS 10. Here is the whole table from CanIUse for reference.
This means that if you are willing to cover a minority of browsers that don’t support the Intl API natively, then you’ll need a polyfill. Thankfully, there is one, Intl.js14. It might sound like a perfect solution once again, but from my experience, it has its own drawbacks. First of all, you’ll need to add it to the JavaScript bundle, and it is quite heavy. You’ll also want to deliver the polyfill only to browsers that don’t support the Intl API natively, to reduce your bundle size. All of these techniques are well known, and you might find them, along with how to do it with webpack, in Intl.js’ documentation15. However, the biggest issue is that Intl.js is not 100% accurate, which means that the DataTime
and Number
representations might differ between the server and client, which will break server-side rendering once again. Please refer to the relevant GitHub issue16 for more details.
I’ve come up with another solution, which certainly has its own drawbacks, but it works fine for me. I implemented a very shallow polyfill17, which has only one piece of functionality. While it is certainly unusable for many cases, it adds only 2 KB to the bundle’s size, so there is not even any need to implement dynamic code-loading for outdated browsers, which makes the overall solution simpler. Feel free to fork and extend it if you think this approach would work for you.
Well, now you might feel that things are becoming too complicated, and you might be tempted to implement everything yourself. I did that once; I wouldn’t recommend it. Eventually, you will arrive at the same ideas behind React Intl’s implementation, or, worse, you might think there are not many options to make certain things better or to do things differently. You might think you can solve the Intl API support issue by relying on Moment.js18 instead (I won’t mention other libraries with the same functionality because they are either unsupported or unusable). Fortunately, I tried that, so I can save you a lot of time. I’ve learned that Moment.js is a monolith and very heavy, so while it might work for some folks, I wouldn’t recommend it. Developing your own polyfill doesn’t sound great because you will surely have to fight with bugs and support the solution for quite some time. The bottom line is that there is no perfect solution at the moment, so choose the one that suits you best.
(If you feel lost at some point or something doesn’t work as expected, check the “solution” branch of my repository19.)
Hopefully, this article has given you all of the knowledge needed to build an internationalized React front-end application. You should now know how to detect the user’s locale, save it in the cookie, let the user change their locale, translate the user interface, and render currencies, DateTimes
and Number
s in the appropriate formats! You should also now be aware of some traps and issues you might face, so choose the option that fits your requirements, bundle-size budget and number of languages to support.
(rb, al, il, vf)
As the saying goes, “A picture is worth a thousand words.” Human beings are highly visual creatures who are able to process visual information almost instantly; 90 percent1 of all information that we perceive and that gets transmitted to our brains is visual. Images can be a powerful way to capture users’ attention and differentiate your product. A single image can convey more to the observer than an elaborate block of text. Furthermore, images can cross language barriers in a way text simply can’t.
Images are more than just decoration; they have the power to make or break a user’s experience. In this article, we’ll cover a good number of useful principles and best practices that will help you successfully integrate imagery into your designs. If you’d like to bring your app or website to life with little effort, you can download and test Adobe XD2for free.
Every image has a story to tell. Just like it is with writing, it’s better if you know what you want to say before you begin. Compelling images have a unique ability to inspire and engage your audience, but not all images improve the experience. Some of them just take up space or, in the worst case, confuse the user. One of the most dangerous elements in any design is imagery that conveys the wrong message.
Users react to visuals faster than text, so make sure your content matches the supporting visuals. You should select images that have a strong relationship with your product goals and ensure that they are context-relevant.
The principle “more is better” doesn’t apply to images. Don’t put a lot of effort in creating purely decorative images because people usually ignore such images. Instead, choose images that showcase the purpose of your product. Use limited number of striking visuals in your designs — the ones that really capture users’ attention.
Make sure your images are appropriately sized for displays across all platforms. Images shouldn’t appear pixelated, so be sure to test appropriate resolution sizes for specific ratios and devices. Display photos and graphics in their original aspect ratio, and don’t scale them greater than 100%. You don’t want the artwork or graphics in your product to look skewed, too small or too large.
Responsive websites and mobile apps often struggle with selecting image resolutions that best match the various user devices. It’s quite clear that one image for all screen resolutions and different devices is not enough. However, an image per pixel is too much; cropping images one at a time can be overwhelming especially if you have a ton of images.
So, how can someone automatically choose the optimal responsive image sizes? Hopefully, there are online tools that allow you to manage multiple sizes for an entire folder of images. One of them is Cloudinary1412 which enables you to interactively generate responsive image breakpoints. This tool uses advanced algorithms to easily generate best matching breakpoints for each uploaded image; images are analyzed to find the best breakpoints on an image by image basis, rather than creating all possible image resolutions.
Getting people’s attention with aesthetically pleasing images certainly has value, but it comes at the price of making other elements harder to see and use.
Putting too much focus on images in your designs may create a visual overkill that can seriously distract users from meaningful engagement with your content. You can see this effect in SoundCloud’s app in which the image takes all the attention and you barely notice the two buttons.
Although image-focused design is appropriate in some cases (e.g. Apple’s homepage), the vast majority of apps and sites should follow a balanced approach — images that are used in user interfaces should support the product, but not obscure other important content or overshadow functionality.
Both illustration and photography can be used within the same product.
Imagery is a visual communication tool that conveys your message. A clear focus communicates the concept at a glance, whereas a lack of focus makes the image meaningless.
When the point of focus is obscured, the iconic quality of the image is lost:
Avoid making the user hunt for the meaning in the image and ensure that a clear concept is conveyed to the user in a memorable way.
Try to minimize distraction and focus on meaningful elements in your images.
Human images are a very effective way to get your users engaged. When we see faces of other humans, it makes us feel like we are actually connecting with them, and not just using a product. However, many corporate sites are notorious for the overuse of insincere photography which is employed to “build trust.”
U32sability tests33 show that purelydecorative photos rarely add value to the design and more often harm than improve the user experience. Users usually overlook such images and might even get frustrated by them.
A very simple rule of thumb is to use high-quality photographs of people who match your app’s or website’s character. Imagery you use should be an authentic representation of your product, company or culture.
When using images in your design, ask yourself if the images you’re taking will match the aesthetic of your app or website. The homepage for Squarespace37 is a good example of focus on imagery. It’s elegant, clean and uses a huge amount of white space and large, full-screen images to create a bold design that grabs your attention.
The product image sells the item. Users often rely on the product image to assess product and it’s features. Regardless of your product, whether it be headphones or toys, product photography is the most important element of any e-commerce website. Ultimately, the more attractive your products look to visitors, the more confident they’ll feel about purchasing from you, and the better your conversion rate will be.
Make product images beautiful. A good image does all the hard work for you: It captures users’ attention and differentiates your product. However, good product photography requires work. Consider reading the article “Improve Your E-Commerce Design With Brilliant Product Photos43” on how to take quality photographs of your products.
Let products sell themselves. Consider the Gorilla Pod44 example shown below. The photo demonstrates brilliantly the benefits of the product.
Display the primary image above the fold. Don’t make users scroll in order to see the main image for a product. The main product image should be anchored in a prominent position in order to merchandise your priority offerings.
For the image to be effective it needs to be big — as big as you can make it. Then, when you’ve maxed out the size of your image, you need to integrate the ability to zoom in on specific details of the product. This is especially important for products like apparel where users are more concerned with detail. The zoomed-in images also need to maintain high quality.
Imagery can convey the essence of a product or service, but it can surprise and delight, too. Images have the capacity to entertain as much as to inform. If you already have a satisfying customer experience, adding delight to your product helps create an emotional connection with your users. Emotionally powerful imagery is a factor in ensuring that users continue to delve into your experience.
The emotional brain is affected by photos and illustrations, especially of people, and by stories. Speaking about illustrations, even sites or apps that don’t incorporate the drawn style throughout still can use cartoons for these purposes. For example, illustrations can be used for instructions, tutorials and empty states.
Landing pages are essentially your shop window and in most cases you have only one chance to impress the user. When it comes to landing pages, you want your visitors to have just one task on your page: to convert. Great landing pages are built around solid user experiences and imagery plays important role in building this experience. When users come to your page, they’ll have some kind of reaction. Whether it’s positive or negative, in large part, is determined by what they see.
Following the advice “show, don’t tell” and:
Below is an example of landing page by Intercom57, a service that allows you to communicate with your customers more efficiently. The landing page takes a reasonably complex idea and makes it easy to understand how the product works, and why it might be beneficial.
Thinking about images in terms of their usability is important. All visual communication in your design leaves a cumulative impression on the user.
Compelling images have a unique ability to inspire and engage your audience to provide useful information. Take the time to make every image in your app or site reinforce user experience.
This article is part of the UX design series sponsored by Adobe. The newly introduced Experience Design app63 is made for a fast and fluid UX design process, creating interactive navigation prototypes, as well as testing and sharing them — all in one place.
You can check out more inspiring projects created with Adobe XD on Behance64, and also visit the Adobe XD blog to stay updated and informed. Adobe XD is being updated with new features frequently, and since it’s in public Beta, you can download and test it for free65.
(ms, vf, yk, il)
Creating that singular piece of graphic design1 that users will first interact with each time they encounter your product can be intimidating. A beautiful, identifiable and memorable app icon can have a huge impact on the popularity and success of the app. But how exactly does one make a “good” app icon? What does that even mean? Fear not, I’ve put together some tips and advice to help answer these questions and to guide you on your way to designing great app icons.
I’ve been designing2, making resources3 and giving talks4 about icon design for the past couple of years. In this article, and in the video at the end, I’ll sum up what I’ve learned about this amazing craft.
The first things you need to understand when setting out to create an icon is what exactly an app icon is and what job it performs. An app icon is a visual anchor for your product. Think of it as a tiny piece of branding that not only needs to look attractive and stand out, but ideally also communicates the essence of your application.
The word “logo” is thrown around carelessly these days. App icons are not logos. While they certainly share branding-like qualities, they’re under a lot of different restrictions. It’s an important distinction for a designer to make: Logos are scalable vector pieces of branding designed for letterheads and billboards. Icons are most often raster-based outputs customized to look good within a square canvas, at specific sizes and in specific contexts. The approach, the tools, the job and, therefore, the criteria for success are different.
From a practical standpoint, when you’re making an app icon, you are creating a set of PNG files in multiple sizes — ranging from small sizes like 29 × 29 pixels all the way up to 1024 × 1024 pixels — that need to be bundled with your app. This set of carefully crafted designs will be used in the many contexts of the operating system where users will encounter your application — including the iOS App Store and Google Play, the settings panel, the search results and the home screen.
App icons can essentially be made in any application capable of producing raster files, but common choices are Photoshop, Illustrator and Sketch. Tools like the ones found on Apply Pixels211210 offer clever PSD templates that can help you get off the ground quickly.
Now, let’s look at some of the best practices in designing app icons. I’ll discuss each of my five core aspects of app icon design, give tips on how to improve each aspect and show off some examples of how I’ve worked with that quality. A lot of these examples will be based on my own work. That’s not because I feel like it is the best or only way to illustrate these things, but it has the added benefit of my knowing what thoughts went into the process. When going through the aspects, try to imagine icons that you like and how the individual aspects take shape in the icons on your home screen. Let’s get started.
One of the most important aspects of an icon is scalability. Because the icon is going to be shown in several places throughout the platform, and at several sizes, it’s important that your creation maintains its legibility and uniqueness. It needs to look good in the App Store, on “Retina” devices and even in the settings panel.
Overly complicated icons that cram too much into the canvas often fall victim to bad scalability. A very big part of the conceptual stage of app icon design should be dedicated to thinking about whether a given design will scale gracefully.
An app icon is like a little song, and being able to identify it easily amidst the noise of the store and the home screen is a key component in great icon design. Just as the verse of a song needs to resonate with the listener, so do the shapes, colors and ideas of an app icon. The design needs to instill a memory and sense of connection on both a functional and an emotional level.
Your icon will be vying for attention among thousands of other icons, all of which have the same 1024-pixel canvas to make their impact and secure their connection with the viewer. While scalability is a huge part of recognizability, so is novelty. The search for balance between these qualities is the very crux of the discipline.
There’s something to be said for creating consistency between the experience of interacting with an app icon and interacting with the app it represents. I feel like good icon design is an extension of what the app is all about. Making sure the two support each other will create a more memorable encounter.
Shaping a sleek, unified image of your app in the user’s mind increases product satisfaction, retention and virality. In short, getting your icon to work harmoniously with the essence, functionality and design of your application is a big win.
This almost goes without saying, but try to make something unique. Mimicking a style or a trend is perfectly fine, but make it your own. Your icon will be constantly competing with other icons for the user’s attention, and standing out can be a perfectly valid argument for a design.
Uniqueness is a tricky part of design, because it relies not only on your skill but also on the choices of others who are tackling a similar task.
This is one of my all-time top pet peeves. Only on the rarest of occasions is it OK to use words in an app icon. If you have to retreat to another tool of abstraction — the written word — then I’d say you’re not using the full force of your pictorial arsenal.
Words and pictures are separate representational tools, and mixing them in what is supposed to be a graphical representation usually leads to an experience that is cluttered, unfocused and harder to decode. Is there really no better way to visualize the application than with dry words? Whenever I see words in an app icon, I feel like the designer missed an opportunity to clearly convey their intention.
The App Store and Google Play have many examples of bland and unopinionated icon design. Your icon is the strongest connection you’ll have with the user. It is what they’ll see first when they meet you in the App Store. It’s what they’ll interact with every single time they use your app. It is what they’ll think of when they think of the app. Anything short of a well-thought-out, fitting and attractive solution will be a failure of your greatest visual asset. Your app icon should not be an afterthought; it should be a working part of the process.
App icons are tiny little pieces of concentrated design, and the qualities of good iconography are universal: scalability, recognizability, consistency and uniqueness. Mastering these will spill over to other areas of your design. Becoming an iconist will make you a better designer.
Whether they’re detailed or simple, conventional or creative, good icons have one unifying property: They all grab people’s attention in the same limited amount of space, on a completely level playing field. It’s a specific challenge, and the answer is always within that same canvas.
Crowning your application with a singular piece of graphic design is no doubt intimidating, but I hope the tips outlined above will make you more confident in taking on the challenge. Now go forth and make a fantastic app icon!
In this article, I’ve used icon tools available to subscribing members of Apply Pixels211210, but many icon tools are out there, both free and paid. Icon design is one of my great passions, and if you’re hungry for more, I’ve made several videos and given a lot of talks on the subject. Below are two that elaborate on the theories behind this article.
(da, yk, al, il)
I’ve been following the idea of algorithm-driven design for several years now and have collected some practical examples. The tools of the approach can help us to construct a UI, prepare assets and content, and personalize the user experience. The information, though, has always been scarce and hasn’t been systematic.
However, in 2016, the technological foundations of these tools became easily accessible, and the design community got interested in algorithms, neural networks and artificial intelligence (AI). Now is the time to rethink the modern role of the designer.
One of the most impressive promises of algorithm-driven design was given by the infamous CMS The Grid3. It chooses templates and content-presentation styles, and it retouches and crops photos — all by itself. Moreover, the system runs A/B tests to choose the most suitable pattern. However, the product is still in private beta, so we can judge it only by its publications and ads.
The Designer News community found real-world examples of websites created with The Grid, and they had a mixed reaction4 — people criticized the design and code quality. Many skeptics opened a champagne bottle on that day.
The idea to fully replace a designer with an algorithm sounds futuristic, but the whole point is wrong. Product designers help to translate a raw product idea into a well-thought-out user interface, with solid interaction principles and a sound information architecture and visual style, while helping a company to achieve its business goals and strengthen its brand.
Designers make a lot of big and small decisions; many of them are hardly described by clear processes. Moreover, incoming requirements are not 100% clear and consistent, so designers help product managers solve these collisions — making for a better product. It’s much more than about choosing a suitable template and filling it with content.
However, if we talk about creative collaboration, when designers work “in pair” with algorithms to solve product tasks, we see a lot of good examples and clear potential. It’s especially interesting how algorithms can improve our day-to-day work on websites and mobile apps.
Designers have learned to juggle many tools and skills to near perfection, and as a result, a new term emerged, “product designer7.” Product designers are proactive members of a product team; they understand how user research works, they can do interaction design and information architecture, they can create a visual style, enliven it with motion design, and make simple changes in the code for it. These people are invaluable to any product team.
However, balancing so many skills is hard — you can’t dedicate enough time to every aspect of product work. Of course, a recent boon of new design tools has shortened the time we need to create deliverables and has expanded our capabilities. However, it’s still not enough. There is still too much routine, and new responsibilities eat up all of the time we’ve saved. We need to automate and simplify our work processes even more. I see three key directions for this:
I’ll show you some examples and propose a new approach for this future work process.
Publishing tools such as Medium, Readymag and Squarespace have already simplified the author’s work — countless high-quality templates will give the author a pretty design without having to pay for a designer. There is an opportunity to make these templates smarter, so that the barrier to entry gets even lower.
For example, while The Grid is still in beta, a hugely successful website constructor, Wix, has started including algorithm-driven features. The company announced Advanced Design Intelligence8, which looks similar to The Grid’s semi-automated way of enabling non-professionals to create a website. Wix teaches the algorithm by feeding it many examples of high-quality modern websites. Moreover, it tries to make style suggestions relevant to the client’s industry. It’s not easy for non-professionals to choose a suitable template, and products like Wix and The Grid could serve as a design expert.