I’m very glad a guide for these features exists because we already know there are so many weird things that variable fonts can do — well done, Tunghsiao Liu!
I got a great email from a fellow named Josh Long the other day. He is, in his words, “relatively new to web design” and was a bit stuck on the concept of getting a site live. I should say that I’m happy to get emails like this an I always read them, but I typically can’t offer tech support over email. If I can respond at all, I normally point people to other community resources.
In this case, it struck me what a perfect moment this is for Josh. He’s a little confused, but he knows enough to be asking a lot of questions and sorting through all this stuff. I figured this was a wonderful opportunity to dig into his questions, hopefully helping him and just maybe helping others in a similar situation.
Here’s one of the original paragraphs Daniel sent me, completely unedited:
I’m relatively new to web design, but I’ve taken a few courses on HTML and CSS and I’ve done a Codecademy course on JavaScript. But, (jumping forward probably quite a while here!) after having fully designed and coded a HTML/CSS/JS website or webpage, I don’t fully understand the full process of going from a local site hosted with mamp/wamp to publishing a public site using wordpress(?) or some other host (is WordPress even a host?!) and also finding a server that’s suitable and some way of hosting images/videos or other content. (If that sounded like I didn’t know what half of those meant, it’s because unfortunately I don’t!.. but I’d really like to!)
Can you sense that enthusiasm? I love it.
We worked together a bit to refine many of his questions for this post, but they are still Josh’s words. Here we go!
What is a Domain Registrar? I get they are for registering domain names, but what’s the difference between them? How do you know which one is right for you? A quick search for “best domain hosts” on Google gave me 5 ads for companies who are domain registrars/hosts and 9 “Top 10” style pages that look as though they have some sort of affiliation with at least one company they’re suggesting. Am I just looking for the cheapest one?
You’re exactly right, domain registrants are for registering domain names. If you want joshlongisverycool.com, you’re going to have to buy it, and domain registrants are companies that help you do that.
It’s a bummer that web search results are so saturated by ads and affiliate-link-saturated SEO-jacked pages. You’ll never get total honesty from that because those pages are full of links promoting whoever will pay them the most to send new customers. Heck, even Google themselves will sell you a domain name.
The truth is that you can’t go too wrong here. Domain names are a bit of a commodity and the hundreds of companies that will sell you one largely compete on marketing.
Some things to watch out for:
Some companies treat the domain as a loss leader. Like a grocery store selling cheap milk to hope you buy some more stuff while you are there. The check out process at any domain registrant will almost certainly try to sell you a bunch of extra stuff. For example, they might try to sell you additional domain names or email hosting you probably don’t need. Just be careful.
Web hosts (which we’re getting to next) will often sell them to you along with hosting. That’s fine I suppose, but I consider it a bit of a conflict of interest. Say you choose to move hosts one day. That hosting company is incentivized in the wrong direction to make that easy for you. If the domain is handled elsewhere, that domain registrant is incentivized the right direction to help you make changes.
I hate to add to the noise for you, but here are some domain registrants that I’ve used personally and aren’t paying for sponsorship here nor are affiliate links:
Our own Sarah Drasner recommends looking at ZEIT domains, which are super interesting in that you buy and manage them entirely over the command line.
I might suggest, if you can see yourself owning several domain names in your life, keeping them consolidated to a single registrant. Managing domains isn’t something you’ll do very often, so it’s easy to lose track of what domains you registered on what registrant, not to mention how/where to change all the settings across different registrants.
I’d also suggest it’s OK to experiment here. That’s how all of us have learned. Pick one with a UI that you don’t hate and a trustable vibe. Maybe your friend also uses that one. Hopefully, it works out fine. If you hate it, it’ll be a little work, but you can always switch.
What is a web host and why do I need one? A Google search throws you a mountain of “best web host” articles and ads. These websites all seem to be full of jargon like “shared hosting” and “managed hosting.” I see things like “suggested hosts” on some sites. How do you find the right web host? I’m not even sure what my needs are. Should I just find the cheapest one?
Just because you own a domain doesn’t mean it will do anything. In fact, right after you buy it, it’s likely that the domain registrant slaps up a “coming soon” page for you:
To host a website at your new domain, you’ll need to configure the DNS of your new domain to point at a server connected to the internet. Here’s an interesting tidbit… you could do this right from your house if you wanted to. Your Internet Service Provider (ISP) at home probably gives you an IP address. It’s all a bit nerdy, but you could point your domain name at that IP and set up your computer to be a web server that responds to incoming requests and serves up your website. Almost nobody does that though. You don’t want your web server to stop working because you closed your laptop or your ISP changed your IP.
Web hosting services give you that server. Like domain registrants, web hosts are almost a commodity. There are lots of them that provide a similar service, so the price is driven fairly low and they find other things to compete on.
Buying web hosting is a little trickier than buying a domain though. You’ll need to know a little bit about the website you intend to host when making that choice. Will it be a WordPress site? Or another PHP/MySQL-based CMS site (we’ll get to those later)? That means your host will need to support those technologies. Most do, some don’t. You might wanna look in their documentation or literally ask them before pulling the trigger if you are unsure. There are lots of technologies for running websites. Say the site will use Ruby on Rails — that’s a different set of requirements that not all hosts offer. Or Node… Or Python… same story.
If a web host says they specialize in a particular set of technologies, and that’s what you need, that’s probably a decent way to go, particularly in your early days. Let’s take a very limited gander. Again, these are not affiliate or paid-for links and they are somewhat randomly selected web hosts that come to mind for me:
WP Engine is a web host that focuses specifically on WordPress.
BlueHost is has very inexpensive hosting choices, but has essentially the same capabilities as those others.
Now here some other web hosts that are a little less traditional. Forgive the techy terms here — if they don’t mean anything to you, just ignore them.
Netlify does static site hosting, which is great for things like static site generators and JAMstack sites.
Zeit is a host where you interact with it only through the command line.
Digital Ocean has their own way of talking about hosting. They call their servers Droplets, which are kind of like virtual machines with extra features.
Heroku is great for hosting apps with a ready-to-use backend for things like Node, Ruby, Java, and Python.
Again, I’d say it’s OK to, in a sense, make some mistakes here. If you aren’t hosting something particularly mission-critical, like your personal website, pick a host that seems to fit the bill and give it a go. Hopefully, it works out great — if not, you can move. Moving isn’t always super fun, but everybody ends up doing it, and you’ll learn as you go.
When you buy web hosting, that host is going to tell you how to use it. One common way is that the host will give you SFTP credentials. Then you’ll use software that is built for connecting to servers over SFTP, and that will allow you to upload files to the web server.
This is a magic moment!
Say you’ve been working on a file that is a single index.html file, that loads a style.css file. Upload those files over SFTP into the folder your host tells you is correct public root directory for that site.
That is the process for taking a site from local to live! There is nothing wrong with it, either. This is referred to as deployment, and this is about as basic and simple as it gets. Even fancier ways of doing deployment sometimes ultimately do just this behind the scenes. We’ll get more into deployment later.
Should you bundle your domain registrar and web host into one if a company offers both?
I mentioned this above a little: I’m a fan of not doing that in general. On one hand, it’s mighty handy. Things like shared billing and a single checkout flow. The host will also do things like configuring the DNS for you to be all set up for their hosting and you probably don’t even have to think about it.
But say the day comes where you just don’t like that host anymore. You’ve found a better deal, outgrown them, were turned off by their support or service, or any other reason. You want to move hosts. The problem is that they aren’t just your host, but your domain registrant, too. Do you leave the domain with them and just move hosts? Probably not, you’re trying to leave them. Now you need to move two things. That makes that move all the more perilous, but worse, this company isn’t exactly incentivized to respond quickly and helpfully to your requests since they know they’re losing you as a customer.
What really is a “CMS”? What’s its purpose? WordPress, Joomla, and Drupal are the most popular names I’ve found for content management systems, and from their descriptions, all sound very similar. What are the kinds of features that set one apart from another? Is a CMS all you need to get your website from your local computer to the public internet?
CMS (Content Management System) is a pretty generic term. It means literally anything that helps you manage content. There are, as you’ve seen, some big players like WordPress and CraftCMS. They don’t really have anything directly to do with that connection between working locally on a site and getting that site live. But they do rather complicate it.
The reason that you’d use a CMS at all is to make working on your site easier. Consider this site you’re looking at right now. There are tens of thousands of pages on this site. It would be untenable for each of them to be a hand-authored file.html file.
Instead, a CMS allows us to craft all those pages by combining data and templates.
Let’s consider the technology behind WordPress, a CMS that works pretty good for CSS-Tricks. In order for WordPress to run, it needs:
PHP (the back-end language)
MySQL (the database)
Apache (the web server)
You can do all that locally!
I use Local by Flywheel for that (Mac and Windows), but there are a number of ways to get those technologies running: MAMP, Docker, Vagrant, etc.
That’ll get you up and running locally with your CMS. That’s a beautiful thing. Having a good local development environment for your site is crucial. But it doesn’t help you get that site live.
We’ll go over getting all this to a live site a bit later, but you should know this: the web server that you run will need to run these same technologies. Looking at the WordPress example from above, your web server will also need to run PHP, MySQL, and Apache. Then you’ll need to set up a system for getting all your files from your local computer to your web server like you would for any other site, but also probably have a system for dealing with the database. The database is a bit tricky as it’s not a “flat file” like most of the rest of your site.
A CMS could be built from any set of technologies, not just the ones listed above. For example, see KeystoneJS. Instead of PHP, Keystone is Node.js. Instead of MySQL for the database, it uses MongoDB. Instead of Apache, it uses Express. Just a different set of technologies. Both of which you can get running locally and on a live web server.
A CMS could even have no database at all! Static site generators are like this. You get the site running locally, and they produce a set of flat files which you move to your live server. A different way to do things, but absolutely still a CMS. What I always say is that the best CMS is one that is customized to your needs.
What is “Asset Hosting”? Are assets not content? What is the difference between a CMS and an asset hosting service? What does an asset host do?
Let’s define an asset: any “flat” file. As in, not dynamically generated like how a CMS might generate an HTML file. Images are a prime example of an “asset.” But it’s also things like CSS and JavaScript files, as well as videos and PDFs.
And before we get any further: you probably don’t need to worry about this right away. Your web host can host assets and that’s plenty fine for your early days on small sites.
One major reason people go with an asset host (probably more commonly referred to as a CDN or Content Delivery Network) is for a speed boost. Asset hosts are also servers, just like your web host’s web server, but they are designed for hosting those flat file assets super fast. So, not only do those assets get delivered to people looking at your site super fast, but your web server is relieved of that burden.
You could even think of something like YouTube as an asset host. That 100 MB video of a butterfly in your garden is a heavy load for your little web server, and potentially a problem if your outgoing bandwidth is capped like it often is. Uploading that video to YouTube puts your video into that whole social universe, but a big reason to do it other than the social stuff is that it’s hosting that video asset for you.
I’ve heard of “repositories”, but don’t really get what they are. I hear stuff like “just upload it to my Git Repository.” What the heck does that mean? I feel like a moron for asking this. What is Git? What is it for? Do I need to use it? Is it involved in the “local to live” process at all?
Sorry you got hit with a “just” there. There is an epidemic in technology conversations where people slip that in to make it seem like what they are about to say is easy and obvious when it could be neither depending on who is reading.
But let’s get into talking about Git repositories.
Git is a specific form of version control. There are others, but Git is so dominant in the web industry that it’s hardly worth mentioning any others.
Let’s say you and me are working on a website together. We’ve purchased a domain and hosting and gotten the site live. We’ve shared the SFTP credentials so we both have access to change the files on the live site. This could be quite dangerous!
Say we both edit a file and upload it over SFTP… which change wins? It’s whoever uploaded their file last. We have no idea who has done what. We’re over-writing each other and have no way of staying in sync with each other’s changes, changes immediately affect the live site which could break things and we have no way of undoing changes in case we break things. That’s a situation so unacceptable that it’s really never done.
Instead, we work with version control, like Git. With Git, when we make changes, we commit them to a repository. A repository can be hosted anywhere, even on your local machine. But to make them really useful, they are hosted on the internet somewhere everyone has access to. You’ve surely seen GitHub, which hosts these repositories and adds a bunch of other features like issue tracking. Similar are GitLab and Bitbucket.
Now let’s say you and me are working on that same site, but we’ve set up a Git repository for it. When I make a change, I commit it to the repository. If you want to make a change as well, you have to pull my changes down which merges them into your own copy of the code. Then you can push your changes up to the repository. Like anything, it gets more complicated, but that’s the gist of it.
But a Git repository isn’t the live website. You’re on your own for getting the files from a Git repository to a live site. Fortunately, that’s a situation that everyone faces, so there are lots of options. Good thing your last question is about this!
OK. So now with all that straight… where do you start with going from local to live? Where do you “upload” your HTML, CSS and JavaScript files? How do you link your shiny new domain name to those files and see it out in the wild? Which service is in charge of adding new content to your site, or updating it? Does it get really confusing if you have different companies for each service?
Let’s start with a very simple website on what I’d consider typical web hosting. Say you have just index.html, style.css, and script.js files on your local computer which is your entire website. You’ve purchased a domain name and pointed the DNS settings at a web host. That host has given you SFTP credentials. You’ll use those credentials in an app that allows SFTP connections to log in:
Your host will also tell you which folder is the “public root” of your website. The files there will be out on the public internet for the world to see!
You might hear people refer to the “live” website as a “production” site. When someone asks something like, Did that bug make it to production?” they mean whether the bug is on the live website. “Development” is your local computer. You might also have a “Staging” site, which is a clone of the live website on the same hardware/software of the live site for testing.
Remember earlier when we talked about Git repositories? While the repositories themselves don’t directly help you get the files in them to your web server, most systems that help you with the local-to-live process work with your repositories.
The phrase “local-to-live” refers to deployment. When you have changes that you want to go out to your production website, you deploy them. That’s the process of moving your work from “development” to “production.”
One service that helps with this idea of deployment is Beanstalk. Beanstalk hosts your Git repository, plus you give it the SFTP credentials for your server — that way it can move the files to your web server when you make commits. Cool right? Say you wanted to host that Git repo elsewhere though, like GitHub, Bitbucket, or Gitlab. Check out DeployBoy, which does the same thing, only it can connect to those sites as well. It probably comes as no surprise by now that there are lots of options here, ranging in price and complexity.
Let’s go back to our WordPress example.
You’ve got it running locally (on your computer) just perfectly and now want to go live with it.
You’ve bought a domain name from a registrar.
You’ve purchased hosting that meets WordPress requirements.
You’ve pointed the DNS of the domain name at the web host.
You’ve verified it’s all working (easy way: upload an index.html file in the public root via SFTP and verify that it loads when you type you type the domain name into a browser.)
Now, you’ve still got some work to do:
Set up a Git repository for the site.
Set up a deployment service to move the files from the repository to the live site.
Configure/Set up the live site as needed. For example, you’ll need a database on the live site. You’ll have to create that (your host will have instructions) and do things like run the WordPress installer and update configuration files.
If you have things in your local database that you want to move to the live site, you might be exporting/importing things. That can happen at the raw MySQL level using WordPress’ native import/export features, or a fancy plugin like WP DB Migrate Pro.
It’s a non-trivial amount of work! Sorry. This process is pretty similar for any site though. It’s a matter of configuring and setting up your production web server exactly how it should be, then deploying files to it. Every site is a bit different, but you’ll get the hang of this whole dance.
It really is a big dance. I’ve only painted one picture for you here. I tried to pick one that was generic and broad enough that it shows the landscape of what needs to be done. But, at every step in this dance, there are different ways you can do things, different services you can pick, companies trying to help you at different pain points… it’s an ever-changing world.
Right now, Netlify is enjoying a lot of popularity because they are one of the only web hosts that actually helps you with deployment. They’ll watch your Git repositories and do deployment for you! Netlify is for static sites only, but that can be a whole world onto itself. ZEIT also is massively innovative in how it helps with deploying and hosting web projects, including directly connecting with GitHub.
Good luck!
I hope this was helpful. Remember, you aren’t alone in all this. Zillions of other developers have done this before you and there is help to be found on the internet.
Oh, and remember: the best way to learn anything at all is to…
Tired of slow, unreliable web hosting? See how easy it is to self-host your next project on DigitalOcean’s cloud platform. Build and manage ultra-fast websites, blogs, and other static web pages using our user-friendly control panel or simple API, all with a 99.99% uptime SLA. Save time using our One-Click install apps for WordPress, Ghost and Discourse. Never worry about running out of storage space again with Spaces — highly scalable, affordable object storage.
Sign up today with a free $100 credit for CSS-Tricks readers.
Ever spent an hour (or even a day) working on something just to throw the whole lot away and redo it in five minutes? That isn’t just a beginner’s code mistake; it is a real-world situation that you can easily find yourself in especially if the problem you’re trying to solve isn’t well understood to begin with.
This is why I’m such a big proponent of upfront design, user research, and creating often multiple prototypes — also known as the old adage of “You don’t know what you don’t know.” At the same time, it is very easy to look at something someone else has made, which may have taken them quite a lot of time, and think it is extremely easy because you have the benefit of hindsight by seeing a finished product.
This idea that simple is easy was summed up nicely by Jen Simmons while speaking about CSS Grid and Piet Mondrian’s paintings:
“I feel like these paintings, you know, if you look at them with the sense of like ‘Why’s that important? I could have done that.’ It’s like, well yeah, you could paint that today because we’re so used to this kind of thinking, but would you have painted this when everything around you was Victorian — when everything around you was this other style?”
I feel this sums up the feeling I have about seeing websites and design systems that make complete sense; it’s almost as if the fact they make sense means they were easy to make. Of course, it is usually the opposite; writing the code is the simple bit, but it’s the thinking and process that goes into it that takes the most effort.
With that in mind, I’m going to explore building a text box, in an exaggeration of situations many of us often find ourselves in. Hopefully, by the end of this article, we can all feel more emphatic to how the journey from start to finish is rarely linear.
A Comprehensive Guide To User Testing
So you think you’ve designed something that’s perfect, but your test tells you otherwise. Let’s explore the importance of user testing. ?
Brief
We all know that careful planning and understanding of the user need is important to a successful project of any size. We also all know that all too often we feel to need to rush to quickly design and develop new features. That can often mean our common sense and best practices are forgotten as we slog away to quickly get onto the next task on the everlasting to-do list. Rinse and repeat.
Today our task is to build a text box. Simple enough, it needs to allow a user to type in some text. In fact, it is so simple that we leave the task to last because there is so much other important stuff to do. Then, just before we pack up to go home, we smirk and write:
<input type="text">
There we go!
Oh wait, we probably need to hook that up to send data to the backend when the form is submitted, like so:
<input type="text" name="our_textbox">
That’s better. Done. Time to go home.
How Do You Add A New Line?
The issue with using a simple text box is it is pretty useless if you want to type a lot of text. For a name or title it works fine, but quite often a user will type more text than you expect. Trust me when I say if you leave a textbox for long enough without strict validation, someone will paste the entire of War and Peace. In many cases, this can be prevented by having a maximum amount of characters.
In this situation though, we have found out that our laziness (or bad prioritization) of leaving it to the last minute meant we didn’t consider the real requirements. We just wanted to do another task on that everlasting to-do list and get home. This text box needs to be reusable; examples of its usage include as a content entry box, a Twitter-style note box, and a user feedback box. In all of those cases, the user is likely to type a lot of text, and a basic text box would just scroll sideways. Sometimes that may be okay, but generally, that’s an awful experience.
Thankfully for us, that simple mistake doesn’t take long to fix:
<textarea name="our_textbox"></textarea>
Now, let’s take a moment to consider that line. A : as simple as it can get without removing the name. Isn’t it interesting, or is it just my pedantic mind that we need to use a completely different element to add a new line? It isn’t a type of input, or an attribute used to add multi-line to an input. Also, the element is not self-closing but an input is? Strange.
This “moment to consider” sent me time traveling back to October 1993, trawling through the depths of the www-talk mailing list. There was clearly much discussion about the future of the web and what “HTML+” should contain. This was 1993 and they were discussing ideas such as which wasn’t available until HTML5, and Jim Davis said:
“Well, it’s far-fetched I suppose, but you might use HTML forms as part of a game playing interface.”
“Makes the browser code cleaner — they have to be handled differently internally.”
That’s a fair reason to have separate to text, but that’s still not what we ended up with. So why is its own element?
I didn’t find any decision in the mailing list archives, but by the following month, the HTML+ Discussion Document had the element and a note saying:
“In the initial design for forms, multi-line text fields were supported by the INPUT element with TYPE=TEXT. Unfortunately, this causes problems for fields with long text values as SGML limits the length of attributea literals. The HTML+ DTD allows for up to 1024 characters (the SGML default is only 240 characters!)”
Ah, so that’s why the text goes within the element and cannot be self-closing; they were not able to use an attribute for long text. In 1994, the element was included, along with many others from HTML+ such as in the HTML 2 spec.
Okay, that’s enough. I could easily explore the archives further but back to the task.
Styling A
So we’ve got a default . If you rarely use them or haven’t seen the browser defaults in a long time, then you may be surprised. A (made almost purely for multi-line text) looks very similar to a normal text input except most browser defaults style the border darker, the box slightly larger, and there are lines in the bottom right. Those lines are the resize handle; they aren’t actually part of the spec so browsers all handle (pun absolutely intended) it in their own way. That generally means that the resize handle cannot be restyled, though you can disable resizing by setting resize: none to the . It is possible to create a custom handle or use browser specific pseudo elements such as ::-webkit-resizer.
It’s important to understand the defaults, especially because of the resizing ability. It’s a very unique behavior; the user is able to drag to change the size of the element by default. If you don’t override the minimum and maximum sizes then the size could be as small as 9px × 9px (when I checked Chrome) or as large as they have patience to drag it. That’s something that could cause mayhem with the rest of the site’s layout if it’s not considered. Imagine a grid where is in one column and a blue box is in another; the size of the blue box is purely decided by the size of the .
Other than that, we can approach styling a much the same as any other input. Want to change the grey around the edge into thick green dashes? Sure here you go: border: 5px dashed green;. Want to restyle the focus in which a lot of browsers have a slightly blurred box shadow? Change the outline — responsibly though, you know, that’s important for accessibility. You can even add a background image to your if that interests you (I can think of a few ideas that would have been popular when skeuomorphic design was more celebrated).
Scope Creep
We’ve all experienced scope creep in our work, whether it is a client that doesn’t think the final version matches their idea or you just try to squeeze in a tiny tweak and end up taking forever to finish it. So I ( enjoying creating the persona of an exaggerated project manager telling us what we need to build) have decided that our just is not good enough. Yes, it is now multi-line, and that’s great, and yes it even ‘pops’ a bit more with its new styling. Yet, it just doesn’t fit the very vague user need that I’ve pretty much just thought of now after we thought we were almost done.
What happens if the user puts in thousands of words? Or drags the resize handle so far it breaks the layout? It needs to be reusable, as we have already mentioned, but in some of the situations (such as a ‘Twittereqsue’ note taking box), we will need a limit. So the next task is to add a character limit. The user needs to be able to see how many characters they have left.
In the same way we started with instead of , it is very easy to think that adding the maxlength attribute would solve our issue. That is one way to limit the amount of characters the user types, it uses the browser’s built-in validation, but it is not able to display how many characters are left.
We started with the HTML, then added the CSS, now it is time for some JavaScript. As we’ve seen, charging along like a bull in a china shop without stopping to consider the right approaches can really slow us down in the long run. Especially in situations where there is a large refactor required to change it. So let’s think about this counter; it needs to update as the user types, so we need to trigger an event when the user types. It then needs to check if the amount of text is already at the maximum length.
So which event handler should we choose?
change
Intuitively, it may make sense to choose the change event. It works on and does what it says on the tin. Except, it only triggers when the element loses focus so it wouldn’t update while typing.
keypress
The keypress event is triggered when typing any character, which is a good start. But it does not trigger when characters are deleted, so the counter wouldn’t update after pressing backspace. It also doesn’t trigger after a copy/paste.
keyup
This one gets quite close, it is triggered whenever a key has been pressed (including the backspace button). So it does trigger when deleting characters, but still not after a copy/paste.
input
This is the one we want. This triggers whenever a character is added, deleted or pasted.
This is another good example of how using our intuition just isn’t enough sometimes. There are so many quirks (especially in JavaScript!) that are all important to consider before getting started. So the code to add a counter that updates needs to update a counter (which we’ve done with a span that has a class called counter) by adding an input event handler to the . The maximum amount of characters is set in a variable called maxLength and added to the HTML, so if the value is changed it is changed in only one place.
var textEl = document.querySelector('textarea')
var counterEl = document.querySelector('.counter')
var maxLength = 200
textEl.setAttribute('maxlength', maxLength)
textEl.addEventListener('input', (val) => {
var count = textEl.value.length
counterEl.innerHTML = ${count}/${maxLength}
})
Browser Compatibility And Progressive Enhancement
Progressive enhancement is a mindset in which we understand that we have no control over what the user exactly sees on their screen, and instead, we try to guide the browser. Responsive Web Design is a good example, where we build a website that adjusts to suit the content on the particular size viewport without manually setting what each size would look like. It means that on the one hand, we strongly care that a website works across all browsers and devices, but on the other hand, we don’t care that they look exactly the same.
Currently, we are missing a trick. We haven’t set a sensible default for the counter. The default is currently “0/200” if 200 were the maximum length; this kind of makes sense but has two downsides. The first, it doesn’t really make sense at first glance. You need to start typing before it is obvious the 0 updates as you type. The other downside is that the 0 updates as you type, meaning if the JavaScript event doesn’t trigger properly (maybe the script did not download correctly or uses JavaScript that an old browser doesn’t support such as the double arrow in the code above) then it won’t do anything. A better way would be to think carefully beforehand. How would we go about making it useful when it is both working and when it isn’t?
In this case, we could make the default text be “200 character limit.” This would mean that without any JavaScript at all, the user would always see the character limit but it just wouldn’t feedback about how close they are to the limit. However, when the JavaScript is working, it would update as they type and could say “200 characters remaining” instead. It is a very subtle change but means that although two users could get different experiences, neither are getting an experience that feels broken.
Another default that we could set is the maxlength on the element itself rather than afterwards with JavaScript. Without doing this, the baseline version (the one without JS) would be able to type past the limit.
User Testing
It’s all very well testing on various browsers and thinking about the various permutations of how devices could serve the website in a different way, but are users able to use it?
Generally speaking, no. I’m consistently shocked by user testing; people never use a site how you expect them to. This means that user testing is crucial.
It’s quite hard to simulate a user test session in an article, so for the purposes of this article, I’m going to just focus on one point that I’ve seen users struggle with on various projects.
The user is happily writing away, gets to 0 characters remaining, and then gets stuck. They forget what they were writing, or they don’t notice that it had stopped typing.
This happens because there is nothing telling the user that something has changed; if they are typing away without paying much attention, then they can hit the maximum length without noticing. This is a frustrating experience.
One way to solve this issue is to allow overtyping, so the maximum length still counts for it to be valid when submitted but it allows the user to type as much as they want and then edit it before submission. This is a good solution as it gives the control back to the user.
Okay, so how do we implement overtyping? Instead of jumping into the code, let’s step through in theory. maxlength doesn’t allow overtyping, it just stops allowing input once it hits the limit. So we need to remove maxlength and write a JS equivalent. We can use the input event handler as we did before, as we know that works on paste, etc. So in that event, the handler would check if the user has typed more than the limit, and if so, the counter text could change to say “10 characters too many.” The baseline version (without the JS) would no longer have a limit at all, so a useful middle ground could be to add the maxlength to the element in the HTML and remove the attribute using JavaScript.
That way, the user would see that they are over the limit without being cut off while typing. There would still need to be validation to make sure it isn’t submitted, but that is worth the extra small bit of work to make the user experience far better.
Designing The Overtype
This gets us to quite a solid position: the user is now able to use any device and get a decent experience. If they type too much it is not going to cut them off; instead, it will just allow it and encourage them to edit it down.
There’s a variety of ways this could be designed differently, so let’s look at how Twitter handles it:
Twitter has been iterating its main tweet since they started the company. The current version uses a lot of techniques that we could consider using.
As you type on Twitter, there is a circle that completes once you get to the character limit of 280. Interestingly, it doesn’t say how many characters are available until you are 20 characters away from the limit. At that point, the incomplete circle turns orange. Once you have 0 characters remaining, it turns red. After the 0 characters, the countdown goes negative; it doesn’t appear to have a limit on how far you can overtype (I tried as far as 4,000 characters remaining) but the tweet button is disabled while overtyping.
So this works the same way as our does, with the main difference being the characters represented by a circle that updates and shows the number of characters remaining after 260 characters. We could implement this by removing the text and replacing it with an SVG circle.
The other thing that Twitter does is add a red background behind the overtyped text. This makes it completely obvious that the user is going to need to edit or remove some of the text to publish the tweet. It is a really nice part of the design. So how would we implement that? We would start again from the beginning.
You remember the part where we realized that a basic input text box would not give us multiline? And that a maxlength attribute would not give us the ability to overtype? This is one of those cases. As far as I know, there is nothing in CSS that gives us the ability to style parts of the text inside a . This is the point where some people would suggest web components, as what we would need is a pretend . We would need some kind of element — probably a div — with contenteditable on it and in JS we would need to wrap the overtyped text in a span that is styled with CSS.
What would the baseline non-JS version look like then? Well, it wouldn’t work at all because while contenteditable will work without JS, we would have no way to actually do anything with it. So we would need to have a by default and remove that if JS is available. We would also need to do a lot of accessibility testing because while we can trust a to be accessible relying on browser features is a much safer bet than building your own components. How does Twitter handle it? You may have seen it; if you are on a train and your JavaScript doesn’t load while going into a tunnel then you get chucked into a decade-old legacy version of Twitter where there is no character limit at all.
What happens then if you tweet over the character limit? Twitter reloads the page with an error message saying “Your Tweet was over the character limit. You’ll have to be more clever.” No, Twitter. You need to be more clever.
Retro
The only way to conclude this dramatization is a retrospective. What went well? What did we learn? What would we do differently next time or what would we change completely?
We started very simple with a basic textbox; in some ways, this is good because it can be all too easy to overcomplicate things from the beginning and an MVP approach is good. However, as time went on, we realized how important it is to have some critical thinking and to consider what we are doing. We should have known a basic textbox wouldn’t be enough and that a way of setting a maximum length would be useful. It is even possible that if we have conducted or sat in on user research sessions in the past that we could have anticipated the need to allow overtyping. As for the browser compatibility and user experiences across devices, considering progressive enhancement from the beginning would have caught most of those potential issues.
So one change we could make is to be much more proactive about the thinking process instead of jumping straight into the task, thinking that the code is easy when actually the code is the least important part.
On a similar vein to that, we had the “scope creep” of maxlength, and while we could possibly have anticipated that, we would rather not have any scope creep at all. So everybody involved from the beginning would be very useful, as a diverse multidisciplinary approach to even small tasks like this can seriously reduce the time it takes to figure out and fix all the unexpected tweaks.
Back To The Real World
Okay, so I can get quite deep into this made-up project, but I think it demonstrates well how complicated the most seemingly simple tasks can be. Being user-focussed, having a progressive enhancement mindset, and thinking things through from the beginning can have a real impact on both the speed and quality of delivery. And I didn’t even mention testing!
I went into some detail about the history of the and which event listeners to use, some of this can seem overkill, but I find it fascinating to gain a real understanding of the subtleties of the web, and it can often help demystify issues we will face in the future.
Businesses have more options today in terms of how they get their goods and services out to customers: strictly brick-and-mortar, 100% online, or a combination of the two. Because of this, web design isn’t necessarily as straightforward as we’d like it to be.
Of course, there will always be certain best practices to abide by—abundant white space; responsive and mobile-first design; easy-to-find CTAs; streamlined user flow; consistent design and messaging site-wide.
But big-picture items aside, what exactly do you need to know about designing for local businesses, global enterprises, and those that attempt to span both categories? Let’s take a look at the differences in designing for audiences based on location.
Local vs. Global Web Design, What You Need to Know
Below are 4 parts of the web design and development experience that drastically differ between local and global-serving websites. Keep these in mind as you work on future projects:
1. Web Hosting
I won’t get too much into the specifics of web hosting; however, I do think it’s important to understand that some web hosting solutions just aren’t ideal for global web design. This isn’t just because of server resources and how much traffic the server, in turn, is able to handle. There’s also the matter of distance.
Think about it like this: You designed a website that’s hosted from a server in Denver, Colorado. The website sells hats. There’s nothing really tying the hat design to a regional audience, and so you’ve targeted the web design to a global audience. And, for whatever reason, people in Australia love it. However, the web hosting provider doesn’t have a web server anywhere near Australia. They are solely based in the U.S. With already limited resources in the hosting plan, the high amounts of traffic from Australia severely compromise the speed of the website (which happens when transmitting lots of website files, images, scripts, and so on around the world).
So, what do you do? Simply hope the web design and selection of hat offerings are enough to make visitors look past the sluggish loading times? Nope. If you’re serving a global audience, you need to 1) use a web hosting company with servers around the world, 2) choose a web hosting plan capable of handling that kind of traffic, and 3) equip your site with a CDN.
2. Colors
Something like color contrast is a pretty basic consideration for any website you design. But have you ever thought about the underlying meaning behind the colors you’ve chosen? And have you put those in the context of your audience’s perception?
For instance, let’s look at the color red. Red is an incredibly powerful color. It’s also very memorable. Ask any consumer what Target‘s logo is and they’re bound to tell you it’s a red bullseye. Not just a bullseye, but a red one.
But red can have more somber associations in other countries. In South Africa and China, red is used for mourning and funerals, respectively. If you have a growing audience in those parts of the world, that color choice might not make sense despite its more positive connotations in other parts of the world.
Conversely, you can think about how color plays into local design. If you’re trying to form a strong attachment with an audience, say in Chicago, you could use colors reminiscent of one of the more beloved sports teams there. Or design it in a way that it makes them think of a local landmark or event.
3. Images
As with color, you have to be careful about what kind of message you send with images on your website.
For a local audience, you’d be best off using recognizable images from your base of operations as well as of your team; not some cold, generic stock photography. Take Phase 3, as an example.
The background video that plays on the home page looks pretty darn authentic, giving visitors a glimpse behind the scenes of what the agency does. Scroll down further on the page and you’ll see that they’ve added images representative of each office location:
These small touches give a local audience a greater connection to a brand.
Now, with a global company, it may be okay to use premium stock photography, though you might be better off relying on strong swatches of colors (gradients are huge right now anyway).
4. Copy
I would suggest that, when it comes to copy, there are a few rules to stick by for global visitors:
Keep the copy on your site short.
Avoid any colloquial speak or jargon.
Use long-tail keywords that focus on your service or product.
If you sell anything on the site, include easy options for choosing other countries, languages, or currencies.
If you have a contact form, streamline the process of filling it out by providing easy-to-use dropdowns to select country codes, populate addresses, etc.
Mention partnerships, awards, and other associations that bring to mind a global presence (e.g. Google certifications, Amazon integration, major enterprise partners, etc.)
Majux is an example of a Philadelphia-based company that targets a nationwide audience:
As you can see, there’s no mention of “Philadelphia” in the copy, the metadata doesn’t include a hint of it, and they’re even using an 800-number instead of a local area code.
To write copy for a local audience, use these tips instead:
Craft copy that’s longer and feels more personalized.
Use colloquial speak the local audience will recognize and quickly warm to.
Use long-tail keywords that include mention of the physical location.
Reference the location in the copy, too.
If you have a corresponding brick-and-mortar location, create a juxtaposition between the two experiences. Encourage customers to use the site to check prices, let them know they can order online and pick up in store, etc. Make it clear that you want it to be a seamless experience between digital and in-house.
Mention partnerships, awards, and other associations with well-known local companies.
Small Talk Media is a media agency that touts its Philadelphia roots:
As you can tell from this highlight reel, their Philly location plays a big part in the brand’s identity:
Wrapping Up
In order to design a website well, you need to translate the users’ needs and expectations into an experience. And the key to having success with that experience is to target the right audience in the first place. But targeting a local user set versus one scattered around the region, country, or world requires different considerations.
In general, it’s best to play it safe and buttoned-up when designing for a global audience. That way, you run less of a risk of isolating any one group of people and you give off the impression that your brand is much larger and capable of handling anyone’s needs. And, for local design, have fun with it. Show off your local pride and give visitors a way to more deeply connect with your brand.
You know, that panel of tools that allows you to do stuff like inspect the DOM and see network requests. How do the companies that make them refer to them?
I think it’s somewhat safe to generically refer to them as DevTools. Safari is the only browser that doesn’t use that term, but I imagine even die-hard Safari users will know what you mean.
There are few fonts out there that really strike me as bold. Sometimes, you have a certain project, company, or product that is deserving of nothing less than bold. Well, for all of you out there that need such boldness in your life, I have good news.. I have found what you’ve been looking for.
Introducing: Bison Font
Bison is a powerful font created by Ellen Luff. Inspired by the animal, the Bison Font is bold, sturdy, and uncompromising. The straight and controlled lines combined with the smooth curves very much remind me of a bison. It says, “I’m in charge” and “I won’t be moved.”
What does it come with?
Like any great font package, you get a few goodies when you purchase it. The Bison Font package includes 10 all caps fonts: 4 weights, 6 italics, and 2 outlines. In addition, you’ll also get:
Bison Bold – Commanding and structured
Bison Demibold – Persuasive and middleweight
Bison regular – The perfect balance between light and bold
Bison Light – Soft but strong
Bison Outline – The best of both worlds: thick and thin
When to use Bison
Because of the wide range of weights, Bison works well in many applications. Mostly, I imagine it to be used in branding, logos, magazines, and maybe even a few films. It’s the perfect font for grabbing someones attention and getting your point across quickly.
One of the really cool things about Bison is that it supports a few other languages. Bison supports all Central and Western European, Vietnamese, and even some African languages.
Other work by Ellen
Ellen Luff has been hard at work for a while to create some of the best fonts out there. When you take a look at her work on Creative Market, you’ll quickly realize the shear amount of talent we’re dealing with. Here are some of the other amazing fonts you’ll find:
If you like Ellen’s work, and want to see more or get some for your own, be sure to check out her page and other works. And, if you like content like this and want to stay up to date with the design world, check out more from Webdesignledger.
Wall Street Journal design director Matthew Ström on something near and dear to me: the link between code and design tools:
We’re in the middle of a design tool renaissance. In the 8 years since Sketch 1.0 was released, there’s been a wave of competition among traditional design tools. And as the number of tools available to designers grows exponentially, ideas that were once considered fringe are finding a broader audience.
One of these ideas will significantly change the way digital products are designed: integrating design and code at a deep level. Figma can update a React code base in real time; InVision, Abstract, and Zeplin have done away with design-developer handoff documents; Framer’s new Framer X can render interactive React components directly into its workspace. These examples are just a hint of what’s to come.
Matthew then looks at how this combination of code and design has been improving his own design process, specifically on the “story cards” that appear on the homepage of the WSJ:
A tiny bit of NodeJS fills in the cards with live data from the WSJ.com home page. I can make small changes to parts of the component and see how the system reacts in a matter of seconds. This multiplicative process means that small changes have a huge output, making my designs much more comprehensive in the process.
I really can’t wait to see how our design tools are evolve. It’s a thoroughly exciting time to be a designer that’s interested in code.
Flutter is a mobile SDK that, at its core, is about empowering everyone to build beautiful mobile apps. Whether you come from the world of web development or native mobile development, Flutter makes it easier to create mobile apps in a familiar, simplified way, without ever giving up control to the framework.
As of this writing, Google AdWords and Alibaba are both using Flutter in production. You can see more examples of who’s using Flutter (including the app I’ve worked on) on Flutter’s website on the showcase page.
Right now, there’s a lot of buzz about Flutter. The question I see most often is, “Flutter or React Native…which one should I use?” Like all things in programming, its all about the tradeoffs you’re willing to make.
I’m going to try to convince you that Flutter is the best option for mobile app development. I believe it’s better than any other cross platform framework, and it’s possibly better than native development — but more on that in a bit.
Before that though, let me walk (quickly) through what Flutter is, and what it is not, starting with the Dart programming language.
What’s Dart?
Dart is a programming language created by Google and was used to write Flutter. Dart was created, more or less, because Google wanted a language that was “better” than JavaScript to write server side and front-end code. From what I understand, the main issue they had with JavaScript is how slowly it updates with new features since it relies on a huge committee for approvals and several browser vendors to implement it.
After a series of decisions about whether to take on JavaScript directly or not, Google decided to make a language that semantically fit inside of JavaScript. In other words, every single thing you write in Dart can compile to JavaScript. This is why they didn’t just use Java — it’s semantically huge.
The fundamentals of Dart are similar to all high-level languages. That said, programming languages are, as it turns out, hard to learn.
There’s good news, though. Dart excels at being a “safe” language to learn. Google didn’t set out to create anything innovative with Dart. They were seeking to make a language that was simple, productive and could be compiled into JavaScript.
There is nothing particularly exciting about its syntax, and no special operators that will throw you through a loop. In Dart (unlike JavaScript), there is one way to say true: True. There is one way to say false: False.
In JavaScript, this coerces to True:
if (3) { ... }
In Dart, that would blow up your program. Dart is, at its core, a productive, predictable, and simple language.
This is important, because writing an app in Flutter is simply writing Dart. Flutter is, underneath it all, a library of Dart classes. There is no markup language involved or JSX-style hybrid language. Every bit of front-end code is written in Dart. No HTML. No CSS.
Why does Flutter use Dart?
If you’re coming from literally any other background (and you’re like me), you’ve probably complained about the fact that Flutter uses Dart, and not JavaScript. (Developers are, believe it or not, opinionated.)
And there are reasons to be skeptical of this choice. It’s not one of the hot languages of today. It’s not even one of the top 25 most used languages. What gives? Is Google just using it because it’s their language? I’d imagine that played a role, but there are practical reasons, too.
Dart supports both Just In Time (JIT) compiling and Ahead of Time (AOT) compiling.
The AOT compiler changes Dart into efficient native code. This makes Flutter fast (a win for the user and the developer), but it also means that almost all of the framework is written in Dart. For you, the developer, that means you customize everything.
Dart’s optional JIT compiling allows hot-reloading to exist. Fast development and iteration is a key to the joy of using Flutter. When you save code in your text editor, your app is updated in your simulator in less than a second.
Dart is Object Oriented. This makes it easy to write visual user-experiences exclusively with Dart, with no need for a markup language.
Dart is a productive and predictable language. It’s easy to learn and it feels familiar. Whether you come from a dynamic language or a static language, you can get up and running with ease.
And yes, I’d image that it is extremely appealing to use a language made by the same company, because the Flutter team could work closely with the Dart team to implement new needed features.
Flutter vs. React Native (and other options)
Before I offer up my unsolicited opinions on your other options, I want to make this crystal clear: Flutter is not the answer 100% of the time. It’s a tool and we should choose the right tool for the job at hand. That said, I’d only argue that it’s something you should strongly consider in the future.
Native development (iOS and Android)
Your first choice is to write native apps for iOS and Android. This gives you maximum control, debugging tools, and (potentially) a very performant app. At a company, this likely means you have to write everything twice; once for each platform. You likely need different developers on different teams with different skillsets that can’t easily help each other.
React Native, WebViews, and other cross-platform JavaScript options
Your second option: cross-platform, JavaScript-based tools such as WebViews and React Native. These aren’t bad options. The problems you experience with native development disappear. Every front-end web developer on your team can chip in and help — all they need are some modern JavaScript skills. This is precisely why large companies such as AirBnb, Facebook, and Twitter have used React Native on core products. (AirBnb recently announced that it would stop using React Native, because of some of the issues I’ll describe below.)
The first “mobile apps” to be built cross platform are simply WebViews that run on WebKit (a browser rendering engine). These are literally just embedded web pages. The problem with this is basically that manipulating the DOM is very expensive and doesn’t perform well enough to make a great mobile experience.
Some platforms have solved this problem by building the “JavaScript bridge.” This bridge lets JavaScript talk directly to native widgets.
This is much more performant than WebViews, because you eliminate the DOM from the equation, but it’s still not ideal. Every time your app needs to talk directly to the rendering engine, it has to be compiled to native code to “cross the bridge.” On a single interaction, the bridge must be crossed twice: once from platform to app, and then back from app to platform.
Flutter differs because it uses its own rendering engine, Skia, which is the same rendering engine used in Chrome. Skia can communicate with Flutter apps. As a result, Flutter accepts local events directly, rather than having to first compile them into JavaScript. This is essentially possible because Flutter compiles to native ARM code. This is the secret to its success. When your app is fired up on a user’s device, it’s entirely running in the language that the device’s operating system expects.
The JavaScript bridge is a marvel of modern programming, to be sure, but it presents three big problems.
The first problem is that debugging is hard. When there’s an error in the runtime compiler, that error has to be traced back across the JavaScript bridge and found in the JavaScript code. It may be in markup or CSS-like syntax as well. The debugger itself may not work as well as we’d like it to.
A second bigger issue, though, is performance. The JavaScript bridge is very expensive. Every time something in the app is tapped, that event must be sent across the bridge to your JavaScript app. The result, for lack of better term, is jank.
The third big problem, according to AirBnb, is that they found themselves having to dip down into the native code more often than they wanted to, which was a problem for their teams comprised mostly of JavaScript developers. (The jury is still out on this issue with Flutter, but I can say that I’ve never once had to try and write native code at my job. Some members of my team have created plugins in Objective-C and Java.)
The immediate benefits of Flutter
It’s likely, since you’re reading this article, that you’re interested in Flutter… but you might be skeptical. I admire how thorough you are in vetting technology.
Your reasons for being skeptical are fair. It’s a new technology. That means breaking changes in the API. It means missing support for important features (such as Google Maps). It seems possible that Google could abandon it altogether one day.
And, despite the fact that you believe Dart is great language, that doesn’t change the fact that Dart isn’t widely used, and many third-party libraries that you want may not exist.
I would argue against all those points, though. The API unlikely to change, as the Google uses Flutter internally on major revenue-generating apps, including Google AdWords. Dart has recently moved into version 2, which means it will likely be a while until it changes much. It will likely be years until breaking changes are introduced which, in a computer world, is practically forever.
Yes, there are indeed missing features, but Flutter gives you the complete control to add your own native plugins. In fact, many of the most important operating system plugins already exist, such as a map plugin, camera, location services, and device storage. The Dart and Flutter ecosystem and community already exists. It’s much smaller than the JavaScript community, of course, but I would argue that it’s concise. I see people every day contributing to existing packages, rather than creating new ones.
Now, let’s talk about Flutter’s specific benefits.
No JavaScript bridge
This is a major bottleneck in development and in your application’s performance. Again, it leads to jank. Scrolling isn’t smooth, it’s not always performant, and it’s hard to debug.
Flutter compiles to actual native code and is rendered using Skia. The app itself is running in native, so there’s no reason to convert Dart to native. This means that it doesn’t lose any of the performance or productivity when it’s running on a user’s device.
Compile time
If you’re coming from native development, one of your major pains is the development cycle. iOS is infamous for its insane compile times. In Flutter, a full compile generally takes less than 30 seconds, and incremental compiles are sub-seconds, thanks to hot-reload. At my day job, we develop features for our mobile client first because Flutter’s development cycle allows us to move so quickly. Only when we’re sure of our implementation do we go write those features in the web client.
Write once, test once, deploy everywhere
Not only do you get to write your app one time and deploy to iOS and Android, you also only have to write your tests once. Dart unit testing is quite easy, and Flutter includes a library for testing Widgets.
Code sharing
I’m going to be fair here: I suppose this is technically possible in JavaScript as well. But, it’s certainly not possible in native development. With Flutter and Dart, your web and mobile apps can share all the code, except each client’s views. (Of course, only if you’re using Dart for your web apps.) You can quite easily use dependency injection to run an AngularDart app and Flutter app with the same models and controllers.
And of course, even if you don’t want to share code between your web app and your mobile app, you’re sharing all your code between the iOS and Android apps.
In practical terms, this means that you are super productive. I mentioned that we develop our mobile features first at my day job. Because we share business logic between web and mobile, once the mobile feature is implemented, we only have to write views that expect that same controller data.
Productivity and collaboration
Gone are the days of separate teams for iOS and Android. In fact, whether your use Dart or JavaScript in your web apps, Flutter development is familiar enough that all your teams will be unified. It’s not a stretch by any means to expect a JavaScript web developer to also effectively develop in Flutter and Dart. If you believe me here, then it follows that your new unified team will be three times more productive.
Code maintenance
Nothing is more satisfying then fixing a bug once and having it corrected on all your clients. Only in very specific cases is there a bug in a iOS app produced with Flutter that is not also in the Android version (and vice versa). In 100% of these cases, these bugs aren’t bugs, but cosmetic issues because Flutter follows the device OS design systems in it’s built-in widgets. Because these are issues like text sizing or alignment, they are trivial in the context of using engineering time to fix.
Flutter for JavaScript developers
Since you’re reading CSS-Tricks, I’d be willing to bet you’re a web developer. If you’ve used any of today’s hottest frameworks (e.g. React, Angular, Vue, etc.), then you’ll be happy to know that picking up Flutter is easy.
Flutter is completely reactive, so the same mindset and paradigm that you’re used to with React carries over to Flutter. You’re essentially building a ton of small, reusable components (called Widgets in Flutter) just like React. These widgets are complete with lifecycle methods, and they’re written in classes. If you’ve used this syntax in React:
…then you’ll pick up Flutter with no problem. This is how you do the same in Flutter:
class MyWidget extends StatelessWidget {
//...
build(){}
}
And, just like React, Flutter favors composition over inheritance. For example, if you want to make a special AddToCartButton in React, you’d build a button with special functions and styles in JSX. That’s exactly how you do it in Flutter (minus the JSX).
Finally, the layout system in Flutter is similar to CSS rules we’re familiar with, like flexbox and absolute positioning.
This is also where a big difference in making views in Flutter comes in, though. In Flutter, literally everything is a Widget. There are some obvious, concrete Widgets, like Text, Button, and AppBar. But Animations and Layout declarations are also Widgets. To center text, you wrap a Text Widget in a Center Widget. To add padding, there’s a Padding Widget.
Imagine breaking down a React app to the smallest possible reusable components you could make. For example, what if you made a higher-order React component that simply took a prop “padding” and all it did was add that amount of padding to whatever was nested within it. That’s how Flutter works, because there is no CSS or markup.
In this sample picture, here are a few layout widgets that you might use, but you can’t ‘see’ as the user:
That may seem like a ton of monotonous work, but Flutter comes with many, many Widgets built right in (such as Padding and Center) so you don’t have to waste time doing that yourself.
If you want to make buttery smooth mobile apps in a familiar style, then yes! The performance and developer experience are both completely held in tact in Flutter. Its animations tick at 60fps, and it has a bundle of built-in Cupertino-style and Material Design-style Widgets. Or, long story short: it’s incredible how quick you can be productive in Flutter, without sacrificing native performance.
If you want to try Flutter today, here are a couple great places to start:
In 2016, Apple announced a new extension that will allow developers to better customize their push and local notifications called the UNNotificationContentExtension. The extension gets triggered when a user long presses or 3D touches on a notification whenever it is delivered to the phone or from the lock/home screen. In the content extension, developers can use a view controller to structure the UI of their notification, but there was no user interaction enabled within the view controller — until now. With the release of iOS 12 and XCode 10, the view controller in the content extension now enables user interaction which means notifications will become even more powerful and customizable.
At WWDC 2018, Apple also announced several changes to notification settings and how they appear on the home screen. In an effort to make users more aware of how they are using apps and allowing more user control of their app usage, there is a new notification setting called “Deliver Quietly.” Users can set your app to Delivery Quietly from the Notification Center, which means they will not receive banners or sound notifications from your app, but they will appear in the Notification Center. Apple using an in-house algorithm, which presumably tracks often you interact with notifications, will also ask users if they still want to receive notifications from particular apps and encourage you to turn on Deliver Quietly or turn them off completely.
Notifications are getting a big refresh in iOS 12, and I’ve only scratched the surface. In the rest of this article, we’ll go over the rest of the new notification features coming to iOS 12 and how you can implement them in your own app.
There are two ways to send push notifications to a device: remotely or locally. To send notifications remotely, you need a server that can send JSON payloads to Apple’s Push Notification Service. Along with a payload, you also need to send the device token and any other authentication certificate or tokens that verify your server is allowed to send the push notification through Apple. For this article, we focus on local notifications which do not need a separate server. Local notifications are requested and sent through the UNUserNotificationCenter. We’ll go over later how specifically to make the request for a local notification.
In order to send a notification, you first need to get permission from the user on whether or not they want you to send them notifications. With the release of iOS 12, there are a lot of changes to notification settings and permissions so let’s break it down. To test out any of the code yourself, make sure you have the Xcode 10 beta installed.
Notification Settings And Permissions
Deliver Quietly
Delivery Quietly is Apple’s attempt to allow users more control over the noise they may receive from notifications. Instead of going into the settings app and looking for the app whose notification settings you want to change, you can now change the setting directly from the notification. This means that a lot more users may turn off notifications for your app or just delivery them quietly which means the app will get badged and notifications only show up in the Notification Center. If your app has its own custom notification settings, Apple is allowing you to link directly to that screen from the settings management view pictured below.
In order to link to your custom notification setting screen, you must set providesAppNotificationSettings as a UNAuthorizationOption when you are requesting notification permissions in the app delegate.
In didFinishLaunchingWithOptions, add the following code:
When you do this, you’ll now see your custom notification settings in two places:
If the user selects Turn Off when they go to manage settings directly from the notification;
In the notification settings within the system’s Settings app.
You also have to make sure to handle the callback for when the user selects on either way to get to your notification settings. Your app delegate or an extension of your app delegate has to conform to the protocol UNUserNotificationCenterDelegate so you can then implement the following callback method:
Another new UNAuthorizationOption is provisional authorization. If you don’t mind your notifications being delivered quietly, you can set add .provisional to your authorization options as shown below. This means that you don’t have to prompt the user to allow notifications — the notifications will still show up in the Notification Center.
So now that you’ve determined how to request permission from the user to deliver notifications and how to navigate users to your own customized settings view, let’s go more into more detail about the actual notifications.
Sending Grouped Notifications
Before we get into the customization of the UI of a notification, let’s go over how to make the request for a local notification. First, you have to register any UNNotificationCategory, which are like templates for the notifications you want to send. Any notification set to a particular category will inherit any actions or options that were registered with that category. After you’ve requested permission to send notifications in didFinishLaunchingWithOptions, you can register your categories in the same method.
let hiddenPreviewsPlaceholder = "%u new podcast episodes available"
let summaryFormat = "%u more episodes of %@"
let podcastCategory = UNNotificationCategory(identifier: "podcast", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenPreviewsPlaceholder, categorySummaryFormat: summaryFormat, options: [])
UNUserNotificationCenter.current().setNotificationCategories([podcastCategory])
In the above code, I start by initiating two variables:
hiddenPreviewsPlaceholder
This placeholder is used in case the user has “Show Previews” off for your app; if we don’t have a placeholder there, your notification will show with only “Notification” also the text.
summaryFormat
This string is new for iOS 12 and coincides with the new feature called “Group Notifications” that will help the Notification Center look a lot cleaner. All notifications will show up in stacks which will be either representing all notifications from the app or specific groups that the developer has set for there app.
The code below shows how we associate a notification with a group.
@objc func sendPodcastNotification(for podcastName: String) {
let content = UNMutableNotificationContent()
content.body = "Introducing Season 7"
content.title = "New episode of (podcastName):"
content.threadIdentifier = podcastName.lowercased()
content.summaryArgument = podcastName
content.categoryIdentifier = NotificationCategoryType.podcast.rawValue
sendNotification(with: content)
}
For now, I’ve hardcoded the text of the notification just for the example. The threadIdentifier is what creates the groups that we show as stacks in the Notification Center. In this example, I want the notifications grouped by podcast so each notification you get is separated by what podcast it’s associated with. The summaryArgument matches back to our categorySummaryFormat we set in the app delegate. In this case, we want the string for the format: "%u more episodes of %@" to be the podcast name. Lastly, we have to set the category identifier to ensure the notification has the template we set in the app delegate.
func sendNotification(for category: String, with content: UNNotificationContent) {
let uuid = UUID().uuidString
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: uuid, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
The above method is how we request the notification to be sent to the device. The identifier for the request is just a random unique string; the content is passed in and we create the content in our sendPodcastNotification method, and lastly, the trigger is when you want the notification to send. If you want the notification to send immediately, you can set that parameter to nil.
Using the methods we’ve described above, here’s the result on the simulator. I have a button that has the sendPodcastNotification method as a target. I tapped the button three times to have the notifications sent to the device. In the first photo, I have “Show Previews” set to “Always” so I see the podcast and the name of the new episodes along with the summary that shows I have two more new episodes to check out. When “Show Previews” is set to “Never,” the result is the second image above. The user won’t see which podcast it is to respect the “No Preview” setting, but they can still see that I have three new episodes to check out.
Notification Content Extension
Now that we understand how to set our notification categories and make the request for them to be sent, we can go over how to customize the look of the notification using the Notification Service and Notification Content extensions. The Notification Service extension allows you to edit the notification content and download any attachments in your notification like images, audio or video files. The Notification Content extension contains a view controller and storyboard that allows you to customize the look of your notification as well as handle any user interaction within the view controller or taps on notification actions.
To add these extensions to your app go File ? New ? Target.
You can only add them one at a time, so name your extension and repeat the process to add the other. If a pop-up appears asking you to activate your new scheme, click the “Activate” button to set it up for debugging.
For the purpose of this tutorial, we will be focusing on the Notification Content Extension. For local notifications, we can include the attachments in the request, which we’ll go over later.
First, go to the Info.plist file in the Notification Content Extension target.
The following attributes are required:
UNNotificationExtensionCategory
A string value equal to the notification category which we created and set in the app delegate. This will let the content extension know which notification you want to have custom UI for.
UNNotificationExtensionInitialContentSizeRatio
A number between 0 and 1 which determines the aspect ratio of your UI. The default value is 1 which will allow your interface to have its total height equal to its width.
I’ve also set UNNotificationExtensionDefaultContentHidden to “YES” so that the default notification does not show when the content extension is running.
You can use the storyboard to set up your view or create the UI programmatically in the view controller. For this example I’ve set up my storyboard with an image view which will show the podcast logo, two labels for the title and body of the notification content, and a “Like” button which will show a heart image.
Now, in order to get the image showing for the podcast logo and the button, we need to go back to our notification request:
I added a folder in my project that contains all the images we need for the notification so we can access them through the main bundle.
For each image, we get the file path and use that to create a UNNotificationAttachment. Added that to our notification content allows us to access the images in the Notification Content Extension in the didReceive method shown below.
func didReceive(_ notification: UNNotification) {
self.newEpisodeLabel.text = notification.request.content.title
self.episodeNameLabel.text = notification.request.content.body
let imgAttachment = notification.request.content.attachments[0]
let buttonNormalStateAtt = notification.request.content.attachments[1]
let buttonHighlightStateAtt = notification.request.content.attachments[2]
guard let imageData = NSData(contentsOf: imgAttachment.url), let buttonNormalStateImgData = NSData(contentsOf: buttonNormalStateAtt.url), let buttonHighlightStateImgData = NSData(contentsOf: buttonHighlightStateAtt.url) else { return }
let image = UIImage(data: imageData as Data)
let buttonNormalStateImg = UIImage(data: buttonNormalStateImgData as Data)?.withRenderingMode(.alwaysOriginal)
let buttonHighlightStateImg = UIImage(data: buttonHighlightStateImgData as Data)?.withRenderingMode(.alwaysOriginal)
imageView.image = image
likeButton.setImage(buttonNormalStateImg, for: .normal)
likeButton.setImage(buttonHighlightStateImg, for: .selected)
}
Now we can use the file path URLs we set in the request to grab the data for the URL and turn them into images. Notice that I have two different images for the different button states which will allow us to update the UI for user interaction. When I run the app and send the request, here’s what the notification looks like:
Everything I’ve mentioned so far in relation to the content extension isn’t new in iOS 12, so let’s dig into the two new features: User Interaction and Dynamic Actions. When the content extension was first added in iOS 10, there was no ability to capture user touch within a notification, but now we can register UIControl events and respond when the user interacts with a UI element.
For this example, we want to show the user that the “Like” button has been selected or unselected. We already set the images for the .normal and .selected states, so now we just need to add a target for the UIButton so we can update the selected state.
override func viewDidLoad() {
super.viewDidLoad()
// Do any required interface initialization here.
likeButton.addTarget(self, action: #selector(likeButtonTapped(sender:)), for: .touchUpInside)
}
Now with the above code we get the following behavior:
In the selector method likeButtonTapped, we could also add any logic for saving the liked state in User Defaults or the Keychain, so we have access to it in our main application.
Notification actions have existed since iOS 10, but once you click on them, usually the user will be rerouted to the main application or the content extension is dismissed. Now in iOS 12, we can update the list of notification actions that are shown in response to which action the user selects.
First, let’s go back to our app delegate where we create our notification categories so we can add some actions to our podcast category.
Now when we run the app and send a notification, we see the following actions shown below:
When the user selects “Play,” we want the action to be updated to “Pause.” If they select “Queue Next,” we want that action to be updated to “Remove from Queue.” We can do this in our didReceive method in the Notification Content Extension’s view controller.
func didReceive(_ response: UNNotificationResponse, completionHandler completion:
(UNNotificationContentExtensionResponseOption) -> Void) {
guard let currentActions = extensionContext?.notificationActions else { return }
if response.actionIdentifier == "play-action" {
let pauseAction = UNNotificationAction(identifier: "pause-action", title: "Pause", options: [])
let otherAction = currentActions[1]
let newActions = [pauseAction, otherAction]
extensionContext?.notificationActions = newActions
} else if response.actionIdentifier == "queue-action" {
let removeAction = UNNotificationAction(identifier: "remove-action", title: "Remove from Queue", options: [])
let otherAction = currentActions[0]
let newActions = [otherAction, removeAction]
extensionContext?.notificationActions = newActions
} else if response.actionIdentifier == "pause-action" {
let playAction = UNNotificationAction(identifier: "play-action", title: "Play", options: [])
let otherAction = currentActions[1]
let newActions = [playAction, otherAction]
extensionContext?.notificationActions = newActions
} else if response.actionIdentifier == "remove-action" {
let queueAction = UNNotificationAction(identifier: "queue-action", title: "Queue Next", options: [])
let otherAction = currentActions[0]
let newActions = [otherAction, queueAction]
extensionContext?.notificationActions = newActions
}
completion(.doNotDismiss)
}
By resetting the extensionContext?.notificationActions list to contain the updated actions, it allows us to change the actions every time the user selects one. The behavior is shown in the gif below.
Summary
There’s a lot to do before iOS 12 launches to make sure your notifications are ready. The steps vary in complexity and you don’t have to implement them all. Make sure to first download XCode 10 beta so you can try out the features we’ve gone over. If you want to play around with the demo app I’ve referenced throughout the article, check it out on Github.
For Your Notification Permissions Request And Settings, You’ll Need To:
Determine whether or not you want to enable provisional authorization and add it to your authorization options.
If you have already have a customized notification settings view in your app, add providesAppNotificationSettings to your authorization options as well as implement the call back in your app delegate or whichever class conforms to UNUserNotificationCenterDelegate.
For Notification Grouping:
Add a thread identifier to your remote and local notifications so your notifications are correctly grouped in the Notification Center.
When registering your notification categories, add the category summary parameter if you want your grouped notification to be more descriptive than “more notifications.”
If you want to customize the summary text even more, then add a summary identifier to match whichever formatting you added for the category summary.
For Customized Rich Notifications:
Add the Notification Content extension target to your app to create rich notifications.
Design and implement the view controller to contain whichever elements you want in your notification.
Consider which interactive elements would be useful to you, i.e. buttons, table view, switches, etc.
Update the didReceive method in the view controller to respond to selected actions and update the list of actions if necessary.
Further Reading
“Notifications,” Apple’s list of various documentation regarding user notifications