Join Our New Online Workshops On CSS, Accessibility, Performance, And UX
Rachel Andrew
It has been a month since we launched our first online workshop and, to be honest, we really didn’t know whether people would enjoy them — or if we would enjoy running them. It was an experiment, but one we are so glad we jumped into!
I spoke about the experience of taking my workshop online on a recent episode of the Smashing podcast. As a speaker, I had expected it to feel very much like I was presenting into the empty air, with no immediate feedback and expressions to work from. It turned out, however, that in some ways I felt more connected to the group by sharing my knowledge with a bunch of folks who were exactly in the same situation as me — at home trying to make the best of things!
I spoke to Vitaly about how he felt about leading a workshop online, and he also felt that everyone felt more equal, rather than having a teacher up on a podium. He also noted that we were able to see how global our audience is. Online workshops are more accessible to people who can’t travel even in normal times, and seeing everyone at home in their own environment really brought to life that we were working with folks from all around the world.
As a participant, there are definite advantages in attending a workshop from home. You have your own desk and usual setup, and space to spread out. And, given the workshop seating I’ve encountered in the past, most of you probably have a more comfortable chair. You can also rewatch the recording if you want to listen to an explanation again.
All The Details
As we have now formalized our workshop process, I thought I’d share a little more about how these events run. Unlike in-person workshops where we keep you in a room for an eight hour day, we realized than many of you have a lot of other commitments right now. Perhaps you are also homeschooling your children, trying to keep up with work from home, and it’s hard to be sat in front of a screen working all day. So all of our workshops are split over multiple shorter sessions, so check each workshop description for the full details of days and times. We’ve had great feedback from attendees and speakers about this approach.
In addition to the presenter, there is always at least one other member of the team online, to help with any connectivity or other issues that you might have.
With so many conferences around the world being cancelled, I’m grateful to have virtually attended a very informative @smashingconf workshop for the past couple days. Even from home, the learning never stops. pic.twitter.com/K3pbJBNpof
We’ve chosen Zoom because they turned out to be the best compromise in terms of quality, reliability, and accessibility. You can customize your Zoom setup that works best for you. You can resize the windows of the shared screen and the speaker. You can pop out the chat. Whatever works best for you!
We record each of the sessions for attendees, so if you do miss a session you can catch up later. Or you can go back to check out a detail you want to confirm.
Chat and connect
You can also join us in Slack, to chat during or after the workshop with fellow attendees.
We have also been using collaborative Google docs during the workshops for attendees to write down their own thoughts, resources, tips, and questions. It’s interesting to see people helping each other in those docs as well as posting questions for the Q&A.
A surprising thing for me about the online workshops was that I answered more questions than I do when I teach in-person. Each session has dedicated time for Q&A, and I did not expect to be able to fill 30 minutes, but could have easily been answering questions for longer.
Blown away by all the knowledge I’ve soaked in from @rachelandrew’s online CSS layout masterclass. Incredible how much you can learn about a thing you thought you already knew just by hearing it explained again. Thanks for organising this @smashingconf#smashingconf ✨
We have an amazing line-up of folk coming to share their wisdom in this online format. There really is something for everyone. Many of the previous workshops sold out, and we have a hard limit on each one, so if you want to be there, please grab a ticket now.
Join Miriam for a deep-dive into the heart of CSS: how it works, what makes it special, and how we can harness it for resilient and maintainable design systems that scale. This workshop is spread over six days, 2h each day, with a huge amount to learn and take away.
You’ll study 100s of hand-picked interface design examples in this one. We’ll be designing interfaces together, starting from accordions, to mega-drop-downs, sliders, feature comparisons, car configurators — all the way to timelines and onboarding. This workshop is spread over five days, 2.5h each day, with a huge amount to learn and take away.
Over the course of several sessions, we’ll take a journey from the back-end to the front, auditing the performance of real websites. We’ll be plotting data and findings, making it compelling to clients and non-technical stakeholders; drawing up backlogs and hit-lists; hacking on fixes, improvements, and experiments; and learning a whole lot of obscure performance details on the way.
Over two days learn the key skills you need to learn CSS Layout and put it into practice in your work. A mix of theory and pragmatic advice in terms of how to deal with browser support. This workshop is at an earlier Europe and Asia friendly time, so folk in those places don’t need to concentrate on Grid late at night.
By adopting an accessibility mindset and making improvements throughout the design and development process, we can truly make an impact in people’s lives. This workshop will cover how to approach accessibility with HTML, CSS, and JavaScript using modern tools and techniques to eliminate barriers to access in web projects.
Coding HTML emails is a beast of its own with lots of differences from coding web pages. This workshop will make you reconsider everything you know about coding HTML emails and hopefully make you love the craft.
Join Us!
We hope you’ll join us at one of the workshops. We’d also love to know which other workshop topics and speakers you would like to see online. Read more about the workshops and sign up here.
Mirage JS Deep Dive: Understanding Mirage JS Models And Associations (Part 1)
Kelvin Omereshone
Mirage JS is helping simplify modern front-end development by providing the ability for front-end engineers to craft applications without relying on an actual back-end service. In this article, I’ll be taking a framework-agnostic approach to show you Mirage JS models and associations. If you haven’t heard of Mirage JS, you can read my previous article in which I introduce it and also integrate it with the progressive framework Vue.js.
Note: These deep-dive series are framework agnostic, meaning that we would be looking at Mirage JS itself and not the integration into any front-end framework.
Models
Mirage JS borrowed some terms and concepts which are very much familiar to back-end developers, however, since the library would be used mostly by front-end teams, it’s appropriate to learn what these terms and concepts are. Let’s get started with what models are.
What Are Models?
Models are classes that define the properties of a particular data to be stored in a database. For example, if we have a user model, we would define the properties of a user for our application such as name, email, and so on. So each time we want to create a new user, we use the user model we’ve defined.
Creating models In Mirage JS
Though Mirage JS would allow you mockup data manually, using the Mirage Model class would give you a whole lot of awesome development experience because you’d have at your fingertips, data persistence.
Models wrap your database and allow you to create relationships that are really useful for returning different collections of data to your app.
Mirage uses an in-memory database to store entries you make using its model. Also without models, you won’t have access to associations which we would look at in a bit.
So, in order to create a model in Mirage JS first of, you have to import the Model class from Mirage JS like so:
import { Server, Model } from ‘miragejs’
Then, in our ‘Server’ options we use it as the following:
let server = new Server({
models: {
user: Model,
}
Note: If you don’t know what Mirage JS server is, it’s how Mirage JS intercepts network requests. I explained it in detail in my previous article.
From the above, you could see we are creating a user model instance. Doing so allows us to persist entries for the said model.
Creating Entries
To create new entries for our user model, you need to use the schema class like so:
let user = schema.users.create({name: “Harry Potter”})
Note: Mirage JS automatically pluralize your models to form the schema. You could also see we are not explicitly describing before-hand, the properties the user model instance would have. This approach makes for the rapid creation of entries and flexibility in adding fields for the said entries.
You’d most likely be creating instances of your model in the seeds() method of your server instance, so in that scenario you’d create a new user instance using the create() method of the server object like so:
let server = new Server({
models: {
user: Model
},
seeds(server) {
server.create("user", { name: "Harry Potter" });
});
In the above code, I redundantly added the snippet for both the server and model creation as to establish some context.
You could access the properties or fields of a model instance using dot notation. So if we want to create a new user instance for the user model, then use this:
let user = schema.users.create({name: “Hermione Granger”})
We can also access the name of the user simply by using the following:
user.name
// Hermione Granger
Furthermore, if the instance created has a relationship called ‘posts’ for example, we can access that by using:
user.posts
// Returns all posts belonging to the user
Finding An Instance
Let’s say you’ve created three instances of the user model and you want to find the first one you could simply use the schema on that model like so:
let firstUser = schema.users.find(1)
// Returns the first user
More Model Instance Properties
Mirage exposes a couple of useful properties on model instances. Let’s take a closer look at them.
associations
You could get the associations of a particular instance by using the associations property.
let harry = schema.users.create({name: “Harry Potter”})
user.associations
// would return associations of this instance if any
According to the Mirage JS docs, the above would return a hash of relationships belonging to that instance.
attrs
We can also get all the fields or attributes of a particular instance by using the attrs property of a model instance like so:
harry.attrs
// { name: “Harry Potter” }
Methods
destroy()
This method removes the instances it is called on from Mirage JS database.
harry.destroy()
isNew()
This method returns true if the model has not been persisted yet to the database. Using the create method of the schema would always save an instance to Mirage JS database so isNew() would always return false. However, if you use the new method to create a new instance and you haven’t called save() on it, isNew() would return true.
Here’s an example:
let ron = schema.users.new({name: “Ronald Weasley”})
ron.isNew()
// true
ron.save()
ron.isNew()
// false
isSaved()
This is quite the opposite of the isNew() method. You could use it to check if an instance has been saved into the database. It returns true if the instance has been saved or false if it hasn’t been saved.
reload()
This method reloads an instance from Mirage Js database. Note it works only if that instance has been saved in the database. It’s useful to get the actual attributes in the database and their values if you have previously changed any. For example:
This method does what it says which is, it saves or creates a new record in the database. You’d only need to use it if you created an instance without using the create() method. Let’s see it in action.
let headmaster = schema.users.new({name: “Albus Dumbledore”})
headmaster.id
// null
headmaster.save()
headmaster.name = “Severus Snape”
// Database has not yet been updated to reflect the new name
headmaster.save()
// database has been updated
headmaster.name
// Severus Snape
toString()
This method returns a simple string representation of the model and id of that particular instance. Using our above headmaster instance of the user model, when we call:
headmaster.toString()
We get:
// “model:user:1”
update()
This method updates a particular instance in the database. An example would be:
let headmaster = schema.users.find(1)
headmaster.update(“name”, “Rubeus Harris”)
Note: The update() takes two arguments. The first is the key which is a string and the second argument is the new value you want to update it as.
Associations
Since we’ve now been well acquainted with Models and how we use them in Mirage JS, let’s look at it’s counterpart — associations.
Associations are simply relationships between your models. The relationship could be one-to-one or one-to-many.
Associations are very common in backend development they are powerful for getting a model and its related models, for example, let’s say we want a user and all his posts, associations are utilized in such scenarios. Let’s see how we set that up in Mirage JS.
Once you’ve defined your models, you can create relationships between them using Mirage JS association helpers
Mirage has the following associations helpers
hasMany()
use for defining to-many relationships
belongsTo()
use for defining to-one relationships
When you use either of the above helpers, Mirage JS injects some useful properties and methods in the model instance. Let’s look at a typical scenario of posts, authors, and comments. It could be inferred that a particular author can have more than one blog post also, a particular post can have comments associated with it. So let’s see how we can mock out these relationships with Mirage JS association helpers:
belongsTo()
Import Helpers
First import the belongsTo
import { Server, Model, belongsTo } from 'miragejs'
Next we create our models and use the extend method to add our relationships like so
The above defines a to-one relationship from the post model to an author model. Doing so, the belongsTo helper adds several properties and methods to the affected models. Hence we can now do the following:
post.authorId // returns the author id of the post
post.author // Author instance
post.author = anotherAuthor
post.newAuthor(attrs) // creates a new author without saving to database
post.createAuthor(attrs) // creates a new author and save to database
hasMany()
This helper like its belongsTo counterpart needs to be imported from Mirage JS before use, so:
import { Server, Model, hasMany } from 'miragejs'
Then we can go on creating our to-many relationships:
Like belongsTo(), hasMany() helper also adds several properties and method automatically to the affected models:
post.commentIds // [1, 2, 3]
post.commentIds = [2, 3] // updates the relationship
post.comments // array of related comments
post.comments = [comment1, comment2] // updates the relationship
post.newComment(attrs) // new unsaved comment
post.createComment(attrs) // new saved comment (comment.postId is set)
The above snippet is adapted from the well written Mirage JS documentation
Conclusion
Mirage JS is set out to make mocking our back-end a breeze in modern front-end development. In this first part of the series, we looked at models and associations/relationships and how to utilize them in Mirage JS. Stay tuned for the next parts!
For the most part, we tend to underestimate things that are familiar to us. It is also very likely that we will underestimate those things that though new, seem very simple to process. And that is correct to some degree. But, when we are faced with complex cases and all measures are taken, a good and solid understanding of the basics could help us to find the right solutions.
In this article, we will take a deeper look at one of the most simple, thus, quite often underrated activities in web development that is the design of wireframes. We will find out what are wireframes, why we need to design them, how to get the most out of the wireframes design, and how to take it to the next level.
According to The Top 20 Reasons Startups Fail report by CB Insights, 17% of startups reported lack of user-friendliness as the reason for their failure. Designing a user-friendly interface is not a trivial task, especially for large and complex products where there are many entities, dependencies, and elements to be organized. To design such complex products you should follow a top-down approach and wireframes design is the best technique that could help you with that.
First, Let’s Define Terms
Wireframe — also known as a page schematic or screen blueprint, and it is a visual guide that represents the skeletal framework of a website or an application.
Additional definition we will look at is wireframing — a process of designing a wireframe, and commonly used to lay out content and functionality on a page which takes into account user needs and user journeys. Wireframes are used early in the development process to establish the basic structure of a page before visual design and content is added.
At first glance, wireframing seems simple. And herein lies the major problem: that we tend not to pay enough attention to simple things. One way to help us get the most benefits from wireframing is to define the goals of the product or service.
The main goal of wireframing, that we could get is to show the team and stakeholders which entities, pages, and components the application is going to have and how these elements of the digital product will interact with each other.
From the goal definition we can see how big the impact of wireframing is for both the development process and the final product.
When we keep in mind the goals of the wireframing process, we still need to pay attention to what are the common pitfalls to avoid during wireframes design.
Wireframing Mistakes We Want To Avoid
Creating wireframes for the sake of ‘box-checking’;
Skipping wireframes stage at all;
Preparing wireframes after the visual designs;
Not understanding why to use wireframes.
Wireframes should precede the stage of visual design, not vice versa. It’s like deciding on the technology stack for your application after having the code written.
Wireframe design lays the foundation for the quality of the design, and the better we understand the goal of this phase the more benefits we could get. So let’s dive deeper and find out why we need to design wireframes and what values this technique brings.
Businesses that lack knowledge of the product design may welcome the practice of skipping wireframe design as it allows them to cut the project costs, but this decision may lead to potential failure in the long run. And you, as the designer, should explain why we are doing it, how it will help the final product, and how it could even save future expenses.
Next, let’s check some points that could help you better understand why we need wireframes and see how wireframes help to get feedback from your developers, clients and future users of your product.
Why You Should Design Wireframes
Help Your Team Estimate And Refine The Scope Of Work
Wireframes allow designers to quickly create a visual representation of the future product and demonstrate it to the team for the needed revisions. Wireframes also help you show your team which screens the application is going to have, which elements and controls will be on each screen, and how all the elements will interact with each other. In addition, looking through wireframes is way faster than reading specifications. Also, it helps us avoid discrepancies in scope between the initial estimates and the final ones.
Involve All Team Members In The Product Design Stage
We all have been in the position of having created a top-notch design, only to be faced with development constraints. The use of wireframes allows us to involve developers in discussing designs at the early stages, enabling them to provide feedback and suggest changes before you start working on visual design. This way, you can speed up the design process and avoid wasting time and money.
Hold A Demo For Clients
Getting rapid feedback from your clients and stakeholders is an important component of the design process. Also, we all have experienced multiple change-requests from our stakeholders and that is normal. With wireframes, we could make this process more efficient. Making changes to prototypes requires more time and effort than making changes to wireframes, it will let you be more agile and don’t waste extra time on rework.
Carry Out User Testing
According to Eric Ries, author of Lean Startup, the sooner you carry out user testing, the better — no one wants to roll out an application and find out that users don’t get how to use it right. Wireframes can help designers get valuable feedback from potential users and don’t spend time on developing complex interactive prototypes when they are not needed.
The fact that UI/UX designers use wireframes does not necessarily mean that they do it right. For that, you should remember and follow the best practices.
Wireframing Best Practices
To bring the best results and serve a solid foundation for further UI, you need to follow several simple rules:
1. Minimize The Use Of Color In Wireframes
If you’re using rich color palettes in your wireframes, remind yourself of the goal of wireframing (to show which elements the product is going to have, and how they should interact with each other) and think if extra colors help you to achieve it.
Minimize the use of color in wireframes, we will have dedicated phase for this. (Large preview)
In some cases, they could. But in general, adding colors to your wireframes might distract the viewer’s attention and will surely make any updates more difficult. Moreover, there’s another important issue to consider — not all clients have a good understanding of UX techniques and might take colored wireframes for final designs.
Example of correct color using in wireframes. (Large preview)
However, this doesn’t mean you should never use color on wireframes and strictly stick with the black and white palette. Sometimes using color to highlight specific components is justified. For example, you can use red for error states or blue for notes, etc.
2. Use Simple Design Of Components
When you add components to your wireframes, go for basic designs. Wireframes aren’t intended to contain thoroughly designed and detailed components. Instead, they should be easily recognized by your team members and stakeholders. Adding detailed components will cost you a lot of time and effort without being particularly useful.
Use simple design of components and make its functional purpose clear. (Large preview)
3. Maintain Consistency
Similar components must look the same on all your wireframes. If the same components look different, developers are likely to question if they are actually the same and even add extra time to the estimates because of different designs. When working on wireframes, remember a simple rule: be consistent and try not to create confusion.
Maintain consistency between similar components and avoid using same look for different components. (Large preview)
4. Use Real Content
From time to time we could see that UI/UX designers don’t add real content on the wireframes and use lorem ipsum instead. That’s a common mistake that few designers even realize they make. You may object and say that the content isn’t available at the stage of design. Well, it’s enough to use the draft version of the content.
Use real content instead of lorem ipsum. (Large preview)
Content impacts the design you’ll create, and the draft content will help you make the right decisions and deliver the superb design. If you use lorem ipsum, however, you won’t see the full picture and will likely need to make a lot of adjustments to the UI or even worse — you will create a design that doesn’t work. Also, the real content will add value to your wireframes, explain the context better and maybe indicate that you need to start gathering the real content already.
5. Use Annotations
It may happen that some design solutions can’t be visually illustrated, so stakeholders or developers might have questions about them. For example, the logic behind some controls. In such cases, you can provide on-screen annotations to explain the logic behind that. This way, your team will understand your solutions, and you won’t need to spend time discussing them.
Use annotations to describe specific logic. (Large preview)
6. Low To High-Fidelity
There’s no strict rule. Sometimes you should go for low-fidelity wireframes, while some projects might require high-fidelity ones. It depends on the project, so if you feel like adding more details to the wireframes — don’t hesitate to do it. But according to Eric Ries, don’t do extra work when this doesn’t bring value, start from the basics and then add details as long as they are needed. For instance, if you need to draw the developers’ attention to some custom solution, then add more details to illustrate it in your wireframes.
Both low and high-fidelity wireframes have a place to be. (Large preview)
7. Extend Wireframes To Prototypes
As designers we work with different products, some of them have simple and common interactions, and some of them have quite advanced ones. Sometimes wireframes are not enough to illustrate the interaction of complex and uncommon interfaces, but instead of writing long notes and spending hours on explanations, you could extend your wireframes to interactive prototypes.
Developing interactive prototype now easier than ever. (Large preview)
The good news is that today we have a wide range of simple but very powerful tools like Figma, Invision, Adobe XD, UXPin, Axure, Moqups, etc. and we definitely need to review them and choose the best tool for designing the wireframes and developing simple prototypes.
Wireframe Design Tools
Now it’s time to choose a superb wireframing tool that will help you create amazing designs and streamline your workflow. There are a lot of different options you can use for wireframing, and you might have used some of them before. I’d like to give you a basic understanding of how different they are.
Most wireframe tools are tailored to:
Simplicity
They have a low barrier to entry and are perfect for people who take their first steps in UI/UX design and lack experience using more sophisticated software.
Collaboration
These are packed with a rich functionality for teamwork. Collaboration is a backbone of modern software development, so the best wireframing tools not only provide lots of features but allow for efficient and easy collaboration between all team members involved in the design process.
Here are the most widely used wireframe tools tailored to collaboration:
Figma
A powerful cloud-based tool that comes in a web version and desktop applications for Windows and macOS. Figma comes with a lot of powerful features for building wireframes, prototypes, UIs, and more (see table below).
Sketch
This tool is extremely popular with UI/UX designers. If you need to go beyond the default Sketch toolset, you can use dozens of plugins to get extra functions. Unlike many of its competitors, Sketch is available only on macOS and you will need a 3rd-party solution for collaboration.
There are plenty of applications you can use to design wireframes. You shouldn’t make a choice based solely on the features provided in the application. Instead, I would advise you to try and explore all of them and decide which works for you best. Below you can find a list of some of the most popular tools to start with.
Advanced features to create and edit vector images
Large community
Can be used to design wireframes if you don’t have any other option
Paid
Not dedicated to design UI
No web-based app
No prototyping features
No collaboration features
Overwhelmed UI
As an example of the power of modern design tools, I’d like to share my own experience and show you how we set up an effective wireframing design process with one of the tools above.
Case Study: How We Set Up A Wireframing Process Across Multiple Teams
Context
The company I worked at was building complex fintech digital products. Apart from the design team, there was a professional team of business analysts (BAs). They prepared the requirements and created low-fidelity wireframes that they passed on to our design team.
Picking The Tool
We needed to choose an all-in-one tool for the BA and design teams. Since most business analysts have fairly low design skills, we wanted to find a tool that would be simple enough for BAs and — at the same time — powerful enough for designers. Also, easy collaboration was our team’s priority. Based on these criteria, we opted for Figma.
Creating The Library Of Components
To streamline the product design process, we created a custom library of components that the BA team could use. This allowed us to speed up the wireframing, as the business analysts could quickly use ready-made blocks instead of drawing their own.
Training The Team
To show how to use Figma and the library of components, we held a workshop for our BA team. We also found it important to teach them some extra features, such as prototyping.
Diagram of relations between teams in wireframing design process. (Large preview)
Result
In our case Figma proved to be efficient for wireframing and collaboration, even though the team members were located in Ukraine, Australia, and the Philippines. We currently use Figma for the communication channel — it proved to be more convenient to collaborate on the wireframes by mail or in messengers.
Summing Up
Being a simple practice, wireframes design usually doesn’t get enough awareness from us, designers, when we face them for the first time.
As a result, lack of attention for this technique leads to a number of flaws, when we either add a lot of decoration to wireframes, or create low-fi wireframes for the sake of box-checking when the project rather requires a more detailed solution, or even skip this stage and go straight to visual UI design.
Usually, all of these mistakes are the result of poor understanding of both wireframes design objectives (that is to show which elements the product is going to have, and how they should interact with each other), as well as poor understanding of when wireframes could help us, like:
Wireframes could help the team to get more precise estimates of the project.
Wireframes could help to involve all team members to design processes and avoid engineering mistakes that will affect the development process.
Wireframes could help us to make early presentations to clients, stakeholders and conduct user testing sessions to get feedback as soon as possible, and save time on the development of poor solutions.
Today, as designers, we are lucky as never before because there are dozens of tools available for us to design wireframes and also smoothly integrate this activity in our general design process.
The only thing that we need to do is to spend some time to incorporate both the technique and tools in our own design process, and find a way how to make them work for us to take our product design process to the next level. That they certainly can.
Implementing Dark Mode In React Apps Using styled-components
Blessing Krofegha
One of the most commonly requested software features is dark mode (or night mode, as others call it). We see dark mode in the apps that we use every day. From mobile to web apps, dark mode has become vital for companies that want to take care of their users’ eyes.
Dark mode is a supplemental feature that displays mostly dark surfaces in the UI. Most major companies (such as YouTube, Twitter, and Netflix) have adopted dark mode in their mobile and web apps.
While we won’t go in depth into React and styled-components, a basic knowledge of React, CSS, and styled-components would come in handy. This tutorial will benefit those who are looking to enhance their web applications by catering to those who love dark mode.
StackOverflow announces dark mode on Twitter (Large preview)
A few days before the writing of this article, StackOverflow announced its release of dark mode, giving users the chance to toggle between the two modes.
Dark mode reduces eye strain and helps when you’re working for a long time on a computer or mobile phone.
What Is Dark Mode?
Dark mode is the color scheme of any interface that displays light text and interface elements on a dark background, which makes the screen a little easier to look at mobile phones, tablets, and computers. Dark mode reduces the light emitted by the screen, while maintaining the minimum color-contrast ratios required for readability.
Why Should You Care About Dark Mode?
Dark mode enhances visual ergonomics by reducing eye strain, adjusting the screen to current light conditions, and providing ease of use at night or in dark environments.
Before implementing dark mode in our app, let’s look at its benefits.
Battery Saving
Dark mode in web and mobile apps can prolong the battery life of a device. Google has confirmed that dark mode on OLED screens has been a huge help to battery life.
For example, at 50% brightness, dark mode in the YouTube app saves about 15% more screen energy than a flat white background. At 100% screen brightness, the dark interface saves a whopping 60% of screen energy.
Dark Mode Is Beautiful
Dark mode is beautiful, and it can significantly enhance the appeal of the screen.
While most products are going for that similar bland white look, dark mode offers something different that feels mysterious and new.
It also provides great opportunities to present graphic content such as dashboards, pictures, and photos in a fresh way.
The beauty of Twitter’s dark mode over light mode (Large preview)
Now that you know why you should implement dark mode in your next web app, let’s dive deep into styled-components, which is the defining resource of this tutorial.
Throughout this article, we will be using the styled-components library very often. There have always been many ways to style a modern web app. There’s the traditional method of styling at the document level, which includes creating an index.css file and linking it to the HTML or styling inside the HTML file.
A lot has changed in the ways that web apps are styled recently, since the introduction of CSS-in-JS.
CSS-in-JS refers to a pattern in which CSS is composed using JavaScript. It utilizes tagged template literals to style components in a JavaScript file.
styled-components is a CSS-in-JS library lets you use all of the features of CSS that you love, including media queries, pseudo-selectors, and nesting.
Why styled-components?
styled-components was created for the following reasons:
No class name hell
Instead of you scratching your head to find a class name for an element, styled-components generates unique class names for your styles. You’ll never have to worry about misspellings or using class names that have no meaning.
Using props
styled-components allow us to extend styling properties using the props parameter, commonly used in React — thus, dynamically affecting the feel of a component via the application’s state.
Supports Sass syntax
Writing Sass syntax out of the box without having to set up any preprocessors or extra build tools is possible with styled-components. In your style definitions, you can use the & character to target the current component, use pseudo-selectors, and experiment with nesting.
Theming
styled-components have full theming support by exporting a ThemeProvider wrapper component. This component provides a theme to all React components within itself via the Context API. In the rendering tree, all styled-components will have access to the provided theme, even when they are multiple levels deep. As we continue in this tutorial, we will look deeper into the theming features of styled-components.
Here, we’ve defined and exported lightTheme and darkTheme objects with distinct color variables. Feel free to experiment and customize the variables to suit you.
globalStyles Component
Remaining in your components folder, create a globalStyles.js file, and add the following code:
We’ve imported createGlobalStyle from styled-components. The createGlobalStyle method replaces the now deprecated injectGlobal method from styled-components version 3. This method generates a React component, which, when added to your component tree, will inject global styles into the document, in our case, App.js.
We defined a GlobalStyle component and assigned background and color properties to values from the theme object. Thus, every time we switch the toggle, the values will change depending on the dark theme or light theme objects that we are passing to ThemeProvider (which will be created later, as we proceed).
The transition property of 0.50s enables this change to occur a little more smoothly, so that as we toggle back and forth, we can see the changes happen.
Creating Theme-Toggling Functionality
To implement the theme-toggling functionality, we need to add only a few lines of code. In the App.js file, add the following code (note that the highlighted code is what you should add):
The highlighted code is the one newly added to App.js. We’ve imported ThemeProvider from styled-components. ThemeProvider is a helper component in the styled-components library that provides theming support. This helper component injects a theme into all React component below itself via the Context API.
In the rendering tree, all styled-components will have access to the provided theme, even when they are multiple levels deep. Check out the section on “Theming”.
Next, we import the GlobalStyle wrapper from ./components/Globalstyle. Lastly, from the top, we import both the lightTheme and darkTheme objects from ./components/Themes.
In order for us to create a toggling method, we need a state that holds our theme’s initial color value. So, we create a theme state, and set the initial state to light, using the useState hook.
Now, for the toggling functionality.
The themeToggler method uses a ternary operator to check the state of the theme, and it toggles either dark or light based on the value of the condition.
ThemeProvider, a styled-components helper component, wraps everything in the return statement and injects any components below it. Remember that our GlobalStyles inject global styles into our components; hence, it’s called inside the ThemeProvider wrapper component.
Lastly, we created a button with an onClick event that assigns our themeToggler method to it.
Let’s see the outcome thus far.
Dark mode implemented without persistence (Large preview)
Our App.js file needs to be refactored; a lot of its code is not DRY. (DRY stands for “don’t repeat yourself”, a basic principle of software development aimed at reducing repetition.) All of the logic seems to be in App.js; it’s good practice to separate our logic for the sake of clarity. So, we’ll create a component that handles the toggling functionality.
Toggle Component
Still within the components folder, create a Toggler.js file, and add the following code to it:
To keep things neat, we’ve styled our toggle button in the Toggle component, using the styled function from styled-components.
This is purely for presentation; you can style the button as you see fit.
Inside the Toggle component, we pass two props:
the theme provides the current theme (light or dark);
the toggleTheme function will be used to switch between themes.
Next, we return the Button component and assign a toggleTheme function to the onClick event.
Lastly, we use propTypes to define our types, ensuring that our theme is a string and isRequired, while our toggleTheme is func and isRequired.
Using Custom Hooks (useDarkMode)
When building an application, scalability is paramount, meaning that our business logic must be reusable, so that we can use it in many places and even in different projects.
That is why it would be great to move our toggling functionality to a separate component. For that, we would create our own custom hook.
Let’s create a new file named useDarkMode.js in the components folder, and move our logic to this file, with some tweaks. Add the following code to the file:
setMode We use localStorage to persist between sessions in the browser. So, if a user has chosen the dark or light theme, that’s what they’ll get upon their next visit to the app or if they reload the page. Hence, this function sets our state and passes theme to localStorage.
themeToggler This function uses a ternary operator to check the state of the theme and toggles either dark or light based on the truth of the condition.
useEffect We’ve implemented the useEffect hook to check on component mounting. If the user has previously selected a theme, we will pass it to our setTheme function. In the end, we will return our theme, which contains the chosen theme and the themeToggler function to switch between modes.
I think you’ll agree that our dark-mode component looks sleek.
First, we import our custom hook, destructure the theme and themeToggler props, and set it with the useDarkMode function.
Note that the useDarkMode method replaces our theme state, which was initially in App.js.
We declare a themeMode variable, which renders either a light or dark theme based on the condition of the theme mode at the time.
Now, our ThemeProvider wrapper component is assigned our just recently created themeMode variable to the theme prop.
And lastly, in place of the regular button, we pass in the Toggle component.
Remember that in our Toggle component, we defined and styled a button and passed both theme and toggleTheme to them as props. So, all we have to do is pass these props appropriately to the Toggle component, which will act as our button in App.js.
Yes! Our dark mode is set, and it persists, not changing color when the page is refreshed or visited in a new tab.
Let’s see the outcome in action:
Dark mode implemented, but with a glitch in the button color when the browser reloads. (Large preview)
Almost everything works well, but there is one small thing we can do to make our experience splendid. Switch to the dark theme and then reload the page. Do you see that the blue color in the button loads before the gray for a brief moment? That happens because our useState hook initiates the light theme initially. After that, useEffect runs, checks localStorage, and only then sets the theme to dark. Let’s jump over to our custom hook useDarkMode.js and add a little code:
The highlighted code is the only one added to useDarkMode.js. We’ve created another state named mountedComponent and set the default value to false using the useState hook. Next, inside the useEffect hook, we set the mountedComponent state to true using setMountedComponent. Lastly, in the return array, we include the mountedComponent state.
Finally, let’s add a bit of code in App.js to make it all work.
We’ve added our mountedComponent state as a prop in our useDarkMode hook, and we’ve checked whether our component has mounted, because this is what happens in the useEffect hook. If it hasn’t happened yet, then we will render an empty div.
Now, you’ll notice that while in dark mode, when the page reloads, the button’s color doesn’t change.
Conclusion
Dark mode is increasingly becoming a user preference, and implementing it in a React web app is a lot easier when using the ThemeProvider theming wrapper in styled-components. Go ahead and experiment with styled-components as you implement dark mode; you could add icons instead of a button.
Please do share your feedback and experience with the theming feature in styled-components in the comments section below. I’d love to see what you come up with!