High fives to Wufoo, our long-time sponsor here on CSS-Tricks. It’s powered the vast majority of forms I’ve built over the past decade. If you’ve never used it or heard of it: it’s a form builder. It makes the arduous task of implementing forms trivially easy. Building a form on Wufoo means you’ll get a form that does everything right UX-wise, gives you full design control, integrates with anything, and that you can put anywhere.
The feature list is too long to cover in the confines of a single post, so I always like to cover little bits that I’ve used recently and liked.
Don’t forget they have a robust API. I used the API to submit form entries on a form just the other day. I wanted to do some special things on a form, like be able to react to the DOM event of submitting the form. That’s not really possible when the form is in an <iframe>, but just fine when you host the form yourself and submit via API. Worked great.
I wanted to implement a notification message in one of my projects, similar to what you’d see in Google Docs while a document is saving. In other words, a message shows up indicating that the document is saving every time a change is made. Then, once the changes are saved, the message becomes: “All changes saved in Drive.”
Let’s take a look at how we might do that using a boolean value, but actually covering three possible states. This definitely isn’t the only way to do this, and frankly, I’m not even sure if it’s the best way. Either way, it worked for me.
Here’s an example of that “Saving…” state:
The “Saving” notification displays any time a change is made to the document.
…and the “Saved” state:
The “Saved” notification displays once a change or set of changes has completed.
Using a Boolean value for to define the state was my immediate reaction. I could have a variable called isSaving and use it to render a conditional string in my template, like so:
Now, whenever we start saving, we set the value to true and then set it to false whenever no save is in progress. Simple, right?
There is a problem here, though, and it’s a bit of a UX issue. The default message is rendered as “All changes saved.” When the user initially lands on the page, there is no saving taking place and we get the “Saved” message even though no save ever happened. I would prefer showing nothing until the first change triggers the first “Saving” message.
This calls for a third state in our variable: isSaving. Now the question becomes: do we change the value to a string variable as one of the three states? We could do that, but what if we could get the third state in our current boolean variable itself?
isSaving can take two values: true or false. But what is the value directly after we have declared it in the statement: let isSaving;? It’s undefined because the value of any variable is undefined when it’s declared, unless something is assigned to it. Great! We can use that initial undefined value to our advantage… but, this will require a slight change in how we write our condition in the template.
The ternary operator we are using evaluates to the second expression for anything that can’t be converted to true. The values undefined and false both are not true and, hence, resolve as false for the ternary operator. Even an if/else statement would work a similar way because else is evaluated for anything that isn’t true. But we want to differentiate between undefined and false . This is fixable by explicitly checking for false value, too, like so:
We are now strictly checking for true and false values. This made our ternary operator a little nested and difficult to read. If our template supports if/else statements, then we can refactor the template like this:
Have you ever created a well-intentioned, thoughtful design system only to watch it grow into an ever-increasing and scary codebase? I’ve been working in sort of the opposite direction, inheriting the scary codebase and trying to create a thoughtful system from it.
Here’s Alex Sanders on the topic, explaining how his team at The Guardian has tackled the task of creating design systems while combating complexity:
Systems that try to contain complexity over long periods of time by convention will inevitably tend toward entropy, because one significant characteristic of convention is that it is trivially simple to break one.
You do not even need to be malicious. A convention is not a line in the sand. You can have a very good case for breaking or stretching one, but once a convention is no longer fully observed, subsequent cases for breaking or stretching it are automatically stronger, because the convention is already weakened. The more this happens, the weaker it gets.
Complexity and entropy can be two outcomes in the same situation, but need not be mutually exclusive. Interesting to think that our best intentions to guard against complexity can be somewhat destructive.
I also love how Alex explains why it’s not possible for their team to use a Tachyons-esque approach to writing styles because of the way that their development environment is kinda slow. It would be painful for the team to make that switch, despite how it could solve some other problems. This reminded me that measuring problems in this way is why there can never be a single way to write CSS. We need that inherent flexibility, even at the expense of introducing inconsistencies. Hence, conventions being less of a line in the sand and more of a guide post.
On a separate note, I really like how Alex describes styles and attributes as the reasons why his team is writing those styles. It’s about aligning with business objectives:
…tens of thousands of rules that are intended to describe a maintainable set of responses to business and design problems.
That’s interesting since I don’t think we spend much time here talking specifically about the business side of CSS and the functional requirements that a styled user interface needs to accomplish.
And perhaps thinking about that can help us write better styles in the long term. Is this line of CSS solving a problem? Does this new class resolve an issue that will help our customers? These are good questions to keep in mind as we work, yet I know I don’t spend enough time thinking about them. I often see the design I’m turning into code as a problem to be solved instead.
Perhaps we should expand the way we styling a webpage because maybe, just maybe, it will help us write more maintainable code that’s built to solve a real business objective.
You scroll down to a certain point, now you want to style things in a certain way. A header becomes fixed. An animation triggers. A table of contents appears. To do anything based on scroll position, JavaScript is required right now. You watch the scroll position via a DOM event and alter an element’s styling based on that position. Or, probably better if you can, use IntersectionObserver. We just blogged about all this.
Now there is a new (unofficial) spec trying to bring these possibilities to CSS. I love it when web standards get involved because it sees authors like us trying to pull off certain design effects and wants to (presumably) help make it easier and more performant. I also like how this spec lists editors from Mozilla and Google and Apple.
I wonder how they’ll handle the infinite-loop stuff here. Like you scroll to a point, it triggers some animation, which moves some element such that it changes the scroll position, which stops the animation, which moves the scroll position again… etc. I also wonder why it’s all specific to animation. „Scroll-position styling” seems like it would have the widest appeal and use level of usefulness.
The idea behind most of web applications is to fetch data from the database and present it to the user in the best possible way. When we deal with data there are cases when the best possible way of presentation means creating a list.
Depending on the amount of data and its content, we may decide to show all content at once (very rarely), or show only a specific part of a bigger data set (more likely). The main reason behind showing only part of the existing data is that we want to keep our applications as performant as possible and avoid loading or showing unnecessary data.
If we decide to show our data in „chunks” then we need a way to navigate through that collection. The two most common ways of navigating through set of data are:
The first is pagination, a technique that splits the set of data into a specific number of pages, saving users from being overwhelmed by the amount of data on one page and allowing them to view one set of results at a time. Take this very blog you’re reading, for example. The homepage lists the latest 10 posts. Viewing the next set of latest posts requires clicking a button.
The second common technique is infinite scrolling, something you’re likely familiar with if you’ve ever scrolled through a timeline on either Facebook or Twitter.
The Apple News app also uses infinite scroll to browse a list of articles.
We’re going to take a deeper look at the first type in this post. Pagination is something we encounter on a near-daily basis, yet making it is not exactly trivial. It’s a great use case for a component, so that’s exactly what we’re going to do. We will go through the process of creating a component that is in charge of displaying that list, and triggering the action that fetches additional articles when we click on a specific page to be displayed. In other words, we’re making a pagination component in Vue.js like this:
Let’s go through the steps together.
Step 1: Create the ArticlesList component in Vue
Let’s start by creating a component that will show a list of articles (but without pagination just yet). We’ll call it ArticlesList. In the component template, we’ll iterate through the set of articles and pass a single article item to each ArticleItem component.
Now we need to create a method that will load the next page, the previous page or a selected page.
In the pageChangeHandle method, before loading new articles, we change the currentPage value depending on a property passed to the method and fetch the data respective to a specific page from the API. Upon receiving new data, we replace the existing articles array with the fresh data containing a new page of articles.
The component will accept currentPage and pageCount properties from the parent component and emit proper actions back to the parent when the next or previous button is clicked. It will also be responsible for disabling buttons when we are on the first or last page to prevent moving out of the existing collection.
That was the easy part. Now we need to create a list of page numbers, each allowing us to select a specific page. The number of pages should be customizable and we also need to make sure not to show any pages that may lead us beyond the collection range.
Requirement 2: Allow the user to go to a specific page within a range
Let’s start by creating a component that will be used as a single page number. I called it BasePaginationTrigger. It will do two things: show the page number passed from the BasePagination component and emit an event when the user clicks on a specific number.
In the script section, we need to add one more method (onLoadPage) that will be fired when the loadPage event is emitted from the trigger component. This method will receive a page number that was clicked and emit the event up to the ArticlesList component.
Requirement 3: Change the range of page numbers based on the current page
OK, now we have a single trigger that shows us the current page and allows us to fetch the same page again. Pretty useless, don’t you think? Let’s make some use of that newly created trigger component. We need a list of pages that will allow us to jump from one page to another without needing to go through the pages in between.
We also need to make sure to display the pages in a nice manner. We always want to display the first page (on the far left) and the last page (on the far right) on the pagination list and then the remaining pages between them.
We have three possible scenarios:
The selected page number is smaller than half of the list width (e.g. 1 – 2 – 3 – 4 – 18)
The selected page number is bigger than half of the list width counting from the end of the list (e.g. 1 – 15 – 16 – 17 – 18)
All other cases (e.g. 1 – 4 – 5 – 6 – 18)
To handle these cases, we will create a computed property that will return an array of numbers that should be shown between the next and previous buttons. To make the component more reusable we will accept a property visiblePagesCount that will specify how many pages should be visible in the pagination component.
Before going to the cases one by one we create few variables:
visiblePagesThreshold:- Tells us how many pages from the centre (selected page should be shown)
paginationTriggersArray: Array that will be filled with page numbers
visiblePagesCount: Creates an array with the required length
Scenario 1: The selected page number is smaller than half of the list width
We set the first element to always be equal to 1. Then we iterate through the list, adding an index to each element. At the end, we add the last value and set it to be equal to the last page number — we want to be able to go straight to the last page if we need to.
Scenario 2: The selected page number is bigger than half of the list width counting from the end of the list
Similar to the previous scenario, we start with the last page and iterate through the list, this time subtracting the index from each element. Then we reverse the array to get the proper order and push 1 into the first place in our array.
We know what number should be in the center of our list: the current page. We also know how long the list should be. This allows us to get the first number in our array. Then we populate the list by adding an index to each element. At the end, we push 1 into the first place in our array and replace the last number with our last page number.
And we are done! We just built a nice and reusable pagination component in Vue.
When to avoid this pattern
Although this component is pretty sweet, it’s not a silver bullet for all use cases involving pagination.
For example, it’s probably a good idea to avoid this pattern for content that streams constantly and has a relatively flat structure, like each item is at the same level of hierarchy and has a similar chance of being interesting to the user. In other words, something less like an article with multiple pages and something more like main navigation.
Another example would be browsing news rather than looking for a specific news article. We do not need to know where exactly the news is and how much we scrolled to get to a specific article.
That’s a wrap!
Hopefully this is a pattern you will be able to find useful in a project, whether it’s for a simple blog, a complex e-commerce site, or something in between. Pagination can be a pain, but having a modular pattern that not only can be re-used, but considers a slew of scenarios, can make it much easier to handle.
Brad Frost wrote about a recent experience with a website that used <input type="number">:
Last week I got a call from my bank regarding a wire transfer I had just scheduled. The customer support guy had me repeat everything back to him because there seemed to be a problem with the information. “Hmmmm, everything you said is right right except the last 3 digits of the account number.”
He had me resubmit the wire transfer form. When I exited the account number field, the corner of my eye noticed the account number change ever so slightly. I quickly refocused into the field and slightly moved my index finger up on my Magic Mouse. It started looking more like a slot machine than an input field!
Brad argues that we then shouldn’t be using <input type="number"> for “account numbers, social security numbers, credit card numbers, confirmation numbers” which makes a bunch of sense to me! Instead we can use the pattern attribute that Chris Ferdinandi looked at a while back in a post all about constraint validation in HTML.
It’s worth mentioning that numeric inputs can be more complex than they appear and that their appearance and behavior vary between browsers. All good things to consider along alongside Brad’s advice when evaluating user experience.
Also:
<input inputmode="numeric"> is the way forward for mobile numeric keyboards (paired with `pattern="[0-9]*"` on iOS pending inputmode support).
Refactoring is one of those words that evokes fear in the eyes of many folks, from developers to product owners and everyone in between. It may as well be a four-letter word in many ways. It’s also something that we talk about quite a bit around here because, like books on the topic, where to start with one, and the impact of letting technical debt pile up.
Ben Rady has thoughts on refactoring as well, but in the context of pair programming:
We pair for about 6 hours a day, every day. Everything that’s on the critical path is worked on in a pair. Always. Our goal is always to get the thing we’re working on to production as fast as we responsibly can, and the best way I’ve found to that is with a pair.
Ben then dives into the process of working alongside others and how to ship software with that approach, a lot of which I think relates to front-end development best practices, too. But I also love how punk rock this team is, as they appear not to develop software with a backlog or a ton of meetings for managing their projects:
No formal backlog. We have three states for new features. Now, next, and probably never. Whatever we’re working on now is the most valuable thing we can think of. Whatever’s next is the next most valuable thing. When we pull new work, we ask „What’s next?” and discuss. If someone comes to us with an idea, we ask „Is this more valuable that what we were planning to do next?” If not, it’s usually forgotten, because by the time we finish that there’s something else that’s newer and better. But if it comes up again, maybe it’ll make the cut.
I wonder how much time a year they save without having to argue about stories and points and whether this one tiny feature is more important than this other one. Anyway, I find all of this stuff thoroughly inspiring.
You can make a garden variety anchor link (<a>) open up a new email. Let’s take a little journey into this feature. It’s pretty easy to use, but as with anything web, there are lots of things to consider.
But we immediately run into a handful of UX issues. One of them is that clicking that link surprises some people in a way they don’t like. Sort of the same way clicking on a link to a PDF opens a file instead of a web page. Le sigh. We’ll get to that in a bit.
„Open in new tab” sometimes does matter.
If a user has their default mail client (e.g. Outlook, Apple Mail, etc.) set up to be a native app, it doesn’t really matter. They click a mailto: link, that application opens up, a new email is created, and it behaves the same whether you’ve attempted to open that link in a new tab or not.
But if a user has a browser-based email client set up, it does matter. For example, you can allow Gmail to be your default email handler on Chrome. In that case, the link behaves like any other link, in that if you don’t open in a new tab, the page will redirect to Gmail.
I’m a little on the fence about it. I’ve weighed in on opening links in new tabs before, but not specifically about opening emails. I’d say I lean a bit toward using target="_blank" on mail links, despite my feelings on using it in other scenarios.
You can send to multiple email addresses, and even carbon copy (CC), and blind carbon copy (BCC) people on the email. The trick is more query parameters and comma-separating the email addresses.
I’m not sure how useful this is, but it’s an interesting curiosity that you can make a <form> do a GET, which is basically a redirect to a URL — and that URL can be in the mailto: format with query params populated by the inputs! It can even open in a new tab.
Because mailto: links are valid anchor links like any other, they are typically styled exactly the same. But clicking them clearly produces very different results. It may be worthwhile to indicate mailto: links in a special way.
If you use an actual email address as the link, that’s probably a good indication:
We’ve explored how powerful these tools can be by creating our own custom modal dialog, an element that can be used in most modern application contexts regardless of the underlying framework or library. In this article, we will look at how to consume our element in the various frameworks and look at some advanced tooling to really ramp up your Web Component skills.
Our dialog component works great in almost any framework or even without one. (Granted, if JavaScript is disabled, the whole thing is for naught.) Angular and Vue treat Web Components as first-class citizens: the frameworks have been designed with web standards in mind. React is slightly more opinionated, but not impossible to integrate.
Angular
First, let’s take a look at how Angular handles custom elements. By default, Angular will throw a template error whenever it encounters an element it doesn’t recognize (i.e. the default browser elements or any of the components defined by Angular). This behavior can be changed by including the CUSTOM_ELEMENTS_SCHEMA.
…allows an NgModule to contain the following:
Non-Angular elements named with dash case (-).
Element properties named with dash case (-). Dash case is the naming convention for custom elements.
Vue’s compatibility with Web Components is even better than Angular’s as it doesn’t require any special configuration. Once an element is registered, it can be used with Vue’s default templating syntax:
One caveat with Angular and Vue, however, is their default form controls. If we wish to use something like reactive forms or [(ng-model)] in Angular or v-model in Vue on a custom element with a form control, we will need to set up that plumbing for which is beyond the scope of this article.
React
React is slightly more complicated than Angular. React’s virtual DOM effectively takes a JSX tree and renders it as a large object. So, instead of directly modifying attributes on HTML elements like Angular or Vue, React uses an object syntax to track changes that need to be made to the DOM and updates them in bulk. This works just fine in most cases. Our dialog’s open attribute is bound to its property and will respond perfectly well to changing props.
The catch comes when we start to look at the CustomEvent dispatched when our dialog closes. React implements a series of native event listeners for us with their synthetic event system. Unfortunately, that means that controls like onDialogClosed won’t actually attach event listeners to our component, so we have to find some other way.
The most obvious means of adding custom event listeners in React is by using DOM refs. In this model, we can reference our HTML node directly. The syntax is a bit verbose, but works great:
import React, { Component, createRef } from 'react';
export default class MyComponent extends Component {
constructor(props) {
super(props);
// Create the ref
this.dialog = createRef();
// Bind our method to the instance
this.onDialogClosed = this.onDialogClosed.bind(this);
this.state = {
open: false
};
}
componentDidMount() {
// Once the component mounds, add the event listener
this.dialog.current.addEventListener('dialog-closed', this.onDialogClosed);
}
componentWillUnmount() {
// When the component unmounts, remove the listener
this.dialog.current.removeEventListener('dialog-closed', this.onDialogClosed);
}
onDialogClosed(event) { /** Omitted **/ }
render() {
return <div>
<one-dialog open={this.state.open} ref={this.dialog}>
<span slot="heading">Heading text</span>
<div>
<p>Body copy</p>
</div>
</one-dialog>
</div>
}
}
Or, we can use stateless functional components and hooks:
That’s not bad, but you can see how reusing this component could quickly become cumbersome. Luckily, we can export a default React component that wraps our custom element using the same tools.
import React, { Component, createRef } from 'react';
import PropTypes from 'prop-types';
export default class OneDialog extends Component {
constructor(props) {
super(props);
// Create the ref
this.dialog = createRef();
// Bind our method to the instance
this.onDialogClosed = this.onDialogClosed.bind(this);
}
componentDidMount() {
// Once the component mounds, add the event listener
this.dialog.current.addEventListener('dialog-closed', this.onDialogClosed);
}
componentWillUnmount() {
// When the component unmounts, remove the listener
this.dialog.current.removeEventListener('dialog-closed', this.onDialogClosed);
}
onDialogClosed(event) {
// Check to make sure the prop is present before calling it
if (this.props.onDialogClosed) {
this.props.onDialogClosed(event);
}
}
render() {
const { children, onDialogClosed, ...props } = this.props;
return <one-dialog {...props} ref={this.dialog}>
{children}
</one-dialog>
}
}
OneDialog.propTypes = {
children: children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]).isRequired,
onDialogClosed: PropTypes.func
};
There are a number of great tools for authoring your own custom elements. Searching through npm reveals a multitude of tools for creating highly-reactive custom elements (including my own pet project), but the most popular today by far is lit-html from the Polymer team and, more specifically for Web Components, LitElement.
LitElement is a custom elements base class that provides a series of APIs for doing all of the things we’ve walked through so far. It can be run in a browser without a build step, but if you enjoy using future-facing tools like decorators, there are utilities for that as well.
Before diving into how to use lit or LitElement, take a minute to familiarize yourself with tagged template literals, which are a special kind of function called on template literal strings in JavaScript. These functions take in an array of strings and a collection of interpolated values and can return anything you might want.
function tag(strings, ...values) {
console.log({ strings, values });
return true;
}
const who = 'world';
tag`hello ${who}`;
/** would log out { strings: ['hello ', ''], values: ['world'] } and return true **/
What LitElement gives us is live, dynamic updating of anything passed to that values array, so as a property updates, the element’s render function would be called and the resulting DOM would be re-rendered
import { LitElement, html } from 'lit-element';
class SomeComponent {
static get properties() {
return {
now: { type: String }
};
}
connectedCallback() {
// Be sure to call the super
super.connectedCallback();
this.interval = window.setInterval(() => {
this.now = Date.now();
});
}
disconnectedCallback() {
super.disconnectedCallback();
window.clearInterval(this.interval);
}
render() {
return html`<h1>It is ${this.now}</h1>`;
}
}
customElements.define('some-component', SomeComponent);
What you will notice is that we have to define any property we want LitElement to watch using the static properties getter. Using that API tells the base class to call render whenever a change is made to the component’s properties. render, in turn, will update only the nodes that need to change.
So, for our dialog example, it would look like this using LitElement:
There are several variants of lit-html available, including Haunted, a React hooks-style library for Web Components that can also make use of virtual components using lit-html as a base.
At the end of the day, most of the modern Web Components tools are various flavors of what LitElement is: a base class that abstracts common logic away from our components. Among the other flavors are Stencil, SkateJS, Angular Elements and Polymer.
What’s next
Web Components standards are continuing to evolve and new features are being discussed and added to browsers on an ongoing basis. Soon, Web Component authors will have APIs for interacting with web forms at a high level (including other element internals that are beyond the scope of these introductory articles), like native HTML and CSS module imports, native template instantiation and updating controls, and many more which can be tracked on the W3C/web components issues board on GitHub.
These standards are ready to adopt into our projects today with the appropriate polyfills for legacy browsers and Edge. And while they may not replace your framework of choice, they can be used alongside them to augment you and your organization’s workflows.
One of the most empowering things you can learn as a new front-end developer who is starting to learn JavaScript is to change classes. If you can change classes, you can use your CSS skills to control a lot on a page. Toggle a class to one thing, style it this way, toggle to another class (or remove it) and style it another way.
Extending that toggle-based thinking, what is a user menu if not for a single accordion? Same with modals. If we went that route, we could make JavaScript optional on those dynamic things. That’s exactly what GitHub did with their menu.
Inside the <details> element, GitHub uses some Web Components (that do require JavaScript) to do some bonus stuff, but they aren’t required for basic menu functionality. That means the menu is resilient and instantly interactive when the page is rendered.
We went all in on details to turn a lot of things interactive without JS. There is also https://t.co/SFXtkNzIbZ, and here is a talk I gave on leveraging the power of details, which mentions the CSS trick 🙂 https://t.co/DmX8opvi4z
The worst strike on <details> is its browser support in Edge, but I guess we won’t have to worry about that soon, as Edge will be using Chromium… soon? Does anyone know?